Merge "Delay denial when service token is invalid"
This commit is contained in:
commit
f85cf357e5
|
@ -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
|
||||
-----------------------------------------------------
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue