From 99daead510eac0e2ca1a07b13e421987541efc36 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 17 Sep 2019 20:36:32 -0700 Subject: [PATCH] Modernize policy checker Without this patch, the policy checker issues a 'failed' result when checking a system-scoped sample token against a policy string like "role:admin and system_scope:all", because the policy checker does not understand the 'system_scope' attribute that is now in oslo.context[1] and wasn't parsing the "system" scope object from the sample token. Similarly, it fails on a string like "user_id:%(user_id)s" because it never looked up the user_id from the sample token. This change updates the policy checker to understand token contexts and policies like these so that more of the policy defaults in keystone, and soon other projects, will pass. This also adds a new system-scoped sample token to check against. [1] https://review.opendev.org/530509 Change-Id: I02fbbc99d28aa5c787133f530f6e968341107bf7 --- oslo_policy/shell.py | 10 +- oslo_policy/tests/test_checks.py | 6 +- oslo_policy/tests/test_shell.py | 73 +++++++++- oslo_policy/tests/token_fixture.py | 142 +++++++++++++++++++- sample_data/auth_v3_token_system_admin.json | 136 +++++++++++++++++++ 5 files changed, 356 insertions(+), 11 deletions(-) create mode 100644 sample_data/auth_v3_token_system_admin.json diff --git a/oslo_policy/shell.py b/oslo_policy/shell.py index 365a514e..7ffb5ea4 100644 --- a/oslo_policy/shell.py +++ b/oslo_policy/shell.py @@ -74,7 +74,11 @@ def tool(policy_file, access_file, apply_rule, is_admin=False, access_data = jsonutils.loads(access)['token'] access_data['roles'] = [role['name'] for role in access_data['roles']] - access_data['project_id'] = access_data['project']['id'] + access_data['user_id'] = access_data['user']['id'] + if access_data.get('project'): + access_data['project_id'] = access_data['project']['id'] + if access_data.get('system'): + access_data['system_scope'] = 'all' access_data['is_admin'] = is_admin with open(policy_file, "rb", 0) as p: @@ -90,7 +94,9 @@ def tool(policy_file, access_file, apply_rule, is_admin=False, target_data = flatten(jsonutils.loads(target)) else: - target_data = {"project_id": access_data['project_id']} + target_data = {'user_id': access_data['user']['id']} + if access_data.get('project_id'): + target_data['project_id'] = access_data['project_id'] if apply_rule: key = apply_rule diff --git a/oslo_policy/tests/test_checks.py b/oslo_policy/tests/test_checks.py index 1cd08ec5..8c2f143b 100644 --- a/oslo_policy/tests/test_checks.py +++ b/oslo_policy/tests/test_checks.py @@ -185,19 +185,19 @@ class GenericCheckTestCase(base.PolicyBaseTestCase): check = _checks.GenericCheck( 'token.catalog.endpoints.id', token_fixture.REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID) - credentials = token_fixture.SCOPED_TOKEN_FIXTURE + credentials = token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE self.assertTrue(check({}, credentials, self.enforcer)) def test_generic_role_check_matches(self): check = _checks.GenericCheck( 'token.roles.name', 'role1') - credentials = token_fixture.SCOPED_TOKEN_FIXTURE + credentials = token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE self.assertTrue(check({}, credentials, self.enforcer)) def test_generic_missing_role_does_not_matches(self): check = _checks.GenericCheck( 'token.roles.name', 'missing') - credentials = token_fixture.SCOPED_TOKEN_FIXTURE + credentials = token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE self.assertFalse(check({}, credentials, self.enforcer)) def test_multiple_nested_lists_accepted(self): diff --git a/oslo_policy/tests/test_shell.py b/oslo_policy/tests/test_shell.py index 09a07c71..32dad51b 100644 --- a/oslo_policy/tests/test_shell.py +++ b/oslo_policy/tests/test_shell.py @@ -34,6 +34,15 @@ class CheckerTestCase(base.PolicyBaseTestCase): "sampleservice:sample_rule2": "" "sampleservice:sample_rule0": "" "sampleservice:sample_rule1": "" +''' + + SAMPLE_POLICY_SCOPED = '''--- +"sampleservice:sample_rule": "role:role1" +"sampleservice:scoped_rule": "role:role1 and system_scope:all" +''' + + SAMPLE_POLICY_OWNER = '''--- +"sampleservice:owner_rule": "user_id:%(user_id)s" ''' def setUp(self): @@ -41,7 +50,7 @@ class CheckerTestCase(base.PolicyBaseTestCase): self.create_config_file("policy.yaml", self.SAMPLE_POLICY) self.create_config_file( "access.json", - jsonutils.dumps(token_fixture.SCOPED_TOKEN_FIXTURE)) + jsonutils.dumps(token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE)) @mock.patch("oslo_policy._checks.TrueCheck.__call__") def test_pass_rule_parameters(self, call_mock): @@ -53,12 +62,14 @@ class CheckerTestCase(base.PolicyBaseTestCase): stdout = self._capture_stdout() access_data = copy.deepcopy( - token_fixture.SCOPED_TOKEN_FIXTURE["token"]) + token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE["token"]) target = { - "project_id": access_data['project']['id'] + 'user_id': access_data['user']['id'], + 'project_id': access_data['project']['id'] } access_data['roles'] = [ role['name'] for role in access_data['roles']] + access_data['user_id'] = access_data['user']['id'] access_data['project_id'] = access_data['project']['id'] access_data['is_admin'] = is_admin @@ -71,6 +82,56 @@ class CheckerTestCase(base.PolicyBaseTestCase): ''' self.assertEqual(expected, stdout.getvalue()) + def test_pass_rule_parameters_with_scope(self): + self.create_config_file("policy.yaml", self.SAMPLE_POLICY_SCOPED) + self.create_config_file( + "access.json", + jsonutils.dumps(token_fixture.SYSTEM_SCOPED_TOKEN_FIXTURE)) + policy_file = self.get_config_file_fullname('policy.yaml') + access_file = self.get_config_file_fullname('access.json') + apply_rule = None + is_admin = False + stdout = self._capture_stdout() + + access_data = copy.deepcopy( + token_fixture.SYSTEM_SCOPED_TOKEN_FIXTURE["token"]) + access_data['roles'] = [ + role['name'] for role in access_data['roles']] + access_data['user_id'] = access_data['user']['id'] + access_data['is_admin'] = is_admin + + shell.tool(policy_file, access_file, apply_rule, is_admin) + + expected = '''passed: sampleservice:sample_rule +passed: sampleservice:scoped_rule +''' + self.assertEqual(expected, stdout.getvalue()) + + def test_pass_rule_parameters_with_owner(self): + self.create_config_file("policy.yaml", self.SAMPLE_POLICY_OWNER) + self.create_config_file( + "access.json", + jsonutils.dumps(token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE)) + policy_file = self.get_config_file_fullname('policy.yaml') + access_file = self.get_config_file_fullname('access.json') + apply_rule = None + is_admin = False + stdout = self._capture_stdout() + + access_data = copy.deepcopy( + token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE["token"]) + access_data['roles'] = [ + role['name'] for role in access_data['roles']] + access_data['user_id'] = access_data['user']['id'] + access_data['project_id'] = access_data['project']['id'] + access_data['is_admin'] = is_admin + + shell.tool(policy_file, access_file, apply_rule, is_admin) + + expected = '''passed: sampleservice:owner_rule +''' + self.assertEqual(expected, stdout.getvalue()) + def test_pass_rule_parameters_sorted(self): self.create_config_file("policy.yaml", self.SAMPLE_POLICY_UNSORTED) @@ -81,9 +142,10 @@ class CheckerTestCase(base.PolicyBaseTestCase): stdout = self._capture_stdout() access_data = copy.deepcopy( - token_fixture.SCOPED_TOKEN_FIXTURE["token"]) + token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE["token"]) access_data['roles'] = [ role['name'] for role in access_data['roles']] + access_data['user_id'] = access_data['user']['id'] access_data['project_id'] = access_data['project']['id'] access_data['is_admin'] = is_admin @@ -100,9 +162,10 @@ passed: sampleservice:sample_rule2 apply_rule = None is_admin = False access_data = copy.deepcopy( - token_fixture.SCOPED_TOKEN_FIXTURE["token"]) + token_fixture.PROJECT_SCOPED_TOKEN_FIXTURE["token"]) access_data['roles'] = [ role['name'] for role in access_data['roles']] + access_data['user_id'] = access_data['user']['id'] access_data['project_id'] = access_data['project']['id'] access_data['is_admin'] = is_admin diff --git a/oslo_policy/tests/token_fixture.py b/oslo_policy/tests/token_fixture.py index 6695efe2..683f1b17 100644 --- a/oslo_policy/tests/token_fixture.py +++ b/oslo_policy/tests/token_fixture.py @@ -16,7 +16,7 @@ REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID = '8cd4b957090f4ca5842a22e9a74099cd' -SCOPED_TOKEN_FIXTURE = { +PROJECT_SCOPED_TOKEN_FIXTURE = { "token": { "methods": [ "password" @@ -162,3 +162,143 @@ SCOPED_TOKEN_FIXTURE = { } } } + +SYSTEM_SCOPED_TOKEN_FIXTURE = { + "token": { + "methods": [ + "password" + ], + "expires_at": "2038-01-18T21:14:07Z", + "issued_at": "2000-01-18T21:14:07Z", + "roles": [ + { + "id": "41b1af9bb39241e8b8b79fae5906abcc", + "name": "role1" + }, + { + "id": "ac9add6b3c5a46dcaaf21390c4657949", + "name": "role2" + } + ], + "system": { + "all": True + }, + "catalog": [ + { + "endpoints": [ + { + "id": "3b5e554bcf114f2483e8a1be7a0506d1", + "interface": "admin", + "url": "http://127.0.0.1:8776/v1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "54abd2dc463c4ba4a72915498f8ecad1", + "interface": "internal", + "url": "http://127.0.0.1:8776/v1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "70a7efa4b1b941968357cc43ae1419ee", + "interface": "public", + "url": "http://127.0.0.1:8776/v1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + } + ], + "id": "5707c3fc0a294703a3c638e9cf6a6c3a", + "type": "volume", + "name": "volume" + }, + { + "endpoints": [ + { + "id": "92217a3b95394492859bc49fd474382f", + "interface": "admin", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + }, + { + "id": "f20563bdf66f4efa8a1f11d99b672be1", + "interface": "internal", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + }, + { + "id": "375f9ba459a447738fb60fe5fc26e9aa", + "interface": "public", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + } + ], + "id": "15c21aae6b274a8da52e0a068e908aac", + "type": "image", + "name": "glance" + }, + { + "endpoints": [ + { + "id": "edbd9f50f66746ae9ed11dc3b1ae35da", + "interface": "admin", + "url": "http://127.0.0.1:8774/v1.1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "9e03c46c80a34a159cb39f5cb0498b92", + "interface": "internal", + "url": "http://127.0.0.1:8774/v1.1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "1df0b44d92634d59bd0e0d60cf7ce432", + "interface": "public", + "url": + "http://127.0.0.1:8774/v1.1/" + + "64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + } + ], + "id": "2f404fdb89154c589efbc10726b029ec", + "type": "compute", + "name": "nova" + }, + { + "endpoints": [ + { + "id": "a4501e141a4b4e14bf282e7bffd81dc5", + "interface": "admin", + "url": "http://127.0.0.1:35357/v3", + "region": "RegionOne" + }, + { + "id": "3d17e3227bfc4483b58de5eaa584e360", + "interface": "internal", + "url": "http://127.0.0.1:35357/v3", + "region": "RegionOne" + }, + { + "id": REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID, + "interface": "public", + "url": "http://127.0.0.1:5000/v3", + "region": "RegionOne" + } + ], + "id": "c5d926d566424e4fba4f80c37916cde5", + "type": "identity", + "name": "keystone" + } + ], + "user": { + "domain": { + "id": "domain_id1", + "name": "domain_name1" + }, + "name": "user_name1", + "id": "user_id1" + } + } +} diff --git a/sample_data/auth_v3_token_system_admin.json b/sample_data/auth_v3_token_system_admin.json new file mode 100644 index 00000000..bbf963f1 --- /dev/null +++ b/sample_data/auth_v3_token_system_admin.json @@ -0,0 +1,136 @@ +{ + "token": { + "methods": [ + "password" + ], + "expires_at": "2038-01-18T21:14:07Z", + "issued_at": "2000-01-18T21:14:07Z", + "roles": [ + { + "id":"41b1af9bb39241e8b8b79fae5906abcc", + "name": "admin" + }, + { + "id": "ac9add6b3c5a46dcaaf21390c4657949", + "name": "member" + }, + { + "id": "b0cb8117845f4fd489865d498b80bab3", + "name": "reader" + } + ], + "system": { + "all": true + }, + "catalog": [ + { + "endpoints": [ + { + "id": "f84e070735e54914b41e2b5cfa94dcf7", + "interface": "admin", + "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "8220bba1d2844e0b81b171c6ede1155f", + "interface": "internal", + "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "719b92ea82a04e7a9ff1107c62da10da", + "interface": "public", + "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + } + ], + "type": "volume", + "name": "volume", + "id":"547e9195d1914b5eb087bedbc98fccc3" + }, + { + "endpoints": [ + { + "id": "44752324c0d44375bc854168ea22f1fc", + "interface": "admin", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + }, + { + "id": "a59b3734f57449078f1637c10f96c8e8", + "interface": "internal", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + }, + { + "id": "16c3ab1a4df640569812e432c98b2a48", + "interface": "public", + "url": "http://127.0.0.1:9292/v1", + "region": "regionOne" + } + ], + "type": "image", + "name": "glance", + "id": "22c15d232e55419eb4aeb3ebbd12aac2" + }, + { + "endpoints": [ + { + "id": "9c2fdc2d45bb45c5a7f973e235e0f998", + "interface": "admin", + "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "88ccfa8cbb7743998b38b998f4e6a720", + "interface": "internal", + "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + }, + { + "id": "113ee928c6934c92b9a12bd4e456c804", + "interface": "public", + "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", + "region": "regionOne" + } + ], + "type": "compute", + "name": "nova", + "id": "fbf2afcdeb10473392636df9785d3fb5" + }, + { + "endpoints": [ + { + "id": "c10a5cda00784049953296d18464aa38", + "interface": "admin", + "url": "http://127.0.0.1:35357/v3", + "region": "RegionOne" + }, + { + "id": "334650263e064428bb2f0b7c3c7a743c", + "interface": "internal", + "url": "http://127.0.0.1:35357/v3", + "region": "RegionOne" + }, + { + "id": "52ff54addc38430d9b656c7164e2caf8", + "interface": "public", + "url": "http://127.0.0.1:5000/v3", + "region": "RegionOne" + } + ], + "type": "identity", + "name": "keystone", + "id": "a0d9913a4bca4d5699e151804e0b5172" + } + ], + "user": { + "domain": { + "id": "domain_id1", + "name": "domain_name1" + }, + "name": "user_name1", + "id": "user_id1" + } + } +}