Implement system reader role for trusts API

Currently, the trusts API only allows the "project" scope type, and
moreover inconsistently enforces different actions based on admin status
or trustor/trustee relationship: for example, an "admin" can list all
trusts but not filter by trustor or trustee and cannot get details for a
single trust, not can they list or get trust roles. This patch changes
the behavior of the trusts API to allow a system reader to list and get
details for trusts and trust roles, where previously only a trustor or
trustee could do so. This helps make the different actions in the trusts
API consistent with one another and makes the API more useful to a
deployment auditor. A subsequent patch will add system admin
functionality.

This change does not use the oslo.policy deprecation feature for the
'identity:list_trusts_for_trustor' or 'identity:list_trusts_for_trustee'
policies as those are new policies introduced in 7717ed3.

Change-Id: I4e1482643e18fd46e937ffae8b3623cea2d2dd62
Partial-bug: #1818850
Partial-bug: #1818846
Related-Bug: #968696
This commit is contained in:
Colleen Murphy 2019-08-15 18:39:41 -07:00
parent 09e699baba
commit ea7acd8036
2 changed files with 210 additions and 44 deletions

View File

@ -10,12 +10,43 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import versionutils
from oslo_policy import policy
from keystone.common.policies import base
RULE_TRUSTOR = 'user_id:%(target.trust.trustor_user_id)s'
RULE_TRUSTEE = 'user_id:%(target.trust.trustee_user_id)s'
SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE = (
base.SYSTEM_READER + ' or ' + RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
)
SYSTEM_READER_OR_TRUSTOR = base.SYSTEM_READER + ' or ' + RULE_TRUSTOR
SYSTEM_READER_OR_TRUSTEE = base.SYSTEM_READER + ' or ' + RULE_TRUSTEE
deprecated_list_trusts = policy.DeprecatedRule(
name=base.IDENTITY % 'list_trusts',
check_str=base.RULE_ADMIN_REQUIRED
)
deprecated_list_roles_for_trust = policy.DeprecatedRule(
name=base.IDENTITY % 'list_roles_for_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
)
deprecated_get_role_for_trust = policy.DeprecatedRule(
name=base.IDENTITY % 'get_role_for_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
)
deprecated_get_trust = policy.DeprecatedRule(
name=base.IDENTITY % 'get_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
)
DEPRECATED_REASON = """
As of the Train release, the trust API now understands default roles and
system-scoped tokens, making the API more granular by default without
compromising security. The new policy defaults account for these changes
automatically. Be sure to take these new defaults into consideration if you are
relying on overrides in your deployment for the service API.
"""
trust_policies = [
policy.DocumentedRuleDefault(
@ -30,17 +61,20 @@ trust_policies = [
'method': 'POST'}]),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'list_trusts',
check_str=base.RULE_ADMIN_REQUIRED,
scope_types=['project'],
check_str=base.SYSTEM_READER,
scope_types=['system'],
description='List trusts.',
operations=[{'path': '/v3/OS-TRUST/trusts',
'method': 'GET'},
{'path': '/v3/OS-TRUST/trusts',
'method': 'HEAD'}]),
'method': 'HEAD'}],
deprecated_rule=deprecated_list_trusts,
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.TRAIN),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'list_trusts_for_trustor',
check_str=RULE_TRUSTOR,
scope_types=['project'],
check_str=SYSTEM_READER_OR_TRUSTOR,
scope_types=['system', 'project'],
description='List trusts for trustor.',
operations=[{'path': '/v3/OS-TRUST/trusts?trustor_user_id={trustor_user_id}',
'method': 'GET'},
@ -48,8 +82,8 @@ trust_policies = [
'method': 'HEAD'}]),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'list_trusts_for_trustee',
check_str=RULE_TRUSTEE,
scope_types=['project'],
check_str=SYSTEM_READER_OR_TRUSTEE,
scope_types=['system', 'project'],
description='List trusts for trustee.',
operations=[{'path': '/v3/OS-TRUST/trusts?trustee_user_id={trustee_user_id}',
'method': 'GET'},
@ -57,22 +91,28 @@ trust_policies = [
'method': 'HEAD'}]),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'list_roles_for_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
scope_types=['project'],
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
scope_types=['system', 'project'],
description='List roles delegated by a trust.',
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
'method': 'GET'},
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
'method': 'HEAD'}]),
'method': 'HEAD'}],
deprecated_rule=deprecated_list_roles_for_trust,
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.TRAIN),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'get_role_for_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
scope_types=['project'],
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
scope_types=['system', 'project'],
description='Check if trust delegates a particular role.',
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
'method': 'GET'},
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
'method': 'HEAD'}]),
'method': 'HEAD'}],
deprecated_rule=deprecated_get_role_for_trust,
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.TRAIN),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'delete_trust',
check_str=RULE_TRUSTOR,
@ -82,13 +122,16 @@ trust_policies = [
'method': 'DELETE'}]),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'get_trust',
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
scope_types=['project'],
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
scope_types=['system', 'project'],
description='Get trust.',
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}',
'method': 'GET'},
{'path': '/v3/OS-TRUST/trusts/{trust_id}',
'method': 'HEAD'}])
'method': 'HEAD'}],
deprecated_rule=deprecated_get_trust,
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.TRAIN)
]

View File

@ -147,15 +147,28 @@ class _AdminTestsMixin(object):
)
self.assertEqual(1, len(r.json['trusts']))
def test_admin_cannot_get_trust_for_other_user(self):
PROVIDERS.trust_api.create_trust(
class AdminTokenTests(TrustTests, _AdminTestsMixin):
"""Tests for the is_admin user.
The Trusts API has hardcoded is_admin checks that we need to ensure are
preserved through the system-scope transition.
"""
def setUp(self):
super(AdminTokenTests, self).setUp()
self.config_fixture.config(admin_token='ADMIN')
self.headers = {'X-Auth-Token': 'ADMIN'}
def test_admin_can_delete_trust_for_other_user(self):
ref = PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
c.get(
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
c.delete(
'/v3/OS-TRUST/trusts/%s' % ref['id'],
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
expected_status_code=http_client.NO_CONTENT
)
def test_admin_can_get_non_existent_trust_not_found(self):
@ -167,6 +180,17 @@ class _AdminTestsMixin(object):
expected_status_code=http_client.NOT_FOUND
)
def test_admin_cannot_get_trust_for_other_user(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
c.get(
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_admin_cannot_list_trust_roles_for_other_user(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
@ -191,19 +215,118 @@ class _AdminTestsMixin(object):
)
class AdminTokenTests(TrustTests, _AdminTestsMixin):
"""Tests for the is_admin user.
class _SystemUserTests(object):
"""Tests for system admin, member, and reader."""
The Trusts API has hardcoded is_admin checks that we need to ensure are
preserved through the system-scope transition.
"""
def test_user_can_get_non_existent_trust(self):
trust_id = uuid.uuid4().hex
with self.test_client() as c:
c.get(
'/v3/OS-TRUST/trusts/%s' % trust_id,
headers=self.headers,
expected_status_code=http_client.NOT_FOUND
)
def test_user_can_get_trust_for_other_user(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
r = c.get(
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
headers=self.headers
)
self.assertEqual(r.json['trust']['id'], self.trust_id)
def test_user_can_list_trusts_for_trustee(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
c.get(
('/v3/OS-TRUST/trusts?trustee_user_id=%s' %
self.trustee_user_id),
headers=self.headers
)
def test_user_can_list_trusts_for_trustor(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
c.get(
('/v3/OS-TRUST/trusts?trustor_user_id=%s' %
self.trustor_user_id),
headers=self.headers
)
def test_user_can_list_trust_roles_for_other_user(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
r = c.get(
'/v3/OS-TRUST/trusts/%s/roles' % self.trust_id,
headers=self.headers
)
self.assertEqual(r.json['roles'][0]['id'],
self.bootstrapper.member_role_id)
def test_user_can_get_trust_role_for_other_user(self):
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
c.get(
('/v3/OS-TRUST/trusts/%s/roles/%s' %
(self.trust_id, self.bootstrapper.member_role_id)),
headers=self.headers
)
class SystemReaderTests(TrustTests, _SystemUserTests):
"""Tests for system reader users."""
def setUp(self):
super(AdminTokenTests, self).setUp()
self.config_fixture.config(admin_token='ADMIN')
self.headers = {'X-Auth-Token': 'ADMIN'}
super(SystemReaderTests, self).setUp()
self.config_fixture.config(group='oslo_policy', enforce_scope=True)
def test_admin_can_delete_trust_for_other_user(self):
system_reader = unit.new_user_ref(
domain_id=CONF.identity.default_domain_id
)
self.user_id = PROVIDERS.identity_api.create_user(
system_reader
)['id']
PROVIDERS.assignment_api.create_system_grant_for_user(
self.user_id, self.bootstrapper.reader_role_id
)
auth = self.build_authentication_request(
user_id=self.user_id,
password=system_reader['password'],
system=True
)
# Grab a token using the persona we're testing and prepare headers
# for requests we'll be making in the tests.
with self.test_client() as c:
r = c.post('/v3/auth/tokens', json=auth)
self.token_id = r.headers['X-Subject-Token']
self.headers = {'X-Auth-Token': self.token_id}
def test_user_cannot_create_trust(self):
json = {'trust': self.trust_data['trust']}
json['trust']['roles'] = self.trust_data['roles']
with self.test_client() as c:
c.post(
'/v3/OS-TRUST/trusts',
json=json,
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_delete_trust(self):
ref = PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
@ -211,11 +334,11 @@ class AdminTokenTests(TrustTests, _AdminTestsMixin):
c.delete(
'/v3/OS-TRUST/trusts/%s' % ref['id'],
headers=self.headers,
expected_status_code=http_client.NO_CONTENT
expected_status_code=http_client.FORBIDDEN
)
class SystemAdminTests(TrustTests, _AdminTestsMixin):
class SystemAdminTests(TrustTests, _AdminTestsMixin, _SystemUserTests):
"""Tests for system admin users."""
def setUp(self):
@ -250,18 +373,6 @@ class SystemAdminTests(TrustTests, _AdminTestsMixin):
expected_status_code=http_client.FORBIDDEN
)
def test_admin_list_all_trusts_overridden_defaults(self):
self._override_policy_old_defaults()
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
r = c.get(
'/v3/OS-TRUST/trusts',
headers=self.headers
)
self.assertEqual(1, len(r.json['trusts']))
def test_admin_cannot_delete_trust_for_user_overridden_defaults(self):
# only the is_admin admin can do this
self._override_policy_old_defaults()
@ -312,6 +423,18 @@ class SystemAdminTests(TrustTests, _AdminTestsMixin):
expected_status_code=http_client.FORBIDDEN
)
def test_user_list_all_trusts_overridden_defaults(self):
self._override_policy_old_defaults()
PROVIDERS.trust_api.create_trust(
self.trust_id, **self.trust_data)
with self.test_client() as c:
r = c.get(
'/v3/OS-TRUST/trusts',
headers=self.headers
)
self.assertEqual(1, len(r.json['trusts']))
class ProjectUserTests(TrustTests):
"""Tests for all project users."""