diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-03-08 18:13:13 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-03-08 18:13:13 +0000 |
commit | f85cf357e53525320dcdc584c01b4b0b8111fb3a (patch) | |
tree | 84c936dfeb79586f89e1814a5d97840520c1aa0a | |
parent | fe5e1eb22c1629ed3541fa4e775bd141818e2170 (diff) | |
parent | c682b07a4f7ce8d66dbee9976582edf0bc3ff2c6 (diff) | |
download | keystonemiddleware-f85cf357e53525320dcdc584c01b4b0b8111fb3a.tar.gz |
Merge "Delay denial when service token is invalid"
-rw-r--r-- | doc/source/middlewarearchitecture.rst | 16 | ||||
-rw-r--r-- | keystonemiddleware/auth_token/__init__.py | 26 | ||||
-rw-r--r-- | keystonemiddleware/tests/auth_token/test_auth_token_middleware.py | 49 |
3 files changed, 79 insertions, 12 deletions
diff --git a/doc/source/middlewarearchitecture.rst b/doc/source/middlewarearchitecture.rst index e93bc4b..e02aad4 100644 --- a/doc/source/middlewarearchitecture.rst +++ b/doc/source/middlewarearchitecture.rst @@ -426,6 +426,22 @@ is set to `Confirmed`. If the middleware is delegating the auth decision to the service, then the status is set to `Invalid` if the auth request was unsuccessful. +An ``X-Service-Token`` header may also be included with a request. If present, +and the value of ``X-Auth-Token`` or ``X-Storage-Token`` has not caused the +request to be denied, then the middleware will attempt to validate the value of +``X-Service-Token``. If valid, the authentication middleware extends the HTTP +request with the header ``X-Service-Identity-Status`` having value `Confirmed` +and also extends the request with additional headers representing the identity +authenticated and authorised by the token. + +If ``X-Service-Token`` is present and its value is invalid and the +``delay_auth_decision`` option is True then the value of +``X-Service-Identity-Status`` is set to `Invalid` and no further headers are +added. Otherwise if ``X-Service-Token`` is present and its value is invalid +then the middleware will respond to the HTTP request with HTTPUnauthorized, +regardless of the validity of the ``X-Auth-Token`` or ``X-Storage-Token`` +values. + Extended the request with additional User Information ----------------------------------------------------- diff --git a/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware/auth_token/__init__.py index 3a74739..d63f763 100644 --- a/keystonemiddleware/auth_token/__init__.py +++ b/keystonemiddleware/auth_token/__init__.py @@ -69,10 +69,15 @@ will be added. They take the same form as the standard headers but add '_SERVICE_'. These headers will not exist in the environment if no service token is present. -HTTP_X_IDENTITY_STATUS +HTTP_X_IDENTITY_STATUS, HTTP_X_SERVICE_IDENTITY_STATUS 'Confirmed' or 'Invalid' The underlying service will only see a value of 'Invalid' if the Middleware - is configured to run in 'delay_auth_decision' mode + is configured to run in 'delay_auth_decision' mode. As with all such + headers, HTTP_X_SERVICE_IDENTITY_STATUS will only exist in the + environment if a service token is presented. This is different than + HTTP_X_IDENTITY_STATUS which is always set even if no user token is + presented. This allows the underlying service to determine if a + denial should use 401 or 403. HTTP_X_DOMAIN_ID, HTTP_X_SERVICE_DOMAIN_ID Identity service managed unique identifier, string. Only present if @@ -606,11 +611,16 @@ class AuthProtocol(object): serv_headers = self._build_service_headers(serv_token_info) self._add_headers(env, serv_headers) except exc.InvalidToken: - # Delayed auth not currently supported for service tokens. - # (Can be implemented if a use case is found.) - self._LOG.info( - _LI('Invalid service token - rejecting request')) - return self._reject_request(env, start_response) + if self._delay_auth_decision: + self._LOG.info( + _LI('Invalid service token - deferring reject ' + 'downstream')) + self._add_headers(env, + {'X-Service-Identity-Status': 'Invalid'}) + else: + self._LOG.info( + _LI('Invalid service token - rejecting request')) + return self._reject_request(env, start_response) env['keystone.token_auth'] = _user_plugin.UserAuthPlugin( user_auth_ref, serv_auth_ref) @@ -635,6 +645,7 @@ class AuthProtocol(object): """ auth_headers = ['X-Service-Catalog', 'X-Identity-Status', + 'X-Service-Identity-Status', 'X-Roles', 'X-Service-Roles'] for key in six.iterkeys(_HEADER_TEMPLATE): @@ -832,6 +843,7 @@ class AuthProtocol(object): roles = ','.join(auth_ref.role_names) rval = { + 'X-Service-Identity-Status': 'Confirmed', 'X-Service-Roles': roles, } diff --git a/keystonemiddleware/tests/auth_token/test_auth_token_middleware.py b/keystonemiddleware/tests/auth_token/test_auth_token_middleware.py index ae087f9..6c09072 100644 --- a/keystonemiddleware/tests/auth_token/test_auth_token_middleware.py +++ b/keystonemiddleware/tests/auth_token/test_auth_token_middleware.py @@ -64,6 +64,7 @@ EXPECTED_V2_DEFAULT_ENV_RESPONSE = { } EXPECTED_V2_DEFAULT_SERVICE_ENV_RESPONSE = { + 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Confirmed', 'HTTP_X_SERVICE_PROJECT_ID': 'service_project_id1', 'HTTP_X_SERVICE_PROJECT_NAME': 'service_project_name1', 'HTTP_X_SERVICE_USER_ID': 'service_user_id1', @@ -197,7 +198,18 @@ class FakeApp(object): resp = webob.Response() - if env['HTTP_X_IDENTITY_STATUS'] == 'Invalid': + if (env.get('HTTP_X_IDENTITY_STATUS') == 'Invalid' + and env['HTTP_X_SERVICE_IDENTITY_STATUS'] == 'Invalid'): + # Simulate delayed auth forbidding access with arbitrary status + # code to differentiate checking this code path + resp.status = 419 + resp.body = FakeApp.FORBIDDEN + elif env.get('HTTP_X_SERVICE_IDENTITY_STATUS') == 'Invalid': + # Simulate delayed auth forbidding access with arbitrary status + # code to differentiate checking this code path + resp.status = 420 + resp.body = FakeApp.FORBIDDEN + elif env['HTTP_X_IDENTITY_STATUS'] == 'Invalid': # Simulate delayed auth forbidding access resp.status = 403 resp.body = FakeApp.FORBIDDEN @@ -2296,7 +2308,8 @@ class CommonCompositeAuthTests(object): req.headers['X-Foo'] = 'Bar' body = self.middleware(req.environ, self.start_fake_response) for key in six.iterkeys(self.service_token_expected_env): - self.assertFalse(req.headers.get(key)) + header_key = key[len('HTTP_'):].replace('_', '-') + self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, self.response_status) self.assertEqual([FakeApp.FORBIDDEN], body) @@ -2332,14 +2345,39 @@ class CommonCompositeAuthTests(object): def test_composite_auth_delay_invalid_service_token(self): self.middleware._delay_auth_decision = True + self.purge_service_token_expected_env() + expected_env = { + 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', + } + self.update_expected_env(expected_env) + req = webob.Request.blank('/') token = self.token_dict['uuid_token_default'] service_token = 'invalid-service-token' req.headers['X-Auth-Token'] = token req.headers['X-Service-Token'] = service_token body = self.middleware(req.environ, self.start_fake_response) - self.assertEqual(401, self.response_status) - self.assertEqual([b'Authentication required'], body) + self.assertEqual(420, self.response_status) + self.assertEqual([FakeApp.FORBIDDEN], body) + + def test_composite_auth_delay_invalid_service_and_user_tokens(self): + self.middleware._delay_auth_decision = True + self.purge_service_token_expected_env() + self.purge_token_expected_env() + expected_env = { + 'HTTP_X_IDENTITY_STATUS': 'Invalid', + 'HTTP_X_SERVICE_IDENTITY_STATUS': 'Invalid', + } + self.update_expected_env(expected_env) + + req = webob.Request.blank('/') + token = 'invalid-user-token' + service_token = 'invalid-service-token' + req.headers['X-Auth-Token'] = token + req.headers['X-Service-Token'] = service_token + body = self.middleware(req.environ, self.start_fake_response) + self.assertEqual(419, self.response_status) + self.assertEqual([FakeApp.FORBIDDEN], body) def test_composite_auth_delay_no_service_token(self): self.middleware._delay_auth_decision = True @@ -2357,7 +2395,8 @@ class CommonCompositeAuthTests(object): req.headers['X-Foo'] = 'Bar' body = self.middleware(req.environ, self.start_fake_response) for key in six.iterkeys(self.service_token_expected_env): - self.assertFalse(req.headers.get(key)) + header_key = key[len('HTTP_'):].replace('_', '-') + self.assertFalse(req.headers.get(header_key)) self.assertEqual('Bar', req.headers.get('X-Foo')) self.assertEqual(418, self.response_status) self.assertEqual([FakeApp.FORBIDDEN], body) |