RBAC Utils Module

RBAC Utils Module

Overview

Patrole manipulates the os_primary Tempest credentials, which are the primary set of Tempest credentials. It is necessary to use the same credentials across the entire test setup/test execution/test teardown workflow because otherwise 400-level errors will be thrown by OpenStack services.

This is because many services check the request context’s project scope – and in very rare cases, user scope. However, each set of Tempest credentials (via dynamic credentials) is allocated its own distinct project. For example, the os_admin and os_primary credentials each have a distinct project, meaning that it is not always possible for the os_primary credentials to access resources created by the os_admin credentials.

The only foolproof solution is to manipulate the role for the same set of credentials, rather than using distinct credentials for setup/teardown and test execution, respectively. This is especially true when considering custom policy rule definitions, which can be arbitrarily complex.

Patrole, therefore, implicitly splits up each test into 3 stages: set up, test execution, and teardown.

The role workflow is as follows:

  1. Setup: Admin role is used automatically. The primary credentials are overridden with the admin role.
  2. Test execution: [patrole] rbac_test_role is used manually via the call to with rbac_utils.override_role(self). Everything that is executed within this contextmanager uses the primary credentials overridden with the [patrole] rbac_test_role.
  3. Teardown: Admin role is used automatically. The primary credentials have been overridden with the admin role.

Test Setup

Automatic role switch in background.

Resources can be set up inside the resource_setup class method that Tempest provides. These resources are typically reserved for “expensive” resources in terms of memory or storage requirements, like volumes and VMs. These resources are always created via the admin role; Patrole automatically handles this.

Like Tempest, however, Patrole must also create resources inside tests themselves. At the beginning of each test, the primary credentials have already been overridden with the admin role. One can create whatever test-level resources one needs, without having to worry about permissions.

Test Execution

Manual role switch required.

“Test execution” here means calling the API endpoint that enforces the policy action expected by the rbac_rule_validation decorator. Test execution should be performed only after calling with rbac_utils.override_role(self).

Immediately after that call, the API endpoint that enforces the policy should be called.

Examples

Always use the contextmanager before calling the API that enforces the expected policy action.

Example:

@rbac_rule_validation.action(
    service="nova",
    rule="os_compute_api:os-aggregates:show")
def test_show_aggregate_rbac(self):
    # Do test setup before the ``override_role`` call.
    aggregate_id = self._create_aggregate()
    # Call the ``override_role`` method so that the primary credentials
    # have the test role needed for test execution.
    with self.rbac_utils.override_role(self):
        self.aggregates_client.show_aggregate(aggregate_id)

When using a waiter, do the wait outside the contextmanager. “Waiting” always entails executing a GET request to the server, until the state of the returned resource matches a desired state. These GET requests enforce a different policy than the one expected. This is undesirable because Patrole should only test policies in isolation from one another.

Otherwise, the test result will be tainted, because instead of only the expected policy getting enforced with the os_primary role, at least two policies get enforced.

Example using waiter:

@rbac_rule_validation.action(
    service="nova",
    rule="os_compute_api:os-admin-password")
def test_change_server_password(self):
    original_password = self.servers_client.show_password(
        self.server['id'])
    self.addCleanup(self.servers_client.change_password, self.server['id'],
                    adminPass=original_password)

    with self.rbac_utils.override_role(self):
        self.servers_client.change_password(
            self.server['id'], adminPass=data_utils.rand_password())
    # Call the waiter outside the ``override_role`` contextmanager, so that
    # it is executed with admin role.
    waiters.wait_for_server_status(
        self.servers_client, self.server['id'], 'ACTIVE')

Below is an example of a method that enforces multiple policies getting called inside the contextmanager. The _complex_setup_method below performs the correct API that enforces the expected policy – in this case self.resources_client.create_resource – but then proceeds to use a waiter.

Incorrect:

def _complex_setup_method(self):
    resource = self.resources_client.create_resource(
        **kwargs)['resource']
    self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                    self._delete_resource, resource)
    waiters.wait_for_resource_status(
        self.resources_client, resource['id'], 'available')
    return resource

@rbac_rule_validation.action(
    service="example-service",
    rule="example-rule")
