Merge "Increase test stability of backup RBAC tests"
diff --git a/README.rst b/README.rst
index e9c03c8..2028536 100644
--- a/README.rst
+++ b/README.rst
@@ -31,6 +31,8 @@
 * Bugs: https://bugs.launchpad.net/patrole
 * Release notes: https://docs.openstack.org/releasenotes/patrole/
 
+.. _design-principles:
+
 Design Principles
 -----------------
 
@@ -59,6 +61,9 @@
     Realistically this is not always possible because some services have
     not yet moved to policy in code.
 
+* *Customizable*. Patrole should be able to validate custom policy overrides to
+  ensure that those overrides enhance rather than undermine the cloud's RBAC
+  configuration. In addition, Patrole should be able to validate any role.
 * *Self-cleaning*. Patrole should attempt to clean up after itself; whenever
   possible we should tear down resources when done.
 
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 560857e..2dbf63b 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -10,6 +10,14 @@
 
    overview
 
+RBAC Overview
+-------------
+
+.. toctree::
+   :maxdepth: 2
+
+   rbac-overview
+
 User's Guide
 ============
 
diff --git a/doc/source/rbac-overview.rst b/doc/source/rbac-overview.rst
new file mode 100644
index 0000000..5eefa5c
--- /dev/null
+++ b/doc/source/rbac-overview.rst
@@ -0,0 +1,254 @@
+==================================
+Role-Based Access Control Overview
+==================================
+
+Introduction
+------------
+
+Role-Based Access Control (RBAC) is used by most OpenStack services to control
+user access to resources. Authorization is granted if a user has the necessary
+role to perform an action. Patrole is concerned with validating that each of
+these resources *can* be accessed by authorized users and *cannot* be accessed
+by unauthorized users.
+
+OpenStack services use `oslo.policy`_ as the library for RBAC authorization.
+Patrole relies on the same library for deriving expected test results.
+
+.. _policy-in-code:
+
+Policy in Code
+--------------
+
+Services publish their policy-to-API mapping via policy in code documentation.
+This mapping includes the list of APIs that authorize a policy, for each
+policy declared within a service.
+
+For example, Nova's policy in code documentation is located in the
+`Nova repository`_ under ``nova/policies``. Likewise, Keystone's policy in
+code documentation is located in the `Keystone repository`_ under
+``keystone/common/policies``. The other OpenStack services follow the same
+directory layout pattern with respect to policy in code.
+
+The policy in code `governance goal`_ enumerates many advantages with following
+this RBAC design approach. A so-called library of in-code policies offers the
+following advantages, with respect to facilitating validation:
+
+* includes every policy enforced by an OpenStack service, enabling the
+  possibility of complete Patrole test coverage for that service (otherwise
+  one has to read the source code to discover all the policies)
+* provides the policy-to-API mapping for each policy which can be used
+  to write correct Patrole tests (otherwise reading source code and
+  experimentation are required to derive this mapping)
+* by extension, the policy-to-API mapping facilitates writing multi-policy
+  Patrole tests (otherwise even more experimentation and code reading is
+  required to arrive at all the policies enforced by an API)
+* policy in code documentation includes additional information, like
+  descriptions and (in the case of some services, like Keystone)
+  `scope types`_, which help with understanding how to correctly write
+  Patrole tests
+* by extension, such information helps to determine whether a Patrole test
+  should assume :term:`hard authorization` or :term:`soft authorization`
+
+Policy in Code (Default) Validation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, Patrole validates default OpenStack policies. This is so that
+the out-of-the-box defaults are sanity-checked, to ensure that OpenStack
+services are secure, from an RBAC perspective, for each release.
+
+Patrole strives to validate RBAC by using the policy in code documentation,
+wherever possible.
+
+.. _custom-policies:
+
+Custom Policies
+---------------
+
+Operators can override policy in code defaults using `policy.yaml`_. While
+this allows operators to offer more fine-grained RBAC control to their tenants,
+it opens the door to misconfiguration and bugs. Patrole can be used to validate
+that custom policy overrides don't break anything and work as expected.
+
+Custom Policy Validation
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+While testing default policy behavior is a valid use case, oftentimes default
+policies are modified with custom overrides in production. OpenStack's
+`policy.yaml`_ documentation claims that "modifying policy can have unexpected
+side effects", which is why Patrole was created: to ensure that custom
+overrides allow the principle of least privilege to be tailor-made to exact
+specifications via policy overrides, without:
+
+* causing unintended side effects (breaking API endpoints, breaking
+  cross-service workflows, breaking the policy file itself); or
+* resulting in poor RBAC configuration, promoting security vulnerabilities
+
+This has implications on Patrole's :ref:`design-principles`: validating custom
+overrides requires the ability to handle arbitrary roles, which requires logic
+capable of dynamically determining expected test behavior. See
+:ref:`rbac-validation` for more details.
+
+Note that support for custom policies is limited. This is because custom
+policies can be arbitrarily complex, requiring that tests be very robust
+in order to handle all edge cases.
+
+.. _multiple-policies:
+
+Multiple Policies
+-----------------
+
+Behind the scenes, many APIs enforce multiple policies, for many reasons,
+including:
+
+* to control complex cross-service workflows;
+* to control whether a server is booted from an image or booted from a volume
+  (for example);
+* to control whether a response body should contain additional information
+  conditioned upon successful policy authorization.
+
+This makes `policy in code`_ especially important for policy validation: it
+is difficult to keep track of all the policies being enforced across all the
+individual APIs, without policy in code documentation.
+
+Multi-Policy Validation
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Patrole offers support for validating APIs that enforce multiple policies.
+Perhaps in an ideal world each API endpoint would enforce only one policy,
+but in reality some API endpoints enforce multiple policies. Thus, to offer
+accurate validation, Patrole handles multiple policies:
+
+* for services *with* policy in code documentation: this documentation
+  indicates that a single API endpoint enforces multiple policy actions.
+* for services *without* policy in code documentation: the API code clearly
+  shows multiple policy actions being validated. Note that in this case some
+  degree of log tracing is required by developers to confirm that the expected
+  policies are getting enforced, prior to the tests getting merged.
+
+.. todo::
+
+  Link to multi-policy validation documentation section once it has been
+  written.
+
+.. _error-codes:
+
+Error Codes
+-----------
+
+Most OpenStack services raise a ``403 Forbidden`` following failed
+:term:`hard authorization`. Neutron, however, can raise a ``404 NotFound``
+as well. See Neutron's `authorization policy enforcement`_ documentation
+for more details.
+
+Glossary
+--------
+
+The following nomenclature is used throughout Patrole documentation so it is
+important to understand what each term means in order to understand concepts
+related to RBAC in Patrole.
+
+.. glossary::
+
+  authorize
+
+    The act of ``oslo.policy`` determining whether a user can perform a
+    :term:`policy` given his or her :term:`role`.
+
+  enforce
+
+    See :term:`authorize`.
+
+  hard authorization
+
+    The `do_raise`_ flag controls whether policy authorization should result
+    in an exception getting raised or a boolean value getting returned.
+    Hard authorization results in an exception getting raised. Usually, this
+    results in a ``403 Forbidden`` getting returned for unauthorized requests.
+    (See :ref:`error-codes` for further details.)
+
+    Related term: :term:`soft authorization`.
+
+  oslo.policy
+
+    The OpenStack library providing support for RBAC policy enforcement across
+    all OpenStack services. See the `official documentation`_ for more
+    information.
+
+  policy
+
+    Defines an RBAC rule. Each policy is defined by a one-line statement in
+    the form "<target>" : "<rule>". For more information, reference OpenStack's
+    `policy documentation`_.
+
+  policy action
+
+    See :term:`policy target`.
+
+  policy file
+
+    Prior to `governance goal`_ used by all OpenStack services to define
+    policy defaults. Still used by some services, which is why Patrole
+    needs to read the policy files to derive policy information for testing.
+
+  policy in code
+
+    Registers default OpenStack policies for a service in the service's code
+    base.
+
+    Beginning with the Queens release, policy in code became a
+    `governance goal`_.
+
+  policy rule
+
+    The policy rule determines under which circumstances the API call is
+    permitted.
+
+  policy target
+
+    The name of a policy.
+
+  requirements file
+
+    Requirements-driven approach to declaring the expected RBAC test results
+    referenced by Patrole. Uses a high-level YAML syntax to crystallize policy
+    requirements concisely and unambiguously. See :ref:`requirements-authority`
+    for more information.
+
+  role
+
+    A designation for the set of actions that describe what a user can do in
+    the system. Roles are managed through the `Keystone Roles API`_.
+
+  Role-Based Access Control (RBAC)
+
+    May be formally defined as "an approach to restricting system access to
+    authorized users."
+
+  rule
+
+    See :term:`policy rule`. Note that currently the Patrole code base
+    conflates "rule" with :term:`policy target` in some places.
+
+  soft authorization
+
+    The `do_raise`_ flag controls whether policy authorization should result
+    in an exception getting raised or a boolean value getting returned.
+    Soft authorization results in a boolean value getting returned. When policy
+    authorization evaluates to true, additional operations are performed as a
+    part of the API request or additional information is included in the
+    response body (see `response filtering`_ for an example).
+
+    Related term: :term:`hard authorization`.
+
+.. _Nova repository: https://github.com/openstack/nova/tree/master/nova/policies
+.. _Keystone repository: https://github.com/openstack/keystone/tree/master/keystone/common/policies
+.. _governance goal: https://governance.openstack.org/tc/goals/queens/policy-in-code.html
+.. _scope types: https://docs.openstack.org/keystone/latest/admin/identity-tokens.html#authorization-scopes
+.. _policy.yaml: https://docs.openstack.org/ocata/config-reference/policy-yaml-file.html
+.. _oslo.policy: https://docs.openstack.org/oslo.policy/latest/
+.. _policy documentation: https://docs.openstack.org/kilo/config-reference/content/policy-json-file.html
+.. _do_raise: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#oslo_policy.policy.Enforcer.enforce
+.. _authorization policy enforcement: https://docs.openstack.org/neutron/latest/contributor/internals/policy.html
+.. _official documentation: https://docs.openstack.org/oslo.policy/latest/
+.. _Keystone Roles API: https://developer.openstack.org/api-ref/identity/v3/#roles
+.. _response filtering: https://docs.openstack.org/neutron/latest/contributor/internals/policy.html#response-filtering
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index 1a0e186..5506d90 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -19,6 +19,7 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
 
 from patrole_tempest_plugin import rbac_exceptions
 from patrole_tempest_plugin import rbac_rule_validation
