
This change leverages the nine default personas available in tempest[1] to demonstrate a potential framework for testing default policies. An abstract base class is created that helps set up credentials and outlines every policy that needs to be tested, then nine subclasses are created to test every persona. Each test represents one policy rule, and some tests make multiple requests in order to test the policy from different approaches, for example, to check what happens if a different domain is specified, or what happens if the resource does not exist. The idea here is to be very verbose and explicit about what is being tested: every policy gets one test in the base class, and each persona is tested in a subclass. The layout should be easy to understand and someone reading the code should not be left guessing whether a case is missing or if there is magic happening in the background that is causing a false positive or false negative. This is intended to replace the unittest protection tests currently in place. [1] https://review.opendev.org/686306 (this will require additional devstack and keystone configuration to work properly in CI) Depends-on: https://review.opendev.org/686306 Depends-on: https://review.opendev.org/699051 Depends-on: https://review.opendev.org/699519 Depends-on: https://review.opendev.org/700826 Depends-on: https://review.opendev.org/743853 Depends-on: https://review.opendev.org/744087 Depends-on: https://review.opendev.org/744268 Depends-on: https://review.opendev.org/731087 Change-Id: Icb5317b9297230490bd783fe9b07c8db244c06f8
488 lines
19 KiB
Python
488 lines
19 KiB
Python
# Copyright 2020 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.
|
|
|
|
import abc
|
|
|
|
from tempest.api.identity import base
|
|
from tempest import clients
|
|
from tempest.lib import auth
|
|
from tempest.lib.common.utils import data_utils
|
|
from tempest.lib import exceptions
|
|
|
|
from keystone_tempest_plugin.tests.rbac.v3 import base as rbac_base
|
|
|
|
|
|
class IdentityV3RbacAccessRuleTest(rbac_base.IdentityV3RbacBaseTests,
|
|
metaclass=abc.ABCMeta):
|
|
|
|
identity_version = 'v3'
|
|
|
|
@classmethod
|
|
def setup_clients(cls):
|
|
super(IdentityV3RbacAccessRuleTest, cls).setup_clients()
|
|
cls.persona = getattr(cls, 'os_%s' % cls.credentials[0])
|
|
cls.client = cls.persona.access_rules_client
|
|
cls.admin_client = cls.os_system_admin
|
|
|
|
def user(self):
|
|
user = {}
|
|
name = data_utils.rand_name('user')
|
|
user['name'] = name
|
|
user['password'] = data_utils.rand_password()
|
|
return user
|
|
|
|
def app_cred(self):
|
|
app_cred = {}
|
|
app_cred['name'] = data_utils.rand_name('app_cred')
|
|
app_cred['access_rules'] = [
|
|
{
|
|
'path': '/servers',
|
|
'method': 'GET',
|
|
'service': 'compute',
|
|
}
|
|
]
|
|
return app_cred
|
|
|
|
@classmethod
|
|
def setup_user_client(cls, domain_id=None):
|
|
"""Set up project user with its own client.
|
|
|
|
This is to enable the project user to create its own app cred.
|
|
|
|
Returns a client object and the user's ID.
|
|
"""
|
|
user_dict = {
|
|
'name': data_utils.rand_name('user'),
|
|
'password': data_utils.rand_password(),
|
|
}
|
|
if domain_id:
|
|
user_dict['domain_id'] = domain_id
|
|
user_id = cls.admin_client.users_v3_client.create_user(
|
|
**user_dict)['user']['id']
|
|
|
|
def try_delete_user():
|
|
# delete user if not deleted by domain deletion
|
|
try:
|
|
cls.admin_client.users_v3_client.delete_user(user_id)
|
|
except exceptions.NotFound:
|
|
pass
|
|
|
|
cls.addClassResourceCleanup(try_delete_user)
|
|
project_id = cls.admin_client.projects_client.create_project(
|
|
data_utils.rand_name())['project']['id']
|
|
cls.addClassResourceCleanup(
|
|
cls.admin_client.projects_client.delete_project, project_id)
|
|
member_role_id = cls.admin_client.roles_v3_client.list_roles(
|
|
name='member')['roles'][0]['id']
|
|
cls.admin_client.roles_v3_client.create_user_role_on_project(
|
|
project_id, user_id, member_role_id)
|
|
creds = auth.KeystoneV3Credentials(
|
|
user_id=user_id,
|
|
password=user_dict['password'],
|
|
project_id=project_id)
|
|
auth_provider = clients.get_auth_provider(creds)
|
|
creds = auth_provider.fill_credentials()
|
|
client = clients.Manager(credentials=creds)
|
|
return client, user_id
|
|
|
|
@abc.abstractmethod
|
|
def test_identity_get_access_rule(self):
|
|
"""Test identity:get_access_rule policy
|
|
|
|
This test must check:
|
|
* whether the persona can retrieve an access rule they own
|
|
* whether the persona can retrieve an access rule they do not own
|
|
* whether the persona can retrieve an access rule that does not exist
|
|
* whether the persona can retrieve an access rule for a user in their
|
|
own domain (if applicable)
|
|
* whether the persona can retrieve an access rule for a user in
|
|
another domain (if applicable)
|
|
"""
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def test_identity_list_access_rules(self):
|
|
"""Test identity:list_access_rules policy
|
|
|
|
This test must check:
|
|
* whether the persona can list their own access rules
|
|
* whether the persona can list the access rules for another user
|
|
* whether the persona can list the access rules for a user in their
|
|
own domain
|
|
* whether the persona can list the access rules for a user in another
|
|
domain
|
|
"""
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def test_identity_delete_access_rule(self):
|
|
"""Test identity:delete_access_rule policy.
|
|
|
|
This test must check
|
|
* whether the persona can delete an access rule they own
|
|
* whether the persona can delete an access rule for an arbitrary user
|
|
* whether the persona can delete an access rule that does not exist
|
|
* whether the persona can delete an access rule for a user in another
|
|
domain (if applicable)
|
|
* whether the persona can delete an access rule for a user in their
|
|
own domain (if applicable)
|
|
* whether the persona can delete an access rule that does not exist
|
|
"""
|
|
pass
|
|
|
|
|
|
class SystemAdminTests(IdentityV3RbacAccessRuleTest, base.BaseIdentityTest):
|
|
|
|
credentials = ['system_admin']
|
|
|
|
@classmethod
|
|
def setup_clients(cls):
|
|
super(SystemAdminTests, cls).setup_clients()
|
|
cls.test_user_client, cls.test_user_id = cls.setup_user_client()
|
|
|
|
def setUp(self):
|
|
# create app cred for other user
|
|
super(SystemAdminTests, self).setUp()
|
|
app_cred_client = self.test_user_client.application_credentials_client
|
|
app_cred = app_cred_client.create_application_credential(
|
|
user_id=self.test_user_id, **self.app_cred()
|
|
)['application_credential']
|
|
self.app_cred_id = app_cred['id']
|
|
self.access_rule_id = app_cred['access_rules'][0]['id']
|
|
|
|
def try_delete_app_cred(id):
|
|
app_cred_client = self.admin_client.application_credentials_client
|
|
try:
|
|
app_cred_client.delete_application_credential(
|
|
user_id=self.test_user_id,
|
|
application_credential_id=id)
|
|
except exceptions.NotFound:
|
|
pass
|
|
|
|
def try_delete_access_rule(id):
|
|
try:
|
|
self.admin_client.access_rules_client.delete_access_rule(
|
|
user_id=self.test_user_id,
|
|
access_rule_id=id)
|
|
except exceptions.NotFound:
|
|
pass
|
|
self.addCleanup(try_delete_access_rule, self.access_rule_id)
|
|
self.addCleanup(try_delete_app_cred, self.app_cred_id)
|
|
|
|
def test_identity_get_access_rule(self):
|
|
# system admin cannot create app creds and therefore cannot create
|
|
# access rules, so skip retrieval of own access rule
|
|
|
|
# retrieve other user's access rules
|
|
self.do_request(
|
|
'show_access_rule',
|
|
user_id=self.test_user_id, access_rule_id=self.access_rule_id)
|
|
|
|
# retrieving a non-existent access rule should return a 404
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.NotFound,
|
|
user_id=self.test_user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
def test_identity_list_access_rules(self):
|
|
# system admin cannot create app creds and therefore cannot create
|
|
# access rules, so skip listing of own access rule
|
|
|
|
# list other user's access rules
|
|
self.do_request('list_access_rules', user_id=self.test_user_id)
|
|
|
|
def test_identity_delete_access_rule(self):
|
|
# system admin cannot create app creds and therefore cannot create
|
|
# access rules, so skip deletion of own access rule
|
|
|
|
# delete other user's access rules
|
|
app_cred_client = self.admin_client.application_credentials_client
|
|
app_cred_client.delete_application_credential(
|
|
user_id=self.test_user_id,
|
|
application_credential_id=self.app_cred_id)
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=204,
|
|
user_id=self.test_user_id, access_rule_id=self.access_rule_id)
|
|
|
|
# deleting a non-existent access rule should return a 404
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.NotFound,
|
|
user_id=self.test_user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
|
|
class SystemMemberTests(SystemAdminTests):
|
|
|
|
credentials = ['system_member', 'system_admin']
|
|
|
|
def test_identity_delete_access_rule(self):
|
|
app_cred_client = self.admin_client.application_credentials_client
|
|
app_cred_client.delete_application_credential(
|
|
user_id=self.test_user_id,
|
|
application_credential_id=self.app_cred_id)
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id, access_rule_id=self.access_rule_id)
|
|
|
|
# retrieving a non-existent access rule should return a 404
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.NotFound,
|
|
user_id=self.test_user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
|
|
class SystemReaderTests(SystemMemberTests):
|
|
|
|
credentials = ['system_reader', 'system_admin']
|
|
|
|
|
|
class DomainAdminTests(IdentityV3RbacAccessRuleTest, base.BaseIdentityTest):
|
|
|
|
# Domain admins cannot create their own app creds (app creds can only be
|
|
# scoped to projects) and domain admins have no special privileges over the
|
|
# app creds own by users in their domains.
|
|
|
|
credentials = ['domain_admin', 'system_admin']
|
|
|
|
@classmethod
|
|
def setup_clients(cls):
|
|
super(DomainAdminTests, cls).setup_clients()
|
|
own_domain_id = cls.persona.credentials.domain_id
|
|
cls.test_client_1, cls.test_user_1 = cls.setup_user_client(
|
|
domain_id=own_domain_id)
|
|
|
|
def setUp(self):
|
|
super(DomainAdminTests, self).setUp()
|
|
self.other_domain_id = self.admin_client.domains_client.create_domain(
|
|
name=data_utils.rand_name())['domain']['id']
|
|
self.addCleanup(self.admin_client.domains_client.delete_domain,
|
|
self.other_domain_id)
|
|
self.addCleanup(self.admin_client.domains_client.update_domain,
|
|
domain_id=self.other_domain_id, enabled=False)
|
|
self.test_client_2, self.test_user_2 = self.setup_user_client(
|
|
domain_id=self.other_domain_id)
|
|
client = self.test_client_1.application_credentials_client
|
|
app_cred_1 = client.create_application_credential(
|
|
user_id=self.test_user_1, **self.app_cred()
|
|
)['application_credential']
|
|
self.access_rule_1 = app_cred_1['access_rules'][0]['id']
|
|
self.addCleanup(
|
|
self.test_client_1.access_rules_client.delete_access_rule,
|
|
self.test_user_1,
|
|
self.access_rule_1)
|
|
self.addCleanup(
|
|
client.delete_application_credential,
|
|
self.test_user_1,
|
|
app_cred_1['id'])
|
|
client = self.test_client_2.application_credentials_client
|
|
app_cred_2 = client.create_application_credential(
|
|
user_id=self.test_user_2, **self.app_cred()
|
|
)['application_credential']
|
|
self.access_rule_2 = app_cred_2['access_rules'][0]['id']
|
|
self.addCleanup(
|
|
self.test_client_2.access_rules_client.delete_access_rule,
|
|
self.test_user_2,
|
|
self.access_rule_2)
|
|
self.addCleanup(
|
|
client.delete_application_credential,
|
|
self.test_user_2,
|
|
app_cred_2['id'])
|
|
|
|
def test_identity_get_access_rule(self):
|
|
# accessing access rules should be forbidden no matter whether the
|
|
# owner is in the domain or outside of it
|
|
|
|
# retrieve access rule from user in own domain
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_1, access_rule_id=self.access_rule_1)
|
|
|
|
# retrieve access rule from user in other domain
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_2, access_rule_id=self.access_rule_2)
|
|
|
|
# retrieving a non-existent access rule should return a 403
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_1,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_2,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
def test_identity_list_access_rules(self):
|
|
# listing access rules should be forbidden no matter whether the
|
|
# owner is in the domain or outside of it
|
|
self.do_request(
|
|
'list_access_rules', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_1)
|
|
self.do_request(
|
|
'list_access_rules', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_2)
|
|
|
|
def test_identity_delete_access_rule(self):
|
|
# deleting access rules should be forbidden no matter whether the
|
|
# owner is in the domain or outside of it
|
|
|
|
# delete access rule from user in own domain
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_1, access_rule_id=self.access_rule_1)
|
|
|
|
# delete access rule from user in other domain
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_2, access_rule_id=self.access_rule_2)
|
|
|
|
# deleting a non-existent access rule should return a 403
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_1,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_2,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
|
|
class DomainMemberTests(DomainAdminTests):
|
|
|
|
credentials = ['domain_member', 'system_admin']
|
|
|
|
|
|
class DomainReaderTests(DomainAdminTests):
|
|
|
|
credentials = ['domain_reader', 'system_admin']
|
|
|
|
|
|
class ProjectAdminTests(IdentityV3RbacAccessRuleTest, base.BaseIdentityTest):
|
|
|
|
credentials = ['project_admin', 'system_admin']
|
|
|
|
@classmethod
|
|
def setup_clients(cls):
|
|
super(ProjectAdminTests, cls).setup_clients()
|
|
cls.test_user_client, cls.test_user_id = cls.setup_user_client()
|
|
|
|
def setUp(self):
|
|
super(ProjectAdminTests, self).setUp()
|
|
app_cred_client = self.persona.application_credentials_client
|
|
user_id = self.persona.credentials.user_id
|
|
self.app_cred_1 = app_cred_client.create_application_credential(
|
|
user_id, **self.app_cred())['application_credential']
|
|
self.access_rule_1 = self.app_cred_1['access_rules'][0]['id']
|
|
|
|
def try_delete_own_app_cred(id):
|
|
app_cred_client = self.persona.application_credentials_client
|
|
try:
|
|
app_cred_client.delete_application_credential(
|
|
self.persona.credentials.user_id, id)
|
|
except exceptions.NotFound:
|
|
pass
|
|
|
|
def try_delete_own_access_rule(id):
|
|
try:
|
|
self.persona.access_rules_client.delete_access_rule(
|
|
self.persona.credentials.user_id, id)
|
|
except exceptions.NotFound:
|
|
pass
|
|
|
|
self.addCleanup(try_delete_own_access_rule, self.access_rule_1)
|
|
self.addCleanup(try_delete_own_app_cred, self.app_cred_1['id'])
|
|
|
|
app_cred_client = self.test_user_client.application_credentials_client
|
|
self.app_cred_2 = app_cred_client.create_application_credential(
|
|
self.test_user_id, **self.app_cred())['application_credential']
|
|
self.access_rule_2 = self.app_cred_2['access_rules'][0]['id']
|
|
self.addCleanup(
|
|
self.test_user_client.access_rules_client.delete_access_rule,
|
|
self.test_user_id, self.access_rule_2)
|
|
self.addCleanup(
|
|
app_cred_client.delete_application_credential,
|
|
self.test_user_id, self.app_cred_2['id'])
|
|
|
|
def test_identity_get_access_rule(self):
|
|
# should be able to access own credential
|
|
self.do_request(
|
|
'show_access_rule',
|
|
user_id=self.persona.credentials.user_id,
|
|
access_rule_id=self.access_rule_1)
|
|
|
|
# retrieving non-existent access rule for self should return 404
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.NotFound,
|
|
user_id=self.persona.credentials.user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
# should not be able to access another user's credential
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id, access_rule_id=self.access_rule_2)
|
|
|
|
# retrieving non-existent access rule for other user should return 403
|
|
self.do_request(
|
|
'show_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
def test_identity_list_access_rules(self):
|
|
# should be able to list own credentials
|
|
self.do_request(
|
|
'list_access_rules', user_id=self.persona.credentials.user_id)
|
|
|
|
# should not be able to list another user's credentials
|
|
self.do_request(
|
|
'list_access_rules', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id)
|
|
|
|
def test_identity_delete_access_rule(self):
|
|
# should be able to delete own credential
|
|
app_cred_client = self.persona.application_credentials_client
|
|
app_cred_client.delete_application_credential(
|
|
user_id=self.persona.credentials.user_id,
|
|
application_credential_id=self.app_cred_1['id'])
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=204,
|
|
user_id=self.persona.credentials.user_id,
|
|
access_rule_id=self.access_rule_1)
|
|
|
|
# deleting non-existent access rule for self should return 404
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.NotFound,
|
|
user_id=self.persona.credentials.user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
# should not be able to delete another user's credential
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id, access_rule_id=self.access_rule_2)
|
|
|
|
# deleting non-existent access rule for other user should return 403
|
|
self.do_request(
|
|
'delete_access_rule', expected_status=exceptions.Forbidden,
|
|
user_id=self.test_user_id,
|
|
access_rule_id=data_utils.rand_uuid_hex())
|
|
|
|
|
|
class ProjectMemberTests(ProjectAdminTests):
|
|
|
|
credentials = ['project_member', 'system_admin']
|
|
|
|
|
|
class ProjectReaderTests(ProjectAdminTests):
|
|
|
|
credentials = ['project_reader', 'system_admin']
|