Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 1 | .. _rbac-utils: |
| 2 | |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 3 | RBAC Utils Module |
| 4 | ================= |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 5 | |
| 6 | Overview |
| 7 | -------- |
| 8 | |
| 9 | Patrole manipulates the ``os_primary`` `Tempest credentials`_, which are the |
| 10 | primary set of Tempest credentials. It is necessary to use the same credentials |
| 11 | across the entire test setup/test execution/test teardown workflow |
| 12 | because otherwise 400-level errors will be thrown by OpenStack services. |
| 13 | |
| 14 | This is because many services check the request context's project scope -- and |
| 15 | in very rare cases, user scope. However, each set of Tempest credentials (via |
| 16 | `dynamic credentials`_) is allocated its own distinct project. For example, the |
| 17 | ``os_admin`` and ``os_primary`` credentials each have a distinct project, |
| 18 | meaning that it is not always possible for the ``os_primary`` credentials to |
| 19 | access resources created by the ``os_admin`` credentials. |
| 20 | |
| 21 | The only foolproof solution is to manipulate the role for the same set of |
| 22 | credentials, rather than using distinct credentials for setup/teardown |
| 23 | and test execution, respectively. This is especially true when considering |
| 24 | custom policy rule definitions, which can be arbitrarily complex. |
| 25 | |
Felipe Monteiro | 543f7b9 | 2018-06-10 13:38:31 -0400 | [diff] [blame] | 26 | .. _role-overriding: |
| 27 | |
| 28 | Role Overriding |
| 29 | ^^^^^^^^^^^^^^^ |
| 30 | |
| 31 | Role overriding is the way Patrole is able to create resources and delete |
| 32 | resources -- including those that require admin credentials -- while still |
| 33 | being able to exercise the same set of Tempest credentials to perform the API |
| 34 | action that authorizes the policy under test, by manipulating the role of |
| 35 | the Tempest credentials. |
| 36 | |
| 37 | Patrole implicitly splits up each test into 3 stages: set up, test execution, |
| 38 | and teardown. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 39 | |
| 40 | The role workflow is as follows: |
| 41 | |
| 42 | #. Setup: Admin role is used automatically. The primary credentials are |
| 43 | overridden with the admin role. |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 44 | #. Test execution: ``[patrole] rbac_test_role`` is used manually via the |
| 45 | call to ``with rbac_utils.override_role(self)``. Everything that |
| 46 | is executed within this contextmanager uses the primary |
| 47 | credentials overridden with the ``[patrole] rbac_test_role``. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 48 | #. Teardown: Admin role is used automatically. The primary credentials have |
| 49 | been overridden with the admin role. |
| 50 | |
| 51 | .. _Tempest credentials: https://docs.openstack.org/tempest/latest/library/credential_providers.html |
| 52 | .. _dynamic credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials |
| 53 | |
| 54 | Test Setup |
| 55 | ---------- |
| 56 | |
Felipe Monteiro | 543f7b9 | 2018-06-10 13:38:31 -0400 | [diff] [blame] | 57 | Automatic role override in background. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 58 | |
| 59 | Resources can be set up inside the ``resource_setup`` class method that Tempest |
| 60 | provides. These resources are typically reserved for "expensive" resources |
| 61 | in terms of memory or storage requirements, like volumes and VMs. These |
| 62 | resources are **always** created via the admin role; Patrole automatically |
| 63 | handles this. |
| 64 | |
| 65 | Like Tempest, however, Patrole must also create resources inside tests |
| 66 | themselves. At the beginning of each test, the primary credentials have already |
| 67 | been overridden with the admin role. One can create whatever test-level |
| 68 | resources one needs, without having to worry about permissions. |
| 69 | |
| 70 | Test Execution |
| 71 | -------------- |
| 72 | |
Felipe Monteiro | 543f7b9 | 2018-06-10 13:38:31 -0400 | [diff] [blame] | 73 | Manual role override required. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 74 | |
| 75 | "Test execution" here means calling the API endpoint that enforces the policy |
| 76 | action expected by the ``rbac_rule_validation`` decorator. Test execution |
| 77 | should be performed *only after* calling |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 78 | ``with rbac_utils.override_role(self)``. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 79 | |
| 80 | Immediately after that call, the API endpoint that enforces the policy should |
| 81 | be called. |
| 82 | |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 83 | Examples |
| 84 | ^^^^^^^^ |
| 85 | |
| 86 | Always use the contextmanager before calling the API that enforces the |
| 87 | expected policy action. |
| 88 | |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 89 | Example:: |
| 90 | |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 91 | @rbac_rule_validation.action( |
| 92 | service="nova", |
| 93 | rule="os_compute_api:os-aggregates:show") |
| 94 | def test_show_aggregate_rbac(self): |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 95 | # Do test setup before the ``override_role`` call. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 96 | aggregate_id = self._create_aggregate() |
Felipe Monteiro | 144ec1e | 2017-12-26 17:38:11 +0000 | [diff] [blame] | 97 | # Call the ``override_role`` method so that the primary credentials |
| 98 | # have the test role needed for test execution. |
| 99 | with self.rbac_utils.override_role(self): |
| 100 | self.aggregates_client.show_aggregate(aggregate_id) |
| 101 | |
| 102 | When using a waiter, do the wait outside the contextmanager. "Waiting" always |
| 103 | entails executing a ``GET`` request to the server, until the state of the |
| 104 | returned resource matches a desired state. These ``GET`` requests enforce |
| 105 | a different policy than the one expected. This is undesirable because |
| 106 | Patrole should only test policies in isolation from one another. |
| 107 | |
| 108 | Otherwise, the test result will be tainted, because instead of only the |
| 109 | expected policy getting enforced with the ``os_primary`` role, at least |
| 110 | two policies get enforced. |
| 111 | |
| 112 | Example using waiter:: |
| 113 | |
| 114 | @rbac_rule_validation.action( |
| 115 | service="nova", |
| 116 | rule="os_compute_api:os-admin-password") |
| 117 | def test_change_server_password(self): |
| 118 | original_password = self.servers_client.show_password( |
| 119 | self.server['id']) |
| 120 | self.addCleanup(self.servers_client.change_password, self.server['id'], |
| 121 | adminPass=original_password) |
| 122 | |
| 123 | with self.rbac_utils.override_role(self): |
| 124 | self.servers_client.change_password( |
| 125 | self.server['id'], adminPass=data_utils.rand_password()) |
| 126 | # Call the waiter outside the ``override_role`` contextmanager, so that |
| 127 | # it is executed with admin role. |
| 128 | waiters.wait_for_server_status( |
| 129 | self.servers_client, self.server['id'], 'ACTIVE') |
| 130 | |
| 131 | Below is an example of a method that enforces multiple policies getting |
| 132 | called inside the contextmanager. The ``_complex_setup_method`` below |
| 133 | performs the correct API that enforces the expected policy -- in this |
| 134 | case ``self.resources_client.create_resource`` -- but then proceeds to |
| 135 | use a waiter. |
| 136 | |
| 137 | Incorrect:: |
| 138 | |
| 139 | def _complex_setup_method(self): |
| 140 | resource = self.resources_client.create_resource( |
| 141 | **kwargs)['resource'] |
| 142 | self.addCleanup(test_utils.call_and_ignore_notfound_exc, |
| 143 | self._delete_resource, resource) |
| 144 | waiters.wait_for_resource_status( |
| 145 | self.resources_client, resource['id'], 'available') |
| 146 | return resource |
| 147 | |
| 148 | @rbac_rule_validation.action( |
| 149 | service="example-service", |
| 150 | rule="example-rule") |
| 151 | def test_change_server_password(self): |
| 152 | # Never call a helper function inside the contextmanager that calls a |
| 153 | # bunch of APIs. Only call the API that enforces the policy action |
| 154 | # contained in the decorator above. |
| 155 | with self.rbac_utils.override_role(self): |
| 156 | self._complex_setup_method() |
| 157 | |
| 158 | To fix this test, see the "Example using waiter" section above. It is |
| 159 | recommended to re-implement the logic in a helper method inside a test such |
| 160 | that only the relevant API is called inside the contextmanager, with |
| 161 | everything extraneous outside. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 162 | |
| 163 | Test Cleanup |
| 164 | ------------ |
| 165 | |
Felipe Monteiro | 543f7b9 | 2018-06-10 13:38:31 -0400 | [diff] [blame] | 166 | Automatic role override in background. |
Felipe Monteiro | c8ec1f6 | 2017-11-15 08:32:56 +0000 | [diff] [blame] | 167 | |
| 168 | After the test -- no matter whether it ended successfully or in failure -- |
| 169 | the credentials are overridden with the admin role by the Patrole framework, |
| 170 | *before* ``tearDown`` or ``tearDownClass`` are called. This means that |
| 171 | resources are always cleaned up using the admin role. |
| 172 | |
| 173 | Implementation |
| 174 | -------------- |
| 175 | |
| 176 | .. automodule:: patrole_tempest_plugin.rbac_utils |
| 177 | :members: |
| 178 | :private-members: |
Felipe Monteiro | 9358f74 | 2018-09-12 14:49:43 -0600 | [diff] [blame] | 179 | :special-members: |