@@ -66,6 +67,9 @@
                         shared_network=None,
                         router_external=None,
                         router_private=None,
+                        provider_network_type=None,
+                        provider_physical_network=None,
+                        provider_segmentation_id=None,
                         segments=None,
                         **kwargs):
         if not net_id:
@@ -73,13 +77,19 @@
 
         if admin is not None:
             kwargs['admin_state_up'] = admin
-        elif shared_network is not None:
+        if shared_network is not None:
             kwargs['shared'] = shared_network
-        elif router_external is not None:
+        if router_external is not None:
             kwargs['router:external'] = router_external
-        elif router_private is not None:
+        if router_private is not None:
             kwargs['router:private'] = router_private
-        elif segments is not None:
+        if provider_network_type is not None:
+            kwargs['provider:network_type'] = provider_network_type
+        if provider_physical_network is not None:
+            kwargs['provider:physical_network'] = provider_physical_network
+        if provider_segmentation_id is not None:
+            kwargs['provider:segmentation_id'] = provider_segmentation_id
+        if segments is not None:
             kwargs['segments'] = segments
 
         updated_network = self.networks_client.update_network(
@@ -209,6 +219,81 @@
         with self.rbac_utils.override_role(self):
             self._update_network(net_id=network['id'], router_external=True)
 
+    @utils.requires_ext(extension='provider', service='network')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["get_network",
+               "update_network",
+               "update_network:provider:network_type"],
+        expected_error_codes=[404, 403, 403])
+    @decorators.idempotent_id('d064ef96-662b-47b6-94b7-9106dcd7ba8c')
+    def test_update_network_provider_network_type(self):
+
+        """Update Network Provider Network Type Test
+
+        RBAC test for neutron update_network:provider:network_type policy
+        """
+        try:
+            with self.rbac_utils.override_role(self):
+                self._update_network(self.network['id'],
+                                     provider_network_type='vxlan')
+        except lib_exc.BadRequest as exc:
+            # Per the api documentation, most plugins don't support updating
+            # provider attributes
+            self.assertIn(
+                "Plugin does not support updating provider attributes",
+                str(exc))
+
+    @utils.requires_ext(extension='provider', service='network')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["get_network",
+               "update_network",
+               "update_network:provider:physical_network"],
+        expected_error_codes=[404, 403, 403])
+    @decorators.idempotent_id('e3a55660-f75c-494e-a1b1-a8b36cc789ef')
+    def test_update_network_provider_physical_network(self):
+
+        """Update Network Provider Physical Network Test
+
+        RBAC test for neutron update_network:provider:physical_network policy
+        """
+        try:
+            with self.rbac_utils.override_role(self):
+                self._update_network(self.network['id'],
+                                     provider_physical_network='provider')
+        except lib_exc.BadRequest as exc:
+            # Per the api documenation, most plugins don't support updating
+            # provider attributes
+            self.assertIn(
+                "Plugin does not support updating provider attributes",
+                str(exc))
+
+    @utils.requires_ext(extension='provider', service='network')
+    @rbac_rule_validation.action(
+        service="neutron",
+        rules=["get_network",
+               "update_network",
+               "update_network:provider:segmentation_id"],
+        expected_error_codes=[404, 403, 403])
+    @decorators.idempotent_id('f6164228-b670-45fd-9ff9-b101930318c7')
+    def test_update_network_provider_segmentation_id(self):
+
+        """Update Network Provider Segmentation Id Test
+
+        RBAC test for neutron update_network:provider:segmentation_id policy
+        """
+        try:
+            with self.rbac_utils.override_role(self):
+                self._update_network(self.network['id'],
+                                     provider_segmentation_id=400)
+        except lib_exc.BadRequest as exc:
+            # Per the api documenation, most plugins don't support updating
+            # provider attributes
+            self.assertIn(
+                "Plugin does not support updating provider attributes",
+                str(exc))
+
     @rbac_rule_validation.action(service="neutron",
                                  rule="get_network",
                                  expected_error_code=404)