def test_change_server_password(self):
    # Never call a helper function inside the contextmanager that calls a
    # bunch of APIs. Only call the API that enforces the policy action
    # contained in the decorator above.
    with self.rbac_utils.override_role(self):
        self._complex_setup_method()

To fix this test, see the “Example using waiter” section above. It is recommended to re-implement the logic in a helper method inside a test such that only the relevant API is called inside the contextmanager, with everything extraneous outside.

Test Cleanup

Automatic role switch in background.

After the test – no matter whether it ended successfully or in failure – the credentials are overridden with the admin role by the Patrole framework, before tearDown or tearDownClass are called. This means that resources are always cleaned up using the admin role.

Implementation

class patrole_tempest_plugin.rbac_utils.RbacAuthority[source]

Class for validating whether a given role can perform a policy action.

Any class that extends RbacAuthority provides the logic for determining whether a role has permissions to execute a policy action.

allowed(rule, role)[source]

Determine whether the role should be able to perform the API.

Parameters:
  • rule – The name of the policy enforced by the API.
  • role – The role used to determine whether rule can be executed.
Returns:

True if the role has permissions to execute rule, else False.

class patrole_tempest_plugin.rbac_utils.RbacUtils(test_obj)[source]

Utility class responsible for switching os_primary role.

This class is responsible for overriding the value of the primary Tempest credential’s role (i.e. os_primary role). By doing so, it is possible to seamlessly swap between admin credentials, needed for setup and clean up, and primary credentials, needed to perform the API call which does policy enforcement. The primary credentials always cycle between roles defined by CONF.identity.admin_role and CONF.patrole.rbac_test_role.

_override_role(test_obj, toggle_rbac_role=False)[source]

Private helper for overriding os_primary Tempest credentials.

Parameters:
  • test_obj – instance of tempest.test.BaseTestCase
  • toggle_rbac_role – Boolean value that controls the role that overrides default role of os_primary credentials. * If True: role is set to [patrole] rbac_test_role * If False: role is set to [identity] admin_role
override_role(*args, **kwds)[source]

Override the role used by os_primary Tempest credentials.

Temporarily change the role used by os_primary credentials to:

  • [patrole] rbac_test_role before test execution
  • [identity] admin_role after test execution

Automatically switches to admin role after test execution.

Parameters:test_obj – Instance of tempest.test.BaseTestCase.
Returns:None

Warning

This function can alter user roles for pre-provisioned credentials. Work is underway to safely clean up after this function.

Example:

@rbac_rule_validation.action(service='test',
                             rule='a:test:rule')
def test_foo(self):
    # Allocate test-level resources here.
    with self.rbac_utils.override_role(self):
        # The role for `os_primary` has now been overriden. Within
        # this block, call the API endpoint that enforces the
        # expected policy specified by "rule" in the decorator.
        self.foo_service.bar_api_call()
    # The role is switched back to admin automatically. Note that
    # if the API call above threw an exception, any code below this
    # point in the test is not executed.
switch_role(test_obj, toggle_rbac_role)[source]

Switch the role used by os_primary Tempest credentials.

Switch the role used by os_primary credentials to:

  • admin if toggle_rbac_role is False
  • CONF.patrole.rbac_test_role if toggle_rbac_role is True
Parameters:
  • test_obj – instance of tempest.test.BaseTestCase
  • toggle_rbac_role – role to switch os_primary Tempest creds to
class patrole_tempest_plugin.rbac_utils.RbacUtilsMixin[source]

Mixin class to be used alongside an instance of tempest.test.BaseTestCase.

Should be used to perform Patrole class setup for a base RBAC class. Child classes should not use this mixin.

Example:

class BaseRbacTest(rbac_utils.RbacUtilsMixin, base.BaseV2ComputeTest):

    @classmethod
    def skip_checks(cls):
        super(BaseRbacTest, cls).skip_checks()
        cls.skip_rbac_checks()

    @classmethod
    def setup_clients(cls):
        super(BaseRbacTest, cls).setup_clients()
        cls.setup_rbac_utils()
patrole_tempest_plugin.rbac_utils.is_admin()[source]

Verifies whether the current test role equals the admin role.

Returns:True if rbac_test_role is the admin role.
Creative Commons Attribution 3.0 License

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.