Browse Source

Merge "Add validation of app cred access rules"

tags/7.0.0^0
Zuul 2 months ago
parent
commit
3183b3d2fc

+ 73
- 1
keystonemiddleware/auth_token/__init__.py View File

@@ -218,6 +218,7 @@ object is stored.
218 218
 """
219 219
 
220 220
 import copy
221
+import re
221 222
 
222 223
 from keystoneauth1 import access
223 224
 from keystoneauth1 import adapter
@@ -277,6 +278,26 @@ def list_opts():
277 278
     return [(g, copy.deepcopy(o)) for g, o in AUTH_TOKEN_OPTS]
278 279
 
279 280
 
281
+def _path_matches(request_path, path_pattern):
282
+    # The fnmatch module doesn't provide the ability to match * versus **,
283
+    # so convert to regex.
284
+    token_regex = (r'(?P<tag>{[^}]*})|'  # {tag} # nosec
285
+                   '(?P<wild>\*(?=$|[^\*]))|'  # *
286
+                   '(?P<rec_wild>\*\*)|'  # **
287
+                   '(?P<literal>[^{}\*])')  # anything else
288
+    path_regex = ''
289
+    for match in re.finditer(token_regex, path_pattern):
290
+        token = match.groupdict()
291
+        if token['tag'] or token['wild']:
292
+            path_regex += '[^\/]+'
293
+        if token['rec_wild']:
294
+            path_regex += '.*'
295
+        if token['literal']:
296
+            path_regex += token['literal']
297
+    path_regex = r'^%s$' % path_regex
298
+    return re.match(path_regex, request_path)
299
+
300
+
280 301
 class _BIND_MODE(object):
281 302
     DISABLED = 'disabled'
282 303
     PERMISSIVE = 'permissive'
@@ -301,13 +322,15 @@ class BaseAuthProtocol(object):
301 322
                  log=_LOG,
302 323
                  enforce_token_bind=_BIND_MODE.PERMISSIVE,
303 324
                  service_token_roles=None,
304
-                 service_token_roles_required=False):
325
+                 service_token_roles_required=False,
326
+                 service_type=None):
305 327
         self.log = log
306 328
         self._app = app
307 329
         self._enforce_token_bind = enforce_token_bind
308 330
         self._service_token_roles = set(service_token_roles or [])
309 331
         self._service_token_roles_required = service_token_roles_required
310 332
         self._service_token_warning_emitted = False
333
+        self._service_type = service_type
311 334
 
312 335
     @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)
313 336
     def __call__(self, req):
@@ -388,6 +411,8 @@ class BaseAuthProtocol(object):
388 411
                     allow_expired=allow_expired)
389 412
                 self._validate_token(user_auth_ref,
390 413
                                      allow_expired=allow_expired)
414
+                if user_auth_ref.version != 'v2.0':
415
+                    self.validate_allowed_request(request, data['token'])
391 416
                 if not request.service_token:
392 417
                     self._confirm_token_bind(user_auth_ref, request)
393 418
             except ksm_exceptions.InvalidToken:
@@ -516,6 +541,53 @@ class BaseAuthProtocol(object):
516 541
                     {'bind_type': bind_type, 'identifier': identifier})
517 542
                 self._invalid_user_token()
518 543
 
544
+    def validate_allowed_request(self, request, token):
545
+        self.log.debug("Validating token access rules against request")
546
+        app_cred = token.get('application_credential')
547
+        if not app_cred:
548
+            return
549
+        access_rules = app_cred.get('access_rules')
550
+        if access_rules is None:
551
+            return
552
+        if hasattr(self, '_conf'):
553
+            my_service_type = self._conf.get('service_type')
554
+        else:
555
+            my_service_type = self._service_type
556
+        if not my_service_type:
557
+            self.log.warning('Cannot validate request with restricted'
558
+                             ' access rules. Set service_type in'
559
+                             ' [keystone_authtoken] to allow access rule'
560
+                             ' validation.')
561
+            raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
562
+        # token can always be validated regardless of access rules
563
+        if (my_service_type == 'identity' and
564
+                request.method == 'GET' and
565
+                request.path.endswith('/v3/auth/tokens')):
566
+            return
567
+        catalog = token['catalog']
568
+        # validate service type is in catalog
569
+        catalog_svcs = [s for s in catalog if s['type'] == my_service_type]
570
+        if len(catalog_svcs) == 0:
571
+            self.log.warning('Cannot validate request with restricted'
572
+                             ' access rules. service_type in'
573
+                             ' [keystone_authtoken] is not a valid service'
574
+                             ' type in the catalog.')
575
+            raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
576
+        if request.service_token:
577
+            # The request may not match an allowed request, but the presence
578
+            # of the service token indicates this is a chain of requests and
579
+            # hence this request was not user-facing
580
+            return
581
+        for access_rule in access_rules:
582
+            method = access_rule['method']
583
+            path = access_rule['path']
584
+            service = access_rule['service']
585
+            if request.method == method and \
586
+                    service == my_service_type and \
587
+                    _path_matches(request.path, path):
588
+                return
589
+        raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
590
+
519 591
 
520 592
 class AuthProtocol(BaseAuthProtocol):
521 593
     """Middleware that handles authenticating client calls."""

+ 4
- 1
keystonemiddleware/auth_token/_identity.py View File

@@ -21,6 +21,8 @@ from keystonemiddleware.auth_token import _auth
21 21
 from keystonemiddleware.auth_token import _exceptions as ksm_exceptions
22 22
 from keystonemiddleware.i18n import _
23 23
 
24
+ACCESS_RULES_SUPPORT = '1'
25
+
24 26
 
25 27
 class _RequestStrategy(object):
26 28
 
@@ -69,7 +71,8 @@ class _V3RequestStrategy(_RequestStrategy):
69 71
         auth_ref = self._client.tokens.validate(
70 72
             token,
71 73
             include_catalog=self._include_service_catalog,
72
-            allow_expired=allow_expired)
74
+            allow_expired=allow_expired,
75
+            access_rules_support=ACCESS_RULES_SUPPORT)
73 76
 
74 77
         if not auth_ref:
75 78
             msg = _('Failed to fetch token data from identity server')

+ 4
- 0
keystonemiddleware/auth_token/_opts.py View File

@@ -178,6 +178,10 @@ _OPTS = [
178 178
                 ' service tokens pass that don\'t pass the service_token_roles'
179 179
                 ' check as valid. Setting this true will become the default'
180 180
                 ' in a future release and should be enabled if possible.'),
181
+    cfg.StrOpt('service_type',
182
+               help='The name or type of the service as it appears in the'
183
+               ' service catalog. This is used to validate tokens that have'
184
+               ' restricted access rules.'),
181 185
 ]
182 186
 
183 187
 

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

@@ -1413,6 +1413,110 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1413 1413
         e = self.requests_mock.request_history[3].qs.get('allow_expired')
1414 1414
         self.assertIsNone(e)
1415 1415
 
1416
+    def test_app_cred_token_without_access_rules(self):
1417
+        self.set_middleware(conf={'service_type': 'compute'})
1418
+        token = self.examples.v3_APP_CRED_TOKEN
1419
+        token_data = self.examples.TOKEN_RESPONSES[token]
1420
+        resp = self.call_middleware(headers={'X-Auth-Token': token})
1421
+        self.assertEqual(FakeApp.SUCCESS, resp.body)
1422
+        token_auth = resp.request.environ['keystone.token_auth']
1423
+        self.assertEqual(token_data.application_credential_id,
1424
+                         token_auth.user.application_credential_id)
1425
+
1426
+    def test_app_cred_access_rules_token(self):
1427
+        self.set_middleware(conf={'service_type': 'compute'})
1428
+        token = self.examples.v3_APP_CRED_ACCESS_RULES
1429
+        token_data = self.examples.TOKEN_RESPONSES[token]
1430
+        resp = self.call_middleware(headers={'X-Auth-Token': token},
1431
+                                    expected_status=200,
1432
+                                    method='GET', path='/v2.1/servers')
1433
+        token_auth = resp.request.environ['keystone.token_auth']
1434
+        self.assertEqual(token_data.application_credential_id,
1435
+                         token_auth.user.application_credential_id)
1436
+        self.assertEqual(token_data.application_credential_access_rules,
1437
+                         token_auth.user.application_credential_access_rules)
1438
+        resp = self.call_middleware(headers={'X-Auth-Token': token},
1439
+                                    expected_status=401,
1440
+                                    method='GET',
1441
+                                    path='/v2.1/servers/someuuid')
1442
+        token_auth = resp.request.environ['keystone.token_auth']
1443
+        self.assertEqual(token_data.application_credential_id,
1444
+                         token_auth.user.application_credential_id)
1445
+        self.assertEqual(token_data.application_credential_access_rules,
1446
+                         token_auth.user.application_credential_access_rules)
1447
+
1448
+    def test_app_cred_access_rules_service_request(self):
1449
+        self.set_middleware(conf={'service_type': 'image'})
1450
+        token = self.examples.v3_APP_CRED_ACCESS_RULES
1451
+        headers = {'X-Auth-Token': token}
1452
+        self.call_middleware(headers=headers,
1453
+                             expected_status=401,
1454
+                             method='GET', path='/v2/images')
1455
+        service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT
1456
+        headers['X-Service-Token'] = service_token
1457
+        self.call_middleware(headers=headers,
1458
+                             expected_status=200,
1459
+                             method='GET', path='/v2/images')
1460
+
1461
+    def test_app_cred_no_access_rules_token(self):
1462
+        self.set_middleware(conf={'service_type': 'compute'})
1463
+        token = self.examples.v3_APP_CRED_EMPTY_ACCESS_RULES
1464
+        self.call_middleware(headers={'X-Auth-Token': token},
1465
+                             expected_status=401,
1466
+                             method='GET', path='/v2.1/servers')
1467
+        service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT
1468
+        headers = {
1469
+            'X-Auth-Token': token,
1470
+            'X-Service-Token': service_token
1471
+        }
1472
+        self.call_middleware(headers=headers, expected_status=401,
1473
+                             method='GET', path='/v2.1/servers')
1474
+
1475
+    def test_app_cred_matching_rules(self):
1476
+        self.set_middleware(conf={'service_type': 'compute'})
1477
+        token = self.examples.v3_APP_CRED_MATCHING_RULES
1478
+        self.call_middleware(headers={'X-Auth-Token': token},
1479
+                             expected_status=200,
1480
+                             method='GET', path='/v2.1/servers/foobar')
1481
+        self.call_middleware(headers={'X-Auth-Token': token},
1482
+                             expected_status=401,
1483
+                             method='GET', path='/v2.1/servers/foobar/barfoo')
1484
+        self.set_middleware(conf={'service_type': 'image'})
1485
+        self.call_middleware(headers={'X-Auth-Token': token},
1486
+                             expected_status=200,
1487
+                             method='GET', path='/v2/images/foobar')
1488
+        self.call_middleware(headers={'X-Auth-Token': token},
1489
+                             expected_status=401,
1490
+                             method='GET', path='/v2/images/foobar/barfoo')
1491
+        self.set_middleware(conf={'service_type': 'identity'})
1492
+        self.call_middleware(headers={'X-Auth-Token': token},
1493
+                             expected_status=200,
1494
+                             method='GET',
1495
+                             path='/v3/projects/123/users/456/roles/member')
1496
+        self.set_middleware(conf={'service_type': 'block-storage'})
1497
+        self.call_middleware(headers={'X-Auth-Token': token},
1498
+                             expected_status=200,
1499
+                             method='GET', path='/v3/123/types/456')
1500
+        self.call_middleware(headers={'X-Auth-Token': token},
1501
+                             expected_status=401,
1502
+                             method='GET', path='/v3/123/types')
1503
+        self.call_middleware(headers={'X-Auth-Token': token},
1504
+                             expected_status=401,
1505
+                             method='GET', path='/v2/123/types/456')
1506
+        self.set_middleware(conf={'service_type': 'object-store'})
1507
+        self.call_middleware(headers={'X-Auth-Token': token},
1508
+                             expected_status=200,
1509
+                             method='GET', path='/v1/1/2/3')
1510
+        self.call_middleware(headers={'X-Auth-Token': token},
1511
+                             expected_status=401,
1512
+                             method='GET', path='/v1/1/2')
1513
+        self.call_middleware(headers={'X-Auth-Token': token},
1514
+                             expected_status=401,
1515
+                             method='GET', path='/v2/1/2')
1516
+        self.call_middleware(headers={'X-Auth-Token': token},
1517
+                             expected_status=401,
1518
+                             method='GET', path='/info')
1519
+
1416 1520
 
1417 1521
 class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
1418 1522
 

+ 124
- 0
keystonemiddleware/tests/unit/client_fixtures.py View File

@@ -64,6 +64,11 @@ class Examples(fixtures.Fixture):
64 64
         self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
65 65
         self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex
66 66
 
67
+        self.v3_APP_CRED_TOKEN = '6f506fa9641448bbaecbd12dd30678a9'
68
+        self.v3_APP_CRED_ACCESS_RULES = 'c417747898c44629b08791f2579e40a5'
69
+        self.v3_APP_CRED_EMPTY_ACCESS_RULES = 'c75905c307f04fdd9979126582d7aae'
70
+        self.v3_APP_CRED_MATCHING_RULES = 'ad49decc7106489d95ca9ed874b6cb66'
71
+
67 72
         # JSON responses keyed by token ID
68 73
         self.TOKEN_RESPONSES = {}
69 74
 
@@ -86,6 +91,8 @@ class Examples(fixtures.Fixture):
86 91
         SERVICE_ROLE_NAME1 = 'service'
87 92
         SERVICE_ROLE_NAME2 = 'service_role2'
88 93
 
94
+        APP_CRED_ID = 'app_cred_id1'
95
+
89 96
         self.SERVICE_TYPE = 'identity'
90 97
         self.UNVERSIONED_SERVICE_URL = 'https://keystone.example.com:1234/'
91 98
         self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + 'v2.0'
@@ -293,6 +300,123 @@ class Examples(fixtures.Fixture):
293 300
         svc.add_endpoint('public', self.SERVICE_URL)
294 301
         self.TOKEN_RESPONSES[self.v3_NOT_IS_ADMIN_PROJECT] = token
295 302
 
303
+        # Application credential token
304
+        token = fixture.V3Token(user_id=USER_ID,
305
+                                user_name=USER_NAME,
306
+                                user_domain_id=DOMAIN_ID,
307
+                                user_domain_name=DOMAIN_NAME,
308
+                                project_id=PROJECT_ID,
309
+                                project_name=PROJECT_NAME,
310
+                                project_domain_id=DOMAIN_ID,
311
+                                project_domain_name=DOMAIN_NAME,
312
+                                application_credential_id=APP_CRED_ID)
313
+        token.add_role(name=ROLE_NAME1)
314
+        token.add_role(name=ROLE_NAME2)
315
+        svc = token.add_service(self.SERVICE_TYPE)
316
+        svc.add_endpoint('public', self.SERVICE_URL)
317
+        svc = token.add_service('compute')
318
+        svc.add_endpoint('public', 'https://nova.openstack.example.org/v2.1')
319
+        self.TOKEN_RESPONSES[self.v3_APP_CRED_TOKEN] = token
320
+
321
+        # Application credential with access_rules token
322
+        access_rules = [{
323
+            'path': '/v2.1/servers',
324
+            'method': 'GET',
325
+            'service': 'compute'
326
+        }]
327
+        token = fixture.V3Token(
328
+            user_id=USER_ID,
329
+            user_name=USER_NAME,
330
+            user_domain_id=DOMAIN_ID,
331
+            user_domain_name=DOMAIN_NAME,
332
+            project_id=PROJECT_ID,
333
+            project_name=PROJECT_NAME,
334
+            project_domain_id=DOMAIN_ID,
335
+            project_domain_name=DOMAIN_NAME,
336
+            application_credential_id=APP_CRED_ID,
337
+            application_credential_access_rules=access_rules)
338
+        token.add_role(name=ROLE_NAME1)
339
+        token.add_role(name=ROLE_NAME2)
340
+        svc = token.add_service(self.SERVICE_TYPE)
341
+        svc.add_endpoint('public', self.SERVICE_URL)
342
+        svc = token.add_service('compute')
343
+        svc.add_endpoint('public', 'https://nova.openstack.example.org')
344
+        svc = token.add_service('image')
345
+        svc.add_endpoint('public', 'https://glance.openstack.example.org')
346
+        self.TOKEN_RESPONSES[self.v3_APP_CRED_ACCESS_RULES] = token
347
+
348
+        # Application credential with explicitly empty access_rules
349
+        access_rules = []
350
+        token = fixture.V3Token(
351
+            user_id=USER_ID,
352
+            user_name=USER_NAME,
353
+            user_domain_id=DOMAIN_ID,
354
+            user_domain_name=DOMAIN_NAME,
355
+            project_id=PROJECT_ID,
356
+            project_name=PROJECT_NAME,
357
+            project_domain_id=DOMAIN_ID,
358
+            project_domain_name=DOMAIN_NAME,
359
+            application_credential_id=APP_CRED_ID,
360
+            application_credential_access_rules=access_rules)
361
+        token.add_role(name=ROLE_NAME1)
362
+        token.add_role(name=ROLE_NAME2)
363
+        svc = token.add_service(self.SERVICE_TYPE)
364
+        svc.add_endpoint('public', self.SERVICE_URL)
365
+        self.TOKEN_RESPONSES[self.v3_APP_CRED_EMPTY_ACCESS_RULES] = token
366
+
367
+        # Application credential with matching rules
368
+        access_rules = [
369
+            {
370
+                'path': '/v2.1/servers/{server_id}',
371
+                'method': 'GET',
372
+                'service': 'compute'
373
+            },
374
+            {
375
+                'path': '/v2/images/*',
376
+                'method': 'GET',
377
+                'service': 'image'
378
+            },
379
+            {
380
+                'path': '**',
381
+                'method': 'GET',
382
+                'service': 'identity'
383
+            },
384
+            {
385
+                'path': '/v3/{project_id}/types/{volume_type_id}',
386
+                'method': 'GET',
387
+                'service': 'block-storage'
388
+            },
389
+            {
390
+                'path': '/v1/*/*/*',
391
+                'method': 'GET',
392
+                'service': 'object-store'
393
+            }
394
+        ]
395
+        token = fixture.V3Token(
396
+            user_id=USER_ID,
397
+            user_name=USER_NAME,
398
+            user_domain_id=DOMAIN_ID,
399
+            user_domain_name=DOMAIN_NAME,
400
+            project_id=PROJECT_ID,
401
+            project_name=PROJECT_NAME,
402
+            project_domain_id=DOMAIN_ID,
403
+            project_domain_name=DOMAIN_NAME,
404
+            application_credential_id=APP_CRED_ID,
405
+            application_credential_access_rules=access_rules)
406
+        token.add_role(name=ROLE_NAME1)
407
+        token.add_role(name=ROLE_NAME2)
408
+        svc = token.add_service(self.SERVICE_TYPE)
409
+        svc.add_endpoint('public', self.SERVICE_URL)
410
+        svc = token.add_service('compute')
411
+        svc.add_endpoint('public', 'https://nova.openstack.example.org')
412
+        svc = token.add_service('image')
413
+        svc.add_endpoint('public', 'https://glance.openstack.example.org')
414
+        svc = token.add_service('block-storage')
415
+        svc.add_endpoint('public', 'https://cinder.openstack.example.org')
416
+        svc = token.add_service('object-store')
417
+        svc.add_endpoint('public', 'https://swift.openstack.example.org')
418
+        self.TOKEN_RESPONSES[self.v3_APP_CRED_MATCHING_RULES] = token
419
+
296 420
         self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in
297 421
                                           self.TOKEN_RESPONSES.items()])
298 422
 

+ 54
- 0
keystonemiddleware/tests/unit/test_access_rules.py View File

@@ -0,0 +1,54 @@
1
+# Copyright 2019 SUSE LLC
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from keystonemiddleware.auth_token import _path_matches
16
+from keystonemiddleware.tests.unit import utils
17
+
18
+
19
+class TestAccessRules(utils.BaseTestCase):
20
+
21
+    def test_path_matches(self):
22
+        good_matches = [
23
+            ('/v2/servers', '/v2/servers'),
24
+            ('/v2/servers/123', '/v2/servers/{server_id}'),
25
+            ('/v2/servers/123/', '/v2/servers/{server_id}/'),
26
+            ('/v2/servers/123', '/v2/servers/*'),
27
+            ('/v2/servers/123/', '/v2/servers/*/'),
28
+            ('/v2/servers/123', '/v2/servers/**'),
29
+            ('/v2/servers/123/', '/v2/servers/**'),
30
+            ('/v2/servers/123/456', '/v2/servers/**'),
31
+            ('/v2/servers', '**'),
32
+            ('/v2/servers/', '**'),
33
+            ('/v2/servers/123', '**'),
34
+            ('/v2/servers/123/456', '**'),
35
+            ('/v2/servers/123/volume/456', '**'),
36
+            ('/v2/servers/123/456', '/v2/*/*/*'),
37
+            ('/v2/123/servers/466', '/v2/{project_id}/servers/{server_id}'),
38
+        ]
39
+        for (request, pattern) in good_matches:
40
+            self.assertIsNotNone(_path_matches(request, pattern))
41
+        bad_matches = [
42
+            ('/v2/servers/someuuid', '/v2/servers'),
43
+            ('/v2/servers//', '/v2/servers/{server_id}'),
44
+            ('/v2/servers/123/', '/v2/servers/{server_id}'),
45
+            ('/v2/servers/123/456', '/v2/servers/{server_id}'),
46
+            ('/v2/servers/123/456', '/v2/servers/*'),
47
+            ('/v2/servers', 'v2/servers'),
48
+            ('/v2/servers/123/456/789', '/v2/*/*/*'),
49
+            ('/v2/servers/123/', '/v2/*/*/*'),
50
+            ('/v2/servers/', '/v2/servers/{server_id}'),
51
+            ('/v2/servers', '/v2/servers/{server_id}'),
52
+        ]
53
+        for (request, pattern) in bad_matches:
54
+            self.assertIsNone(_path_matches(request, pattern))

+ 2
- 0
keystonemiddleware/tests/unit/test_opts.py View File

@@ -69,6 +69,7 @@ class OptsTestCase(utils.TestCase):
69 69
             'auth_section',
70 70
             'service_token_roles',
71 71
             'service_token_roles_required',
72
+            'service_type',
72 73
         ]
73 74
         opt_names = [o.name for (g, l) in result_of_old_opts for o in l]
74 75
         self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names)))
@@ -113,6 +114,7 @@ class OptsTestCase(utils.TestCase):
113 114
             'auth_section',
114 115
             'service_token_roles',
115 116
             'service_token_roles_required',
117
+            'service_type',
116 118
         ]
117 119
         opt_names = [o.name for (g, l) in result for o in l]
118 120
         self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names)))

+ 2
- 2
lower-constraints.txt View File

@@ -23,7 +23,7 @@ GitPython==2.1.8
23 23
 hacking==0.10.0
24 24
 idna==2.6
25 25
 iso8601==0.1.12
26
-keystoneauth1==3.4.0
26
+keystoneauth1==3.12.0
27 27
 linecache2==1.0.0
28 28
 mccabe==0.2.1
29 29
 mock==2.0.0
@@ -57,7 +57,7 @@ pyinotify==0.9.6
57 57
 pyparsing==2.2.0
58 58
 pyperclip==1.6.0
59 59
 python-dateutil==2.7.0
60
-python-keystoneclient==3.10.0
60
+python-keystoneclient==3.20.0
61 61
 python-memcached==1.59
62 62
 python-mimeparse==1.6.0
63 63
 python-subunit==1.2.0

+ 7
- 0
releasenotes/notes/bp-whitelist-extension-for-app-creds-badf088c8ad584bb.yaml View File

@@ -0,0 +1,7 @@
1
+---
2
+features:
3
+  - |
4
+    [`spec <http://specs.openstack.org/openstack/keystone-specs/specs/keystone/train/capabilities-app-creds.html>`_]
5
+    The auth_token middleware now has support for accepting or denying incoming
6
+    requests based on access rules provided by users in their keystone
7
+    application credentials.

+ 2
- 2
requirements.txt View File

@@ -2,7 +2,7 @@
2 2
 # of appearance. Changing the order has an impact on the overall integration
3 3
 # process, which may cause wedges in the gate later.
4 4
 
5
-keystoneauth1>=3.4.0 # Apache-2.0
5
+keystoneauth1>=3.12.0 # Apache-2.0
6 6
 oslo.cache>=1.26.0 # Apache-2.0
7 7
 oslo.config>=5.2.0 # Apache-2.0
8 8
 oslo.context>=2.19.2 # Apache-2.0
@@ -12,7 +12,7 @@ oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
12 12
 oslo.utils>=3.33.0 # Apache-2.0
13 13
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
14 14
 pycadf!=2.0.0,>=1.1.0 # Apache-2.0
15
-python-keystoneclient>=3.10.0 # Apache-2.0
15
+python-keystoneclient>=3.20.0 # Apache-2.0
16 16
 requests>=2.14.2 # Apache-2.0
17 17
 six>=1.10.0 # MIT
18 18
 WebOb>=1.7.1 # MIT

Loading…
Cancel
Save