Merge "Delay denial when service token is invalid"

This commit is contained in:
Jenkins 2015-03-08 18:13:13 +00:00 committed by Gerrit Code Review
commit f85cf357e5
3 changed files with 79 additions and 12 deletions

View File

@ -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
-----------------------------------------------------

View File

@ -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,
}

View File

@ -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)