Browse Source

Respect delay_auth_decision when Keystone is unavailable

The delay_auth_decision option has two main uses:

  1. Allow a service to provide its own auth mechanism, separate from
     auth tokens (like Swift's tempurl middleware).
  2. Allow a service to integrate with multiple auth middlewares which
     may want to use the same X-Auth-Token header.

The first case works fine even when the service has trouble talking to
Keystone -- the client doesn't send an X-Auth-Token header, so we never
even attempt to contact Keystone.

The second case can be problematic, however. The client will provide
some token, and we don't know whether it's valid for Keystone, the other
auth system, or neither. We have to *try* contacting Keystone, but if
that was down we'd previously return a 503 without ever trying the other
auth system. As a result, a Keystone failure results in a total system
failure.

Now, when delay_auth_decision is True and we cannot determine whether a
token is valid or invalid, we'll instead declare the token invalid and
defer the rejection. As a result, Keystone failures only affect Keystone
users, and tokens issued by the other auth system may still be validated
and used.

Change-Id: Ie4b3319862ba7fbd329dc6883ce837e894d5270c
Tim Burke 7 months ago
parent
commit
da5932affc

+ 5
- 0
keystonemiddleware/auth_token/__init__.py View File

@@ -768,6 +768,11 @@ class AuthProtocol(BaseAuthProtocol):
768 768
                 ksm_exceptions.RevocationListError,
769 769
                 ksm_exceptions.ServiceError) as e:
770 770
             self.log.critical('Unable to validate token: %s', e)
771
+            if self._delay_auth_decision:
772
+                self.log.debug('Keystone unavailable; marking token as '
773
+                               'invalid and deferring auth decision.')
774
+                raise ksm_exceptions.InvalidToken(
775
+                    'Keystone unavailable: %s' % e)
771 776
             raise webob.exc.HTTPServiceUnavailable(
772 777
                 'The Keystone service is temporarily unavailable.')
773 778
         except ksm_exceptions.InvalidToken:

+ 66
- 0
keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py View File

@@ -2043,6 +2043,18 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
2043 2043
 
2044 2044
 class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
2045 2045
 
2046
+    def token_response(self, request, context):
2047
+        auth_id = request.headers.get('X-Auth-Token')
2048
+        self.assertEqual(auth_id, FAKE_ADMIN_TOKEN_ID)
2049
+
2050
+        if request.headers.get('X-Subject-Token') == ERROR_TOKEN:
2051
+            msg = 'Network connection refused.'
2052
+            raise ksc_exceptions.ConnectionRefused(msg)
2053
+
2054
+        # All others just fail
2055
+        context.status_code = 404
2056
+        return ''
2057
+
2046 2058
     def test_header_in_401(self):
2047 2059
         body = uuid.uuid4().hex
2048 2060
         www_authenticate_uri = 'http://local.test'
@@ -2101,6 +2113,60 @@ class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
2101 2113
         self.assertFalse(token_auth.has_service_token)
2102 2114
         self.assertIsNone(token_auth.service)
2103 2115
 
2116
+    def test_auth_plugin_with_token(self):
2117
+        self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI,
2118
+                               text=self.token_response,
2119
+                               headers={'X-Subject-Token': uuid.uuid4().hex})
2120
+
2121
+        body = uuid.uuid4().hex
2122
+        www_authenticate_uri = 'http://local.test'
2123
+        conf = {
2124
+            'delay_auth_decision': 'True',
2125
+            'www_authenticate_uri': www_authenticate_uri,
2126
+            'auth_type': 'admin_token',
2127
+            'endpoint': '%s/v3' % BASE_URI,
2128
+            'token': FAKE_ADMIN_TOKEN_ID,
2129
+        }
2130
+
2131
+        middleware = self.create_simple_middleware(body=body, conf=conf)
2132
+        resp = self.call(middleware, headers={
2133
+            'X-Auth-Token': 'non-keystone-token'})
2134
+        self.assertEqual(six.b(body), resp.body)
2135
+
2136
+        token_auth = resp.request.environ['keystone.token_auth']
2137
+
2138
+        self.assertFalse(token_auth.has_user_token)
2139
+        self.assertIsNone(token_auth.user)
2140
+        self.assertFalse(token_auth.has_service_token)
2141
+        self.assertIsNone(token_auth.service)
2142
+
2143
+    def test_auth_plugin_with_token_keystone_down(self):
2144
+        self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI,
2145
+                               text=self.token_response,
2146
+                               headers={'X-Subject-Token': ERROR_TOKEN})
2147
+
2148
+        body = uuid.uuid4().hex
2149
+        www_authenticate_uri = 'http://local.test'
2150
+        conf = {
2151
+            'delay_auth_decision': 'True',
2152
+            'www_authenticate_uri': www_authenticate_uri,
2153
+            'auth_type': 'admin_token',
2154
+            'endpoint': '%s/v3' % BASE_URI,
2155
+            'token': FAKE_ADMIN_TOKEN_ID,
2156
+            'http_request_max_retries': '0'
2157
+        }
2158
+
2159
+        middleware = self.create_simple_middleware(body=body, conf=conf)
2160
+        resp = self.call(middleware, headers={'X-Auth-Token': ERROR_TOKEN})
2161
+        self.assertEqual(six.b(body), resp.body)
2162
+
2163
+        token_auth = resp.request.environ['keystone.token_auth']
2164
+
2165
+        self.assertFalse(token_auth.has_user_token)
2166
+        self.assertIsNone(token_auth.user)
2167
+        self.assertFalse(token_auth.has_service_token)
2168
+        self.assertIsNone(token_auth.service)
2169
+
2104 2170
 
2105 2171
 class CommonCompositeAuthTests(object):
2106 2172
     """Test Composite authentication.

+ 9
- 0
releasenotes/notes/delay_auth_instead_of_503-f9b46bf4fbc11455.yaml View File

@@ -0,0 +1,9 @@
1
+---
2
+fixes:
3
+  - |
4
+    When ``delay_auth_decision`` is enabled and a Keystone failure prevents
5
+    a final decision about whether a token is valid or invalid, it will be
6
+    marked invalid and the application will be responsible for a final auth
7
+    decision. This is similar to what happens when a token is confirmed *not*
8
+    valid. This allows a Keystone outage to only affect Keystone users in a
9
+    multi-auth system.

Loading…
Cancel
Save