blob: 441bd35e079136e017853e06e29a2cd2ecf85a54 [file] [log] [blame]
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -04001.. _multi-policy-validation:
2
3=======================
4Multi-policy Validation
5=======================
6
7Introduction
8------------
9
10Multi-policy validation exists in Patrole because if one policy were assumed,
11then tests could fail because they would not consider all the policies actually
12being enforced. The reasoning can be found in `this spec`_. Basically,
13since Patrole derives the expected test result dynamically in order to test any
14role, each policy enforced by the API under test must be considered to derive
15an accurate expected test result, or else the expected and actual test
16results will not always match, resulting in overall test failure. For more
17information about Patrole's RBAC validation work flow, reference
18:ref:`rbac-validation`.
19
20Multi-policy support allows Patrole to more accurately offer RBAC tests for API
21endpoints that enforce multiple policy actions.
22
Luigi Toscano6da06ed2019-01-07 17:50:41 +010023.. _this spec: http://specs.openstack.org/openstack/qa-specs/specs/patrole/rbac-testing-multiple-policies.html
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -040024
25Scope
26-----
27
28Multiple policies should be applied only to tests that require them. Not all
29API endpoints enforce multiple policies. Some services consistently enforce
301 policy per API, while on the other side of the spectrum, services like
31Neutron have much more involved policy enforcement work flows. See
32:ref:`neutron-multi-policy-validation` for more information.
33
34.. _neutron-multi-policy-validation:
35
36Neutron Multi-policy Validation
37-------------------------------
38
39Neutron can raise different :ref:`policy-error-codes` following failed policy
40authorization. Many endpoints in Neutron enforce multiple policies, which
41complicates matters when trying to determine whether the endpoint raises a
42403 or a 404 following unauthorized access.
43
44Multi-policy Examples
45---------------------
46
47General Examples
48^^^^^^^^^^^^^^^^
49
50Below is an example of multi-policy validation for a carefully chosen Nova API:
51
52.. code-block:: python
53
54 @rbac_rule_validation.action(
55 service="nova",
56 rules=["os_compute_api:os-lock-server:unlock",
57 "os_compute_api:os-lock-server:unlock:unlock_override"])
58 @decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
59 def test_unlock_server_override(self):
60 """Test force unlock server, part of os-lock-server.
61
62 In order to trigger the unlock:unlock_override policy instead
63 of the unlock policy, the server must be locked by a different
64 user than the one who is attempting to unlock it.
65 """
66 self.os_admin.servers_client.lock_server(self.server['id'])
67 self.addCleanup(self.servers_client.unlock_server, self.server['id'])
68
Sergey Vilgelm78e7f572019-02-03 10:35:01 -060069 with self.override_role():
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -040070 self.servers_client.unlock_server(self.server['id'])
71
72While the ``expected_error_codes`` parameter is omitted in the example above,
73Patrole automatically populates it with a 403 for each policy in ``rules``.
74Therefore, in the example above, the following expected error codes/rules
75relationship is observed:
76
77* "os_compute_api:os-lock-server:unlock" => 403
78* "os_compute_api:os-lock-server:unlock:unlock_override" => 403
79
80Below is an example that uses ``expected_error_codes`` to account for the
81fact that Neutron is expected to raise a ``404`` on the first policy that
82is enforced server-side ("get_port"). Also, in this example, soft authorization
83is performed, meaning that it is necessary to check the response body for an
84attribute that is added only following successful policy authorization.
85
86.. code-block:: python
87
88 @utils.requires_ext(extension='binding', service='network')
89 @rbac_rule_validation.action(service="neutron",
90 rules=["get_port",
91 "get_port:binding:vif_type"],
92 expected_error_codes=[404, 403])
93 @decorators.idempotent_id('125aff0b-8fed-4f8e-8410-338616594b06')
94 def test_show_port_binding_vif_type(self):
95
96 # Verify specific fields of a port
97 fields = ['binding:vif_type']
98
Sergey Vilgelm78e7f572019-02-03 10:35:01 -060099 with self.override_role():
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -0400100 retrieved_port = self.ports_client.show_port(
101 self.port['id'], fields=fields)['port']
102
103 # Rather than throwing a 403, the field is not present, so raise exc.
104 if fields[0] not in retrieved_port:
105 raise rbac_exceptions.RbacMalformedResponse(
106 attribute='binding:vif_type')
107
108Note that in the example above, failure to authorize
109"get_port:binding:vif_type" results in the response body getting successfully
110returned by the server, but without additional dictionary keys. If Patrole
111fails to find those expected keys, it *acts as though* a 403 was thrown (by
112raising an exception itself, the ``rbac_rule_validation`` decorator handles
113the rest).
114
115Neutron Examples
116^^^^^^^^^^^^^^^^
117
118A basic Neutron example that only expects 403's to be raised:
119
120.. code-block:: python
121
122 @utils.requires_ext(extension='external-net', service='network')
123 @rbac_rule_validation.action(service="neutron",
124 rules=["create_network",
125 "create_network:router:external"],
126 expected_error_codes=[403, 403])
127 @decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
128 def test_create_network_router_external(self):
129
130 """Create External Router Network Test
131
132 RBAC test for the neutron create_network:router:external policy
133 """
Sergey Vilgelm78e7f572019-02-03 10:35:01 -0600134 with self.override_role():
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -0400135 self._create_network(router_external=True)
136
137Note that above the following expected error codes/rules relationship is
138observed:
139
140* "create_network" => 403
141* "create_network:router:external" => 403
142
143A more involved example that expects a 404 to be raised, should the first
144policy under ``rules`` fail authorization, and a 403 to be raised for any
145subsequent policy authorization failure:
146
147.. code-block:: python
148
149 @rbac_rule_validation.action(service="neutron",
150 rules=["get_network",
151 "update_network",
152 "update_network:shared"],
153 expected_error_codes=[404, 403, 403])
154 @decorators.idempotent_id('37ea3e33-47d9-49fc-9bba-1af98fbd46d6')
155 def test_update_network_shared(self):
156
157 """Update Shared Network Test
158
159 RBAC test for the neutron update_network:shared policy
160 """
Sergey Vilgelm78e7f572019-02-03 10:35:01 -0600161 with self.override_role():
Felipe Monteiro42f7e1c2018-06-10 20:09:26 -0400162 self._update_network(shared_network=True)
163 self.addCleanup(self._update_network, shared_network=False)
164
165Note that above the following expected error codes/rules relationship is
166observed:
167
168* "get_network" => 404
169* "update_network" => 403
170* "update_network:shared" => 403
171
172Limitations
173-----------
174
175Multi-policy validation in RBAC tests comes with limitations, due to technical
176and practical challenges.
177
178Technically, there are challenges associated with multiple policies across
179cross-service API communication in OpenStack, such as between Nova and Cinder
180or Nova and Neutron. The current framework does not account for these
181cross-service policy enforcement workflows, and it is still up for debate
182whether it should.
183
184Practically, it is not possible to enumerate every policy enforced by every API
185in Patrole, as the maintenance overhead would be huge.
186
187.. _Neutron policy documentation: https://docs.openstack.org/neutron/pike/contributor/internals/policy.html