Add keystone backend

This patch adds keystone support for the client. It is possible to
either provide os_auth + endpoint through the CLI or all other required
params to retrieve both or one of them.

Implements blueprint keystone-integration

Change-Id: Id059d075aabb26f92c82b0a863d85743d8564f69
This commit is contained in:
Flavio Percoco
2013-09-24 15:02:04 +02:00
parent dc6f33dcc8
commit f145031621
3 changed files with 209 additions and 0 deletions

View File

@@ -13,14 +13,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
import six
from marconiclient.auth import base
if not six.PY3:
from marconiclient.auth import keystone
else:
keystone = None
# NOTE(flaper87): Replace with logging
print('Keystone auth does not support Py3K')
_BACKENDS = {
'noauth': base.NoAuth,
}
if keystone:
_BACKENDS['keystone'] = keystone.KeystoneAuth
def _register_opts(conf):
"""Registers auth cli options.

View File

@@ -0,0 +1,126 @@
# Copyright (c) 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo.config import cfg
from keystoneclient.v2_0 import client as ksclient
from marconiclient.auth import base
from marconiclient.common import utils
# NOTE(flaper87): Some of the code below
# was brought to you by the very unique
# work of ceilometerclient.
class KeystoneAuth(base.AuthBackend):
_CLI_OPTIONS = [
cfg.StrOpt("os_username", default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME]'),
cfg.StrOpt("os_password", default=utils.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD]'),
cfg.StrOpt("os_project_id", default=utils.env('OS_PROJECT_ID'),
help='Defaults to env[OS_PROJECT_ID]'),
cfg.StrOpt("os_project_name", default=utils.env('OS_PROJECT_NAME'),
help='Defaults to env[OS_PROJECT_NAME]'),
cfg.StrOpt("os_auth_url", default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL]'),
cfg.StrOpt("os_auth_token", default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN]'),
cfg.StrOpt("os_region_name", default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME]'),
cfg.StrOpt("os_service_type", default=utils.env('OS_SERVICE_TYPE'),
help='Defaults to env[OS_SERVICE_TYPE]'),
cfg.StrOpt("os_service_type", default=utils.env('OS_SERVICE_TYPE'),
help='Defaults to env[OS_SERVICE_TYPE]'),
cfg.StrOpt("os_endpoint_type", default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE]'),
]
def __init__(self, conf):
super(KeystoneAuth, self).__init__(conf)
conf.register_cli_opts(self._CLI_OPTIONS)
def _get_ksclient(self, **kwargs):
"""Get an endpoint and auth token from Keystone.
:param kwargs: keyword args containing credentials:
* username: name of user
* password: user's password
* auth_url: endpoint to authenticate against
* insecure: allow insecure SSL (no cert verification)
* project_{name|id}: name or ID of project
"""
return ksclient.Client(username=self.conf.os_username,
password=self.conf.os_password,
tenant_id=self.conf.os_project_id,
tenant_name=self.conf.os_project_name,
auth_url=self.conf.os_auth_url,
insecure=self.conf.insecure)
def _get_endpoint(self, client):
"""Get an endpoint using the provided keystone client."""
return client.service_catalog.url_for(
service_type=self.conf.service_type or 'queuing',
endpoint_type=self.conf.endpoint_type or 'publicURL')
def authenticate(self, api_version, request):
"""Get an authtenticated client, based on the credentials
in the keyword args.
:param api_version: the API version to use ('1' or '2')
:param request: The request spec instance to modify with
the auth information.
"""
token = self.conf.os_auth_token
if not self.conf.os_auth_token or not request.endpoint:
# NOTE(flaper87): Lets assume all the
# required information was provided
# either through env variables or CLI
# params. Let keystoneclient fail otherwise.
ks_kwargs = {
'username': self.conf.os_username,
'password': self.conf.os_password,
'tenant_id': self.conf.os_project_id,
'tenant_name': self.conf.os_project_name,
'auth_url': self.conf.os_auth_url,
'service_type': self.conf.os_service_type,
'endpoint_type': self.conf.os_endpoint_type,
'insecure': self.conf.insecure,
}
_ksclient = self._get_ksclient(**ks_kwargs)
if not token:
token = _ksclient.auth_token
if not request.endpoint:
request.endpoint = self._get_endpoint(_ksclient, **ks_kwargs)
# NOTE(flaper87): Update the request spec
# with the final token.
request.headers['X-Auth-Token'] = token
return request

View File

@@ -0,0 +1,71 @@
# Copyright (c) 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from oslo.config import cfg
try:
from keystoneclient.v2_0 import client as ksclient
except ImportError:
ksclient = None
from marconiclient import auth
from marconiclient.tests import base
from marconiclient.transport import request
class _FakeKeystoneClient(object):
auth_token = 'test-token'
def __init__(self, *args, **kwargs):
pass
class TestKeystoneAuth(base.TestBase):
def setUp(self):
super(TestKeystoneAuth, self).setUp()
if not ksclient:
self.skipTest('Keystone client is not installed')
auth._register_opts(self.conf)
self.config(auth_backend='keystone')
self.auth = auth.get_backend(self.conf)
# FIXME(flaper87): Remove once insecure is added
# in the right place.
self.conf.register_opt(cfg.BoolOpt('insecure', default=False))
def test_no_token(self):
test_endpoint = 'http://example.org:8888'
with mock.patch.object(ksclient, 'Client',
new_callable=lambda: _FakeKeystoneClient):
with mock.patch.object(self.auth, '_get_endpoint') as get_endpoint:
get_endpoint.return_value = test_endpoint
req = self.auth.authenticate(1, request.Request())
self.assertEqual(req.endpoint, test_endpoint)
self.assertIn('X-Auth-Token', req.headers)
def test_with_token(self):
self.config(os_auth_token='test-token')
req = request.Request(endpoint='http://example.org:8888')
req = self.auth.authenticate(1, req)
self.assertIn('X-Auth-Token', req.headers)
self.assertIn(req.headers['X-Auth-Token'], 'test-token')