summaryrefslogtreecommitdiff
path: root/keystonemiddleware/tests
diff options
context:
space:
mode:
authorColleen Murphy <colleen@gazlene.net>2019-01-26 23:06:00 +0100
committerColleen Murphy <colleen.murphy@suse.de>2019-07-15 16:05:59 -0700
commit5f093bf5ee9f8ed201f01bab9c9afbde0423df07 (patch)
tree90e46bfea194cc999b03e367cb9eb2572dcf011d /keystonemiddleware/tests
parent2d3765ed565aba3b9793efcfc5c2d1ea534f1a0d (diff)
downloadkeystonemiddleware-5f093bf5ee9f8ed201f01bab9c9afbde0423df07.tar.gz
Add validation of app cred access rules
This commit adds a validation step in the auth_token middleware to check for the presence of an access_rules attribute in an application credential token and to validate the request against the permissions granted for that token. During token validation it sends a header to keystone to indicate that it is capable of validating these access rules, and not providing this header for a token like this would result in the token failing validation. This disregards access rules for a service request made by a service on behalf of a user, such as nova making a request to glance, because such a request is not under the control of the user and is not expected to be explicitly allowed in the access rules. bp whitelist-extension-for-app-creds Depends-On: https://review.opendev.org/670377 Change-Id: I185e0541d5df538d74edadf9976b3034a2470c88
Diffstat (limited to 'keystonemiddleware/tests')
-rw-r--r--keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py104
-rw-r--r--keystonemiddleware/tests/unit/client_fixtures.py124
-rw-r--r--keystonemiddleware/tests/unit/test_access_rules.py54
-rw-r--r--keystonemiddleware/tests/unit/test_opts.py2
4 files changed, 284 insertions, 0 deletions
diff --git a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
index 04e605c..9fd0328 100644
--- a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
+++ b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
@@ -1413,6 +1413,110 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
e = self.requests_mock.request_history[3].qs.get('allow_expired')
self.assertIsNone(e)
+ def test_app_cred_token_without_access_rules(self):
+ self.set_middleware(conf={'service_type': 'compute'})
+ token = self.examples.v3_APP_CRED_TOKEN
+ token_data = self.examples.TOKEN_RESPONSES[token]
+ resp = self.call_middleware(headers={'X-Auth-Token': token})
+ self.assertEqual(FakeApp.SUCCESS, resp.body)
+ token_auth = resp.request.environ['keystone.token_auth']
+ self.assertEqual(token_data.application_credential_id,
+ token_auth.user.application_credential_id)
+
+ def test_app_cred_access_rules_token(self):
+ self.set_middleware(conf={'service_type': 'compute'})
+ token = self.examples.v3_APP_CRED_ACCESS_RULES
+ token_data = self.examples.TOKEN_RESPONSES[token]
+ resp = self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET', path='/v2.1/servers')
+ token_auth = resp.request.environ['keystone.token_auth']
+ self.assertEqual(token_data.application_credential_id,
+ token_auth.user.application_credential_id)
+ self.assertEqual(token_data.application_credential_access_rules,
+ token_auth.user.application_credential_access_rules)
+ resp = self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET',
+ path='/v2.1/servers/someuuid')
+ token_auth = resp.request.environ['keystone.token_auth']
+ self.assertEqual(token_data.application_credential_id,
+ token_auth.user.application_credential_id)
+ self.assertEqual(token_data.application_credential_access_rules,
+ token_auth.user.application_credential_access_rules)
+
+ def test_app_cred_access_rules_service_request(self):
+ self.set_middleware(conf={'service_type': 'image'})
+ token = self.examples.v3_APP_CRED_ACCESS_RULES
+ headers = {'X-Auth-Token': token}
+ self.call_middleware(headers=headers,
+ expected_status=401,
+ method='GET', path='/v2/images')
+ service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT
+ headers['X-Service-Token'] = service_token
+ self.call_middleware(headers=headers,
+ expected_status=200,
+ method='GET', path='/v2/images')
+
+ def test_app_cred_no_access_rules_token(self):
+ self.set_middleware(conf={'service_type': 'compute'})
+ token = self.examples.v3_APP_CRED_EMPTY_ACCESS_RULES
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v2.1/servers')
+ service_token = self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT
+ headers = {
+ 'X-Auth-Token': token,
+ 'X-Service-Token': service_token
+ }
+ self.call_middleware(headers=headers, expected_status=401,
+ method='GET', path='/v2.1/servers')
+
+ def test_app_cred_matching_rules(self):
+ self.set_middleware(conf={'service_type': 'compute'})
+ token = self.examples.v3_APP_CRED_MATCHING_RULES
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET', path='/v2.1/servers/foobar')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v2.1/servers/foobar/barfoo')
+ self.set_middleware(conf={'service_type': 'image'})
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET', path='/v2/images/foobar')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v2/images/foobar/barfoo')
+ self.set_middleware(conf={'service_type': 'identity'})
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET',
+ path='/v3/projects/123/users/456/roles/member')
+ self.set_middleware(conf={'service_type': 'block-storage'})
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET', path='/v3/123/types/456')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v3/123/types')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v2/123/types/456')
+ self.set_middleware(conf={'service_type': 'object-store'})
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=200,
+ method='GET', path='/v1/1/2/3')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v1/1/2')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/v2/1/2')
+ self.call_middleware(headers={'X-Auth-Token': token},
+ expected_status=401,
+ method='GET', path='/info')
+
class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
diff --git a/keystonemiddleware/tests/unit/client_fixtures.py b/keystonemiddleware/tests/unit/client_fixtures.py
index a807054..27ba482 100644
--- a/keystonemiddleware/tests/unit/client_fixtures.py
+++ b/keystonemiddleware/tests/unit/client_fixtures.py
@@ -64,6 +64,11 @@ class Examples(fixtures.Fixture):
self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex
+ self.v3_APP_CRED_TOKEN = '6f506fa9641448bbaecbd12dd30678a9'
+ self.v3_APP_CRED_ACCESS_RULES = 'c417747898c44629b08791f2579e40a5'
+ self.v3_APP_CRED_EMPTY_ACCESS_RULES = 'c75905c307f04fdd9979126582d7aae'
+ self.v3_APP_CRED_MATCHING_RULES = 'ad49decc7106489d95ca9ed874b6cb66'
+
# JSON responses keyed by token ID
self.TOKEN_RESPONSES = {}
@@ -86,6 +91,8 @@ class Examples(fixtures.Fixture):
SERVICE_ROLE_NAME1 = 'service'
SERVICE_ROLE_NAME2 = 'service_role2'
+ APP_CRED_ID = 'app_cred_id1'
+
self.SERVICE_TYPE = 'identity'
self.UNVERSIONED_SERVICE_URL = 'https://keystone.example.com:1234/'
self.SERVICE_URL = self.UNVERSIONED_SERVICE_URL + 'v2.0'
@@ -293,6 +300,123 @@ class Examples(fixtures.Fixture):
svc.add_endpoint('public', self.SERVICE_URL)
self.TOKEN_RESPONSES[self.v3_NOT_IS_ADMIN_PROJECT] = token
+ # Application credential token
+ token = fixture.V3Token(user_id=USER_ID,
+ user_name=USER_NAME,
+ user_domain_id=DOMAIN_ID,
+ user_domain_name=DOMAIN_NAME,
+ project_id=PROJECT_ID,
+ project_name=PROJECT_NAME,
+ project_domain_id=DOMAIN_ID,
+ project_domain_name=DOMAIN_NAME,
+ application_credential_id=APP_CRED_ID)
+ token.add_role(name=ROLE_NAME1)
+ token.add_role(name=ROLE_NAME2)
+ svc = token.add_service(self.SERVICE_TYPE)
+ svc.add_endpoint('public', self.SERVICE_URL)
+ svc = token.add_service('compute')
+ svc.add_endpoint('public', 'https://nova.openstack.example.org/v2.1')
+ self.TOKEN_RESPONSES[self.v3_APP_CRED_TOKEN] = token
+
+ # Application credential with access_rules token
+ access_rules = [{
+ 'path': '/v2.1/servers',
+ 'method': 'GET',
+ 'service': 'compute'
+ }]
+ token = fixture.V3Token(
+ user_id=USER_ID,
+ user_name=USER_NAME,
+ user_domain_id=DOMAIN_ID,
+ user_domain_name=DOMAIN_NAME,
+ project_id=PROJECT_ID,
+ project_name=PROJECT_NAME,
+ project_domain_id=DOMAIN_ID,
+ project_domain_name=DOMAIN_NAME,
+ application_credential_id=APP_CRED_ID,
+ application_credential_access_rules=access_rules)
+ token.add_role(name=ROLE_NAME1)
+ token.add_role(name=ROLE_NAME2)
+ svc = token.add_service(self.SERVICE_TYPE)
+ svc.add_endpoint('public', self.SERVICE_URL)
+ svc = token.add_service('compute')
+ svc.add_endpoint('public', 'https://nova.openstack.example.org')
+ svc = token.add_service('image')
+ svc.add_endpoint('public', 'https://glance.openstack.example.org')
+ self.TOKEN_RESPONSES[self.v3_APP_CRED_ACCESS_RULES] = token
+
+ # Application credential with explicitly empty access_rules
+ access_rules = []
+ token = fixture.V3Token(
+ user_id=USER_ID,
+ user_name=USER_NAME,
+ user_domain_id=DOMAIN_ID,
+ user_domain_name=DOMAIN_NAME,
+ project_id=PROJECT_ID,
+ project_name=PROJECT_NAME,
+ project_domain_id=DOMAIN_ID,
+ project_domain_name=DOMAIN_NAME,
+ application_credential_id=APP_CRED_ID,
+ application_credential_access_rules=access_rules)
+ token.add_role(name=ROLE_NAME1)
+ token.add_role(name=ROLE_NAME2)
+ svc = token.add_service(self.SERVICE_TYPE)
+ svc.add_endpoint('public', self.SERVICE_URL)
+ self.TOKEN_RESPONSES[self.v3_APP_CRED_EMPTY_ACCESS_RULES] = token
+
+ # Application credential with matching rules
+ access_rules = [
+ {
+ 'path': '/v2.1/servers/{server_id}',
+ 'method': 'GET',
+ 'service': 'compute'
+ },
+ {
+ 'path': '/v2/images/*',
+ 'method': 'GET',
+ 'service': 'image'
+ },
+ {
+ 'path': '**',
+ 'method': 'GET',
+ 'service': 'identity'
+ },
+ {
+ 'path': '/v3/{project_id}/types/{volume_type_id}',
+ 'method': 'GET',
+ 'service': 'block-storage'
+ },
+ {
+ 'path': '/v1/*/*/*',
+ 'method': 'GET',
+ 'service': 'object-store'
+ }
+ ]
+ token = fixture.V3Token(
+ user_id=USER_ID,
+ user_name=USER_NAME,
+ user_domain_id=DOMAIN_ID,
+ user_domain_name=DOMAIN_NAME,
+ project_id=PROJECT_ID,
+ project_name=PROJECT_NAME,
+ project_domain_id=DOMAIN_ID,
+ project_domain_name=DOMAIN_NAME,
+ application_credential_id=APP_CRED_ID,
+ application_credential_access_rules=access_rules)
+ token.add_role(name=ROLE_NAME1)
+ token.add_role(name=ROLE_NAME2)
+ svc = token.add_service(self.SERVICE_TYPE)
+ svc.add_endpoint('public', self.SERVICE_URL)
+ svc = token.add_service('compute')
+ svc.add_endpoint('public', 'https://nova.openstack.example.org')
+ svc = token.add_service('image')
+ svc.add_endpoint('public', 'https://glance.openstack.example.org')
+ svc = token.add_service('block-storage')
+ svc.add_endpoint('public', 'https://cinder.openstack.example.org')
+ svc = token.add_service('object-store')
+ svc.add_endpoint('public', 'https://swift.openstack.example.org')
+ self.TOKEN_RESPONSES[self.v3_APP_CRED_MATCHING_RULES] = token
+
self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in
self.TOKEN_RESPONSES.items()])
diff --git a/keystonemiddleware/tests/unit/test_access_rules.py b/keystonemiddleware/tests/unit/test_access_rules.py
new file mode 100644
index 0000000..663b806
--- /dev/null
+++ b/keystonemiddleware/tests/unit/test_access_rules.py
@@ -0,0 +1,54 @@
+# Copyright 2019 SUSE LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from keystonemiddleware.auth_token import _path_matches
+from keystonemiddleware.tests.unit import utils
+
+
+class TestAccessRules(utils.BaseTestCase):
+
+ def test_path_matches(self):
+ good_matches = [
+ ('/v2/servers', '/v2/servers'),
+ ('/v2/servers/123', '/v2/servers/{server_id}'),
+ ('/v2/servers/123/', '/v2/servers/{server_id}/'),
+ ('/v2/servers/123', '/v2/servers/*'),
+ ('/v2/servers/123/', '/v2/servers/*/'),
+ ('/v2/servers/123', '/v2/servers/**'),
+ ('/v2/servers/123/', '/v2/servers/**'),
+ ('/v2/servers/123/456', '/v2/servers/**'),
+ ('/v2/servers', '**'),
+ ('/v2/servers/', '**'),
+ ('/v2/servers/123', '**'),
+ ('/v2/servers/123/456', '**'),
+ ('/v2/servers/123/volume/456', '**'),
+ ('/v2/servers/123/456', '/v2/*/*/*'),
+ ('/v2/123/servers/466', '/v2/{project_id}/servers/{server_id}'),
+ ]
+ for (request, pattern) in good_matches:
+ self.assertIsNotNone(_path_matches(request, pattern))
+ bad_matches = [
+ ('/v2/servers/someuuid', '/v2/servers'),
+ ('/v2/servers//', '/v2/servers/{server_id}'),
+ ('/v2/servers/123/', '/v2/servers/{server_id}'),
+ ('/v2/servers/123/456', '/v2/servers/{server_id}'),
+ ('/v2/servers/123/456', '/v2/servers/*'),
+ ('/v2/servers', 'v2/servers'),
+ ('/v2/servers/123/456/789', '/v2/*/*/*'),
+ ('/v2/servers/123/', '/v2/*/*/*'),
+ ('/v2/servers/', '/v2/servers/{server_id}'),
+ ('/v2/servers', '/v2/servers/{server_id}'),
+ ]
+ for (request, pattern) in bad_matches:
+ self.assertIsNone(_path_matches(request, pattern))
diff --git a/keystonemiddleware/tests/unit/test_opts.py b/keystonemiddleware/tests/unit/test_opts.py
index 7700d17..799bdd0 100644
--- a/keystonemiddleware/tests/unit/test_opts.py
+++ b/keystonemiddleware/tests/unit/test_opts.py
@@ -69,6 +69,7 @@ class OptsTestCase(utils.TestCase):
'auth_section',
'service_token_roles',
'service_token_roles_required',
+ 'service_type',
]
opt_names = [o.name for (g, l) in result_of_old_opts for o in l]
self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names)))
@@ -113,6 +114,7 @@ class OptsTestCase(utils.TestCase):
'auth_section',
'service_token_roles',
'service_token_roles_required',
+ 'service_type',
]
opt_names = [o.name for (g, l) in result for o in l]
self.assertThat(opt_names, matchers.HasLength(len(expected_opt_names)))