summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamie Lennox <jamielennox@redhat.com>2014-10-23 17:20:29 +0200
committerJamie Lennox <jamielennox@redhat.com>2014-12-09 14:02:44 +1000
commitf8b09b565f56e76fe4ea253ff39f313501a2d4f3 (patch)
tree4dd26feb63a438ac6a1c0267d614aaf22e1cb8e7
parent6ed9ec4194a233c4da8ea45e0687e9108aa6863c (diff)
downloadkeystonemiddleware-f8b09b565f56e76fe4ea253ff39f313501a2d4f3.tar.gz
Allow loading other auth methods in auth_token
Add handlers for loading user specified authentication plugins from the config file. If an auth plugin is not specified then we fall back to using the older legacy options. Change-Id: I4d09a30c0163106dab52062fab6000aaec0efb5d Closes-Bug: #1372142 Blueprint: pluggable-auth
-rw-r--r--keystonemiddleware/auth_token.py54
-rw-r--r--keystonemiddleware/tests/test_auth_token_middleware.py89
2 files changed, 125 insertions, 18 deletions
diff --git a/keystonemiddleware/auth_token.py b/keystonemiddleware/auth_token.py
index 785f47a..3066b46 100644
--- a/keystonemiddleware/auth_token.py
+++ b/keystonemiddleware/auth_token.py
@@ -353,6 +353,7 @@ _OPTS = [
_AUTHTOKEN_GROUP = 'keystone_authtoken'
CONF = cfg.CONF
CONF.register_opts(_OPTS, group=_AUTHTOKEN_GROUP)
+auth.register_conf_options(CONF, _AUTHTOKEN_GROUP)
_HEADER_TEMPLATE = {
'X%s-Domain-Id': 'domain_id',
@@ -833,16 +834,24 @@ class AuthProtocol(object):
_LI('Invalid service token - rejecting request'))
return self._reject_request(env, start_response)
+ except exceptions.NoMatchingPlugin as e:
+ msg = _LC('Required auth plugin does not exist. %s') % e
+ self._LOG.critical(msg)
+ return self._do_503_error(env, start_response)
+
except ServiceError as e:
self._LOG.critical(_LC('Unable to obtain admin token: %s'), e)
- resp = _MiniResp('Service unavailable', env)
- start_response('503 Service Unavailable', resp.headers)
- return resp.body
+ return self._do_503_error(env, start_response)
self._LOG.debug("Received request from %s" % _fmt_msg(env))
return self._call_app(env, start_response)
+ def _do_503_error(self, env, start_response):
+ resp = _MiniResp('Service unavailable', env)
+ start_response('503 Service Unavailable', resp.headers)
+ return resp.body
+
def _init_auth_headers(self):
"""Initialize auth header list.
@@ -1332,21 +1341,30 @@ class AuthProtocol(object):
timeout=self._conf_get('http_connect_timeout')
))
- # NOTE(jamielennox): Loading AuthTokenPlugin here should be exactly
- # the same as calling _AuthTokenPlugin.load_from_conf_options(CONF,
- # GROUP) however we can't do that because we have to use _conf_get
- # to support the paste.ini options.
- auth_plugin = _AuthTokenPlugin.load_from_options(
- auth_host=self._conf_get('auth_host'),
- auth_port=int(self._conf_get('auth_port')),
- auth_protocol=self._conf_get('auth_protocol'),
- auth_admin_prefix=self._conf_get('auth_admin_prefix'),
- admin_user=self._conf_get('admin_user'),
- admin_password=self._conf_get('admin_password'),
- admin_tenant_name=self._conf_get('admin_tenant_name'),
- admin_token=self._conf_get('admin_token'),
- identity_uri=self._conf_get('identity_uri'),
- log=self._LOG)
+ # NOTE(jamielennox): The original auth mechanism allowed deployers
+ # to configure authentication information via paste file. These
+ # are accessible via _conf_get, however this doesn't work with the
+ # plugin loading mechanisms. For using auth plugins we only support
+ # configuring via the CONF file.
+ auth_plugin = auth.load_from_conf_options(CONF, _AUTHTOKEN_GROUP)
+
+ if not auth_plugin:
+ # NOTE(jamielennox): Loading AuthTokenPlugin here should be
+ # exactly the same as calling
+ # _AuthTokenPlugin.load_from_conf_options(CONF, GROUP) however
+ # we can't do that because we have to use _conf_get to support
+ # the paste.ini options.
+ auth_plugin = _AuthTokenPlugin.load_from_options(
+ auth_host=self._conf_get('auth_host'),
+ auth_port=int(self._conf_get('auth_port')),
+ auth_protocol=self._conf_get('auth_protocol'),
+ auth_admin_prefix=self._conf_get('auth_admin_prefix'),
+ admin_user=self._conf_get('admin_user'),
+ admin_password=self._conf_get('admin_password'),
+ admin_tenant_name=self._conf_get('admin_tenant_name'),
+ admin_token=self._conf_get('admin_token'),
+ identity_uri=self._conf_get('identity_uri'),
+ log=self._LOG)
adap = adapter.Adapter(
sess,
diff --git a/keystonemiddleware/tests/test_auth_token_middleware.py b/keystonemiddleware/tests/test_auth_token_middleware.py
index 658428c..b46aa2e 100644
--- a/keystonemiddleware/tests/test_auth_token_middleware.py
+++ b/keystonemiddleware/tests/test_auth_token_middleware.py
@@ -31,6 +31,7 @@ from keystoneclient import exceptions
from keystoneclient import fixture
from keystoneclient import session
import mock
+from oslo.config import fixture as cfg_fixture
from oslo.serialization import jsonutils
from oslo.utils import timeutils
from requests_mock.contrib import fixture as rm_fixture
@@ -2505,5 +2506,93 @@ class DefaultAuthPluginTests(testtools.TestCase):
self.assertEqual(token.token_id, plugin.get_token(self.session))
+class AuthProtocolLoadingTests(BaseAuthTokenMiddlewareTest):
+
+ AUTH_URL = 'http://auth.url/prefix'
+ DISC_URL = 'http://disc.url/prefix'
+ KEYSTONE_BASE_URL = 'http://keystone.url/prefix'
+ CRUD_URL = 'http://crud.url/prefix'
+
+ # NOTE(jamielennox): use the /v2.0 prefix here because this is what's most
+ # likely to be in the service catalog and we should be able to ignore it.
+ KEYSTONE_URL = KEYSTONE_BASE_URL + '/v2.0'
+
+ def setUp(self):
+ super(AuthProtocolLoadingTests, self).setUp()
+ self.cfg = self.useFixture(cfg_fixture.Config())
+
+ def test_loading_password_plugin(self):
+ # the password options aren't set on config until loading time, but we
+ # need them set so we can override the values for testing, so force it
+ opts = auth.get_plugin_options('password')
+ self.cfg.register_opts(opts, group=auth_token._AUTHTOKEN_GROUP)
+
+ project_id = uuid.uuid4().hex
+
+ # configure the authentication options
+ self.cfg.config(auth_plugin='password',
+ username='testuser',
+ password='testpass',
+ auth_url=self.AUTH_URL,
+ project_id=project_id,
+ user_domain_id='userdomainid',
+ group=auth_token._AUTHTOKEN_GROUP)
+
+ # admin_token is the token that the service will get back from auth
+ admin_token_id = uuid.uuid4().hex
+ admin_token = fixture.V3Token(project_id=project_id)
+ s = admin_token.add_service('identity', name='keystone')
+ s.add_standard_endpoints(admin=self.KEYSTONE_URL)
+
+ # user_token is the data from the user's inputted token
+ user_token_id = uuid.uuid4().hex
+ user_token = fixture.V3Token()
+ user_token.set_project_scope()
+
+ # first touch is to discover the available versions at the auth_url
+ self.requests.get(self.AUTH_URL,
+ json=fixture.DiscoveryList(href=self.DISC_URL),
+ status_code=300)
+
+ # then we use the url returned from discovery to actually auth
+ self.requests.post(self.DISC_URL + '/v3/auth/tokens',
+ json=admin_token,
+ headers={'X-Subject-Token': admin_token_id})
+
+ # then we do discovery on the URL from the service catalog. In practice
+ # this is mostly the same URL as before but test the full range.
+ self.requests.get(self.KEYSTONE_BASE_URL + '/',
+ json=fixture.DiscoveryList(href=self.CRUD_URL),
+ status_code=300)
+
+ # actually authenticating the user will then use the base url that was
+ # retrieved from discovery from the service catalog.
+ self.requests.get(self.CRUD_URL + '/v3/auth/tokens',
+ request_headers={'X-Subject-Token': user_token_id,
+ 'X-Auth-Token': admin_token_id},
+ json=user_token)
+
+ body = uuid.uuid4().hex
+ app = auth_token.AuthProtocol(new_app('200 OK', body)(), {})
+
+ req = webob.Request.blank('/')
+ req.headers['X-Auth-Token'] = user_token_id
+ resp = app(req.environ, self.start_fake_response)
+
+ self.assertEqual(200, self.response_status)
+ self.assertEqual(six.b(body), resp[0])
+
+ def test_invalid_plugin_503(self):
+ self.cfg.config(auth_plugin=uuid.uuid4().hex,
+ group=auth_token._AUTHTOKEN_GROUP)
+ app = auth_token.AuthProtocol(new_app('200 OK', '')(), {})
+
+ req = webob.Request.blank('/')
+ req.headers['X-Auth-Token'] = uuid.uuid4().hex
+ app(req.environ, self.start_fake_response)
+
+ self.assertEqual(503, self.response_status)
+
+
def load_tests(loader, tests, pattern):
return testresources.OptimisingTestSuite(tests)