From b3b41d4141a4273826fdcdd9b6636155c96f91b1 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 24 Aug 2015 07:04:59 -0700 Subject: [PATCH] Devref for authorization policies enforcement This patch adds a policy.rst document which describes how the neutron.policy module works, how it uses oslo_policy and how it's wired in API controllers. The patch also remove an obsolete docstring from neutron/policy.py Change-Id: I3353a23f4e97824a5a91eaedf57a91ace031a9de --- doc/source/devref/index.rst | 1 + doc/source/devref/policy.rst | 292 +++++++++++++++++++++++++++++++++++ neutron/policy.py | 4 - 3 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 doc/source/devref/policy.rst diff --git a/doc/source/devref/index.rst b/doc/source/devref/index.rst index 694f0f07e..b74349aad 100644 --- a/doc/source/devref/index.rst +++ b/doc/source/devref/index.rst @@ -58,6 +58,7 @@ Neutron Internals api_extensions plugin-api db_layer + policy rpc_api rpc_callbacks layer3 diff --git a/doc/source/devref/policy.rst b/doc/source/devref/policy.rst new file mode 100644 index 000000000..692191ec5 --- /dev/null +++ b/doc/source/devref/policy.rst @@ -0,0 +1,292 @@ +================================== +Authorization Policy Enforcement +================================== + +As most OpenStack projects, Neutron leverages oslo_policy [#]_. However, since +Neutron loves to be special and complicate every developer's life, it also +"augments" oslo_policy capabilities by: + + * A wrapper module with its own API: neutron.policy; + * The ability of adding fine-grained checks on attributes for resources in + request bodies; + * The ability of using the policy engine to filter out attributes in responses; + * Adding some custom rule checks beyond those defined in oslo_policy; + +This document discusses Neutron-specific aspects of policy enforcement, and in +particular how the enforcement logic is wired into API processing. +For any other information please refer to the developer documentation for +oslo_policy [#]_. + +Authorization workflow +----------------------- + +The Neutron API controllers perform policy checks in two phases during the +processing of an API request: + + * Request authorization, immediately before dispatching the request to the + plugin layer for ``POST``, ``PUT``, and ``DELETE``, and immediately after + returning from the plugin layer for ``GET`` requests; + * Response filtering, when building the response to be returned to the API + consumer. + +Request authorization +~~~~~~~~~~~~~~~~~~~~~~ + +The aim of this step is to authorize processing for a request or reject it +with an error status code. +This step uses the ``neutron.policy.enforce`` routine. This routine raises +``oslo_policy.PolicyNotAuthorized`` when policy enforcement fails. The Neutron +REST API controllers catch this exception and return: + + * A 403 response code on a ``POST`` request or an ``PUT`` request for an + object owned by the tenant submitting the request; + * A 403 response for failures while authorizing API actions such as + ``add_router_interface``; + * A 404 response for ``DELETE``, ``GET`` and all other ``PUT`` requests. + +For ``DELETE`` operations the resource must first be fetched. This is done +invoking the same ``_item`` [#]_ method used for processing ``GET`` requests. +This is also true for ``PUT`` operations, since the Neutron API implements +``PATCH`` semantics for ``PUTs``. +The criteria to evaluate are built in the ``_build_match_rule`` [#]_ routine. +This routine takes in input the following parameters: + + * The action to be performed, in the ``_`` form, + ``e.g.: create_network`` + * The data to use for performing checks. For ``POST`` operations this could + be a partial specification of the object, whereas it is always a full + specification for ``GET``, ``PUT``, and ``DELETE`` requests, as resource + data are retrieved before dispatching the call to the plugin layer. + * The collection name for the resource specified in the previous parameter; + for instance, for a network it would be the "networks". + +The ``_build_match_rule`` routine returns a ``oslo_policy.RuleCheck`` instance +built in the following way: + + * Always add a check for the action being performed. This will match + a policy like create_network in ``policy.json``; + * Return for ``GET`` operations; more detailed checks will be performed anyway + when building the response; + * For each attribute which has been explicitly specified in the request + create a rule matching policy names in the form + ``_:`` rule, and link it with the + previous rule with an 'And' relationship (using ``oslo_policy.AndCheck``); + this step will be performed only if the enforce_policy flag is set to + True in the resource attribute descriptor (usually found in a data + structure called ``RESOURCE_ATTRIBUTE_MAP``); + * If the attribute is a composite one then further rules will be created; + These will match policy names in the form ``_: + :``. An 'And' relationship will be used in this + case too. + +As all the rules to verify are linked by 'And' relationships, all the policy +checks should succeed in order for a request to be authorized. Rule +verification is performed by ``oslo_policy`` with no "customization" from the +Neutron side. + +Response Filtering +~~~~~~~~~~~~~~~~~~~ + +Some Neutron extensions, like the provider networks one, add some attribute +to resources which are however not meant to be consumed by all clients. This +might be because these attributes contain implementation details, or are +meant only to be used when exchanging information between services, such +as Nova and Neutron; + +For this reason the policy engine is invoked again when building API +responses. This is achieved by the ``_exclude_attributes_by_policy`` [#]_ +method in ``neutron.api.v2.base.Controller``; + +This method, for each attribute in the response returned by the plugin layer, +first checks if the ``is_visible`` flag is True. In that case it proceeds to +checking policies for the attribute; if the policy check fails the attribute +is added to a list of attributes that should be removed from the response +before returning it to the API client. + +The neutron.policy API +------------------------ + +The ``neutron.policy`` module exposes a simple API whose main goal if to allow the +REST API controllers to implement the authorization workflow discussed in this +document. It is a bad practice to call the policy engine from within the plugin +layer, as this would make request authorization dependent on configured +plugins, and therefore make API behaviour dependent on the plugin itself, which +defies Neutron tenet of being backend agnostic. + +The neutron.policy API exposes the following routines: + + * ``init`` + Initializes the policy engine loading rules from the json policy (files). + This method can safely be called several times. + * ``reset`` + Clears all the rules currently configured in the policy engine. It is + called in unit tests and at the end of the initialization of core API + router [#]_ in order to ensure rules are loaded after all the extensions + are loaded. + * ``refresh`` + Combines init and reset. Called when a SIGHUP signal is sent to an API + worker. + * ``set_rules`` + Explicitly set policy engine's rules. Used only in unit tests. + * ``check`` + Perform a check using the policy engine. Builds match rules as described + in this document, and then evaluates the resulting rule using oslo_policy's + policy engine. Returns True if the checks succeeds, false otherwise. + * ``enforce`` + Operates like the check routine but raises if the check in oslo_policy + fails. + * ``check_is_admin`` + Enforce the predefined context_is_admin rule; used to determine the is_admin + property for a neutron context. + * ``check_is_advsvc`` + Enforce the predefined context_is_advsvc rule; used to determine the + is_advsvc property for a neutron context. + +Neutron specific policy rules +------------------------------ + +Neutron provides two additional policy rule classes in order to support the +"augmented" authorization capabilities it provides. They both extend +``oslo_policy.RuleCheck`` and are registered using the +``oslo_policy.register`` decorator. + +OwnerCheck: Extended Checks for Resource Ownership +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This class is registered for rules matching the ``tenant_id`` keyword and +overrides the generic check performed by oslo_policy in this case. +It uses for those cases where neutron needs to check whether the tenant +submitting a request for a new resource owns the parent resource of the one +being created. Current usages of ``OwnerCheck`` include, for instance, +creating and updating a subnet. + +The check, performed in the ``__call__`` method, works as follows: + + * verify if the target field is already in the target data. If yes, then + simply verify whether the value for the target field in target data + is equal to value for the same field in credentials, just like + ``oslo_policy.GeneriCheck`` would do. This is also the most frequent case + as the target field is usually ``tenant_id``; + * if the previous check failed, extract a parent resource type and a + parent field name from the target field. For instance + ``networks:tenant_id`` identifies the ``tenant_id`` attribute of the + ``network`` resource; + * if no parent resource or target field could be identified raise a + ``PolicyCheckError`` exception; + * Retrieve a 'parent foreign key' from the ``RESOURCE_FOREIGN_KEYS`` data + structure in ``neutron.api.v2.attributes``. This foreign key is simply the + attribute acting as a primary key in the parent resource. A + ``PolicyCheckError`` exception will be raised if such 'parent foreign key' + cannot be retrieved; + * Using the core plugin, retrieve an instance of the resource having + 'parent foreign key' as an identifier; + * Finally, verify whether the target field in this resource matches the + one in the initial request data. For instance, for a port create request, + verify whether the ``tenant_id`` of the port data structure matches the + ``tenant_id`` of the network where this port is being created. + + +FieldCheck: Verify Resource Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This class is registered with the policy engine for rules matching the 'field' +keyword, and provides a way to perform fine grained checks on resource +attributes. For instance, using this class of rules it is possible to specify +a rule for granting every tenant read access to shared resources. + +In policy.json, a FieldCheck rules is specified in the following way:: + +> field: := + +This will result in the initialization of a FieldCheck that will check for +```` in the target resource data, and return ``True`` if it is equal +to ```` or return ``False`` is the ```` either is not equal to +```` or does not exist at all. + + +Guidance for API developers +---------------------------- + +When developing REST APIs for Neutron it is important to be aware of how the +policy engine will authorize these requests. This is true both for APIs +served by Neutron "core" and for the APIs served by the various Neutron +"stadium" services. + + * If an attribute of a resource might be subject to authorization checks + then the ``enforce_policy`` attribute should be set to ``True``. While + setting this flag to ``True`` for each attribute is a viable strategy, + it is worth noting that this will require a call to the policy engine + for each attribute, thus consistently increasing the time required to + complete policy checks for a resource. This could result in a scalability + issue, especially in the case of list operations retrieving a large + number of resources; + * Some resource attributes, even if not directly used in policy checks + might still be required by the policy engine. This is for instance the + case of the ``tenant_id`` attribute. For these attributes the + ``required_by_policy`` attribute should always set to ``True``. This will + ensure that the attribute is included in the resource data sent to the + policy engine for evaluation; + * The ``tenant_id`` attribute is a fundamental one in Neutron API request + authorization. The default policy, ``admin_or_owner``, uses it to validate + if a tenant owns the resource it is trying to operate on. To this aim, + if a resource without a tenant_id is created, it is important to ensure + that ad-hoc authZ policies are specified for this resource. + * There is still only one check which is hardcoded in Neutron's API layer: + the check to verify that a tenant owns the network on which it is creating + a port. This check is hardcoded and is always executed when creating a + port, unless the network is shared. Unfortunatelu a solution for performing + this check in an efficient way through the policy engine has not yet been + found. Due to its nature, there is no way to override this check using the + policy engine. + * It is strongly advised to not perform policy checks in the plugin or in + the database management classes. This might lead to divergent API + behaviours across plugins. Also, it might leave the Neutron DB in an + inconsistent state if a request is not authorized after it has already + been dispatched to the backend. + + +Notes +----------------------- + + * No authorization checks are performed for requests coming from the RPC over + AMQP channel. For all these requests a neutron admin context is built, and + the plugins will process them as such. + * For ``PUT`` and ``DELETE`` requests a 404 error is returned on request + authorization failures rather than a 403, unless the tenant submitting the + request own the resource to update or delete. This is to avoid conditions + in which an API client might try and find out other tenants' resource + identifiers by sending out ``PUT`` and ``DELETE`` requests for random + resource identifiers. + * There is no way at the moment to specify an ``OR`` relationship between two + attributes of a given resource (eg.: ``port.name == 'meh' or + port.status == 'DOWN'``), unless the rule with the or condition is explicitly + added to the policy.json file. + * ``OwnerCheck`` performs a plugin access; this will likely require a database + access, but since the behaviour is implementation specific it might also + imply a round-trip to the backend. This class of checks, when involving + retrieving attributes for 'parent' resources should be used very sparingly. + * In order for ``OwnerCheck`` rules to work, parent resources should have an + entry in ``neutron.api.v2.attributes.RESOURCE_FOREIGN_KEYS``; moreover the + resource must be managed by the 'core' plugin (ie: the one defined in the + core_plugin configuration variable) + +References +---------- + +.. [#] `Oslo policy module `_ +.. [#] `Oslo policy developer `_ +.. [#] API controller item_ method + +.. _item: http://git.openstack.org/cgit/openstack/neutron/tree/neutron/api/v2/base.py?id=2015.1.1#n282 + +.. [#] Policy engine's build_match_rule_ method + +.. _build_match_rule: http://git.openstack.org/cgit/openstack/neutron/tree/neutron/policy.py?id=2015.1.1#n187 + +.. [#] exclude_attributes_by_policy_ method + +.. _exclude_attributes_by_policy: http://git.openstack.org/cgit/openstack/neutron/tree/neutron/api/v2/base.py?id=2015.1.1#n132 + +.. [#] Policy reset_ in neutron.api.v2.router + +.. _reset: http://git.openstack.org/cgit/openstack/neutron/tree/neutron/api/v2/router.py?id=2015.1.1#n122 diff --git a/neutron/policy.py b/neutron/policy.py index e1d955a60..d0064b432 100644 --- a/neutron/policy.py +++ b/neutron/policy.py @@ -13,10 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Policy engine for neutron. Largely copied from nova. -""" - import collections import re -- 2.45.2