diff --git a/keystone/common/policies/domain.py b/keystone/common/policies/domain.py index 2a8238ed12..342bcdf615 100644 --- a/keystone/common/policies/domain.py +++ b/keystone/common/policies/domain.py @@ -43,17 +43,19 @@ deprecated_delete_domain = policy.DeprecatedRule( name=base.IDENTITY % 'delete_domain', check_str=base.RULE_ADMIN_REQUIRED ) +SYSTEM_USER_OR_DOMAIN_USER_OR_PROJECT_USER = ( + '(role:reader and system_scope:all) or ' + 'token.domain.id:%(target.domain.id)s or ' + 'token.project.domain.id:%(target.domain.id)s' +) + domain_policies = [ policy.DocumentedRuleDefault( name=base.IDENTITY % 'get_domain', # NOTE(lbragstad): This policy allows system, domain, and # project-scoped tokens. - check_str=( - '(role:reader and system_scope:all) or ' - 'token.domain.id:%(target.domain.id)s or ' - 'token.project.domain.id:%(target.domain.id)s' - ), + check_str=SYSTEM_USER_OR_DOMAIN_USER_OR_PROJECT_USER, scope_types=['system', 'domain', 'project'], description='Show domain details.', operations=[{'path': '/v3/domains/{domain_id}', diff --git a/keystone/tests/unit/protection/v3/test_domains.py b/keystone/tests/unit/protection/v3/test_domains.py index 43136a6c74..5997ac986f 100644 --- a/keystone/tests/unit/protection/v3/test_domains.py +++ b/keystone/tests/unit/protection/v3/test_domains.py @@ -12,14 +12,17 @@ import uuid +from oslo_serialization import jsonutils from six.moves import http_client +from keystone.common.policies import domain as dp from keystone.common import provider_api import keystone.conf from keystone.tests.common import auth as common_auth from keystone.tests import unit from keystone.tests.unit import base_classes from keystone.tests.unit import ksfixtures +from keystone.tests.unit.ksfixtures import temporaryfile CONF = keystone.conf.CONF PROVIDERS = provider_api.ProviderAPIs @@ -384,3 +387,156 @@ class DomainUserTests(base_classes.TestCaseWithBootstrap, r = c.post('/v3/auth/tokens', json=auth) self.token_id = r.headers['X-Subject-Token'] self.headers = {'X-Auth-Token': self.token_id} + + +class ProjectReaderTests(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _DomainAndProjectUserDomainTests): + + def setUp(self): + super(ProjectReaderTests, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=True) + + domain = PROVIDERS.resource_api.create_domain( + uuid.uuid4().hex, unit.new_domain_ref() + ) + self.domain_id = domain['id'] + + project_reader = unit.new_user_ref(domain_id=self.domain_id) + project_reader_id = PROVIDERS.identity_api.create_user( + project_reader + )['id'] + project = unit.new_project_ref(domain_id=self.domain_id) + project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.reader_role_id, user_id=project_reader_id, + project_id=project_id + ) + + auth = self.build_authentication_request( + user_id=project_reader_id, + password=project_reader['password'], + project_id=project_id + ) + + # 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} + + +class ProjectMemberTests(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _DomainAndProjectUserDomainTests): + + def setUp(self): + super(ProjectMemberTests, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=True) + + domain = PROVIDERS.resource_api.create_domain( + uuid.uuid4().hex, unit.new_domain_ref() + ) + self.domain_id = domain['id'] + + project_member = unit.new_user_ref(domain_id=self.domain_id) + project_member_id = PROVIDERS.identity_api.create_user( + project_member + )['id'] + project = unit.new_project_ref(domain_id=self.domain_id) + project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.member_role_id, user_id=project_member_id, + project_id=project_id + ) + + auth = self.build_authentication_request( + user_id=project_member_id, + password=project_member['password'], + project_id=project_id + ) + + # 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} + + +class ProjectAdminTests(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _DomainAndProjectUserDomainTests): + + def setUp(self): + super(ProjectAdminTests, self).setUp() + self.loadapp() + self.policy_file = self.useFixture(temporaryfile.SecureTempFile()) + self.policy_file_name = self.policy_file.file_name + self.useFixture( + ksfixtures.Policy( + self.config_fixture, policy_file=self.policy_file_name + ) + ) + self._override_policy() + self.config_fixture.config(group='oslo_policy', enforce_scope=True) + + domain = PROVIDERS.resource_api.create_domain( + uuid.uuid4().hex, unit.new_domain_ref() + ) + self.domain_id = domain['id'] + + project_admin = unit.new_user_ref(domain_id=self.domain_id) + project_admin_id = PROVIDERS.identity_api.create_user( + project_admin + )['id'] + project = unit.new_project_ref(domain_id=self.domain_id) + project_id = PROVIDERS.resource_api.create_project( + project['id'], project + )['id'] + + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.admin_role_id, user_id=project_admin_id, + project_id=project_id + ) + + auth = self.build_authentication_request( + user_id=project_admin_id, + password=project_admin['password'], + project_id=project_id + ) + + # 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 _override_policy(self): + # TODO(lbragstad): Remove this once the deprecated policies in + # keystone.common.policies.domains have been removed. This is only + # here to make sure we test the new policies instead of the deprecated + # ones. Oslo.policy will OR deprecated policies with new policies to + # maintain compatibility and give operators a chance to update + # permissions or update policies without breaking users. This will + # cause these specific tests to fail since we're trying to correct this + # broken behavior with better scope checking. + with open(self.policy_file_name, 'w') as f: + overridden_policies = { + 'identity:get_domain': ( + dp.SYSTEM_USER_OR_DOMAIN_USER_OR_PROJECT_USER + ) + } + f.write(jsonutils.dumps(overridden_policies)) diff --git a/releasenotes/notes/bug-1794864-3116bf165a146be6.yaml b/releasenotes/notes/bug-1794864-3116bf165a146be6.yaml new file mode 100644 index 0000000000..267c8d5c12 --- /dev/null +++ b/releasenotes/notes/bug-1794864-3116bf165a146be6.yaml @@ -0,0 +1,41 @@ +--- +upgrade: + - | + [`bug 1794864 `_] + [`bug 1794376 `_] + The default policies that protect the domains API have been deprecated in + favor of ones that are more secure and self-serviceable. If you're + maintaining custom policies, please make sure you resolve your domain + policies to work with the new default by adding the proper role + assignments, or continue maintaining custom overrides. The new defaults + allow for better protection of the domains API when giving the `admin` role + to users on domains and projects. +deprecations: + - | + [`bug 1794864 `_] + [`bug 1794376 `_] + The default policies that protect the domains API have been deprecated in + favor of ones that are more secure and self-serviceable. If you're + maintaining custom policies, please make sure you resolve your domain + policies to work with the new default by adding the proper role + assignments, or continue maintaining custom overrides. The new defaults + allow for better protection of the domains API when giving the `admin` role + to users on domains and projects. +security: + - | + [`bug 1794864 `_] + [`bug 1794376 `_] + The default policies that protect the domains API have been deprecated in + favor of ones that are more secure and self-serviceable. +fixes: + - | + [`bug 1794864 `_] + [`bug 1794376 `_] + The default policies that protect the domains API have been deprecated in + favor of ones that are more secure and self-serviceable. Users with roles + on domains and projects are now able to call the + ``GET /v3/domains/{domain_id}`` API if they use a token scoped to that + domain or a token scoped to a project within that domain. System users are + allowed to access the domain APIs in the same way legacy `admin` users were + able to. This allows for better protection of the domain API when giving + the `admin` role to users on domains and projects.