From 37d133aa2ee455caec80bdadbd102b0568f0c00f Mon Sep 17 00:00:00 2001 From: Adam Young Date: Tue, 27 May 2014 21:51:12 -0400 Subject: [PATCH] Kerberos as method name To date kerberos has been supported by the "external" method name. However, the Client plugin architecture needs to refer to the method name, and we do not want to expose to the client the difference between kerberos as performed by an external module or an eventual kerberos-in-eventlet style implementation. If the "external" plugin is missing, the old code would throw an exception attempting to process "REMOTE_USER" behavior. Now, if only 'kerberos' is specified, this is checked and skipped. Blueprint: kerberos-authentication SecurityImpact: Minimal, as Kerberos is already used via external, this just changes the main way it is named. Change-Id: If698fc1d0751cded556825b081539da4dd51275e --- keystone/auth/controllers.py | 15 ++++++++++++--- keystone/auth/plugins/external.py | 12 ++++++++++++ keystone/tests/test_v3.py | 14 ++++++++++---- keystone/tests/test_v3_auth.py | 23 +++++++++++++++++++---- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py index 37bc3388fc..f3c1b00a54 100644 --- a/keystone/auth/controllers.py +++ b/keystone/auth/controllers.py @@ -453,10 +453,19 @@ class Auth(controller.V3Controller): def authenticate(self, context, auth_info, auth_context): """Authenticate user.""" - # user has been authenticated externally + # The 'external' method allows any 'REMOTE_USER' based authentication if 'REMOTE_USER' in context['environment']: - external = get_auth_method('external') - external.authenticate(context, auth_info, auth_context) + try: + external = get_auth_method('external') + external.authenticate(context, auth_info, auth_context) + except exception.AuthMethodNotSupported: + # This will happen there is no 'external' plugin registered + # and the container is performing authentication. + # The 'kerberos' and 'saml' methods will be used this way. + # In those cases, it is correct to not register an + # 'external' plugin; if there is both an 'external' and a + # 'kerberos' plugin, it would run the check on identity twice. + pass # need to aggregate the results in case two or more methods # are specified diff --git a/keystone/auth/plugins/external.py b/keystone/auth/plugins/external.py index c6d401403a..8c6b8f1973 100644 --- a/keystone/auth/plugins/external.py +++ b/keystone/auth/plugins/external.py @@ -96,6 +96,18 @@ class Domain(Base): return user_ref +@dependency.requires('assignment_api', 'identity_api') +class KerberosDomain(Domain): + """Allows `kerberos` as a method.""" + method = 'kerberos' + + def _authenticate(self, remote_user, context): + auth_type = context['environment'].get('AUTH_TYPE') + if auth_type != 'Negotiate': + raise exception.Unauthorized(_("auth_type is not Negotiate")) + return super(KerberosDomain, self)._authenticate(remote_user, context) + + class ExternalDefault(DefaultDomain): """Deprecated. Please use keystone.auth.external.DefaultDomain instead.""" diff --git a/keystone/tests/test_v3.py b/keystone/tests/test_v3.py index 79d2cde250..b03f8cb935 100644 --- a/keystone/tests/test_v3.py +++ b/keystone/tests/test_v3.py @@ -1115,7 +1115,7 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): def build_authentication_request(self, token=None, user_id=None, username=None, user_domain_id=None, user_domain_name=None, password=None, - **kwargs): + kerberos=False, **kwargs): """Build auth dictionary. It will create an auth dictionary based on all the arguments @@ -1123,6 +1123,9 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): """ auth_data = {} auth_data['identity'] = {'methods': []} + if kerberos: + auth_data['identity']['methods'].append('kerberos') + auth_data['identity']['kerberos'] = {} if token: auth_data['identity']['methods'].append('token') auth_data['identity']['token'] = self.build_token_auth(token) @@ -1135,12 +1138,15 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase): return {'auth': auth_data} def build_external_auth_request(self, remote_user, - remote_domain=None, auth_data=None): - context = {'environment': {'REMOTE_USER': remote_user}} + remote_domain=None, auth_data=None, + kerberos=False): + context = {'environment': {'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}} if remote_domain: context['environment']['REMOTE_DOMAIN'] = remote_domain if not auth_data: - auth_data = self.build_authentication_request()['auth'] + auth_data = self.build_authentication_request( + kerberos=kerberos)['auth'] no_context = None auth_info = auth.controllers.AuthInfo.create(no_context, auth_data) auth_context = {'extras': {}, 'method_names': []} diff --git a/keystone/tests/test_v3_auth.py b/keystone/tests/test_v3_auth.py index 2fb4d4f5a4..954aca6ec7 100644 --- a/keystone/tests/test_v3_auth.py +++ b/keystone/tests/test_v3_auth.py @@ -1522,6 +1522,7 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): def config_overrides(self): super(TestAuthExternalDomain, self).config_overrides() + self.kerberos = False self.config_fixture.config( group='auth', methods=['keystone.auth.plugins.external.Domain', @@ -1533,7 +1534,7 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): remote_user = self.user['name'] remote_domain = self.domain['name'] context, auth_info, auth_context = self.build_external_auth_request( - remote_user, remote_domain=remote_domain) + remote_user, remote_domain=remote_domain, kerberos=self.kerberos) api.authenticate(context, auth_info, auth_context) self.assertEqual(auth_context['user_id'], self.user['id']) @@ -1544,7 +1545,7 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): self.identity_api.update_user(self.user['id'], user) remote_user = user['name'] context, auth_info, auth_context = self.build_external_auth_request( - remote_user, remote_domain=remote_domain) + remote_user, remote_domain=remote_domain, kerberos=self.kerberos) api.authenticate(context, auth_info, auth_context) self.assertEqual(auth_context['user_id'], self.user['id']) @@ -1552,7 +1553,8 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): def test_project_id_scoped_with_remote_user(self): CONF.token.bind = ['kerberos'] auth_data = self.build_authentication_request( - project_id=self.project['id']) + project_id=self.project['id'], + kerberos=self.kerberos) remote_user = self.user['name'] remote_domain = self.domain['name'] self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, @@ -1564,7 +1566,7 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): def test_unscoped_bind_with_remote_user(self): CONF.token.bind = ['kerberos'] - auth_data = self.build_authentication_request() + auth_data = self.build_authentication_request(kerberos=self.kerberos) remote_user = self.user['name'] remote_domain = self.domain['name'] self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, @@ -1575,6 +1577,19 @@ class TestAuthExternalDomain(test_v3.RestfulTestCase): self.assertEqual(token['bind']['kerberos'], self.user['name']) +class TestAuthKerberos(TestAuthExternalDomain): + + def config_overrides(self): + super(TestAuthKerberos, self).config_overrides() + self.kerberos = True + + self.config_fixture.config( + group='auth', + methods=['keystone.auth.plugins.external.KerberosDomain', + 'keystone.auth.plugins.password.Password', + 'keystone.auth.plugins.token.Token']) + + class TestAuthJSON(test_v3.RestfulTestCase): content_type = 'json'