Add ext_parent policy check

Add common parent owner check for the resources which introduced by
service plugin.

Then port forwarding resource will share the same tenant_id with
floatingip. That means only the fip owner can create/update/get/delete
the associated port forwarding resource.

Partially-Implements: blueprint port-forwarding
Partial-Bug: #1491317
Change-Id: I450c674e55ca15e1d9a6a6224138f3305427da68
This commit is contained in:
ZhaoBo 2018-07-25 20:07:18 +08:00
parent 4088461ed6
commit 35d945e92f
8 changed files with 136 additions and 20 deletions

View File

@ -180,7 +180,13 @@ overrides the generic check performed by oslo_policy in this case.
It uses for those cases where neutron needs to check whether the project It uses for those cases where neutron needs to check whether the project
submitting a request for a new resource owns the parent resource of the one submitting a request for a new resource owns the parent resource of the one
being created. Current usages of ``OwnerCheck`` include, for instance, being created. Current usages of ``OwnerCheck`` include, for instance,
creating and updating a subnet. creating and updating a subnet. This class supports the extension parent
resources owner check which the parent resource introduced by
service plugins. Such as router and floatingip owner check for ``router``
service plugin. Developers can register the extension resource name and service
plugin name which were registered in neutron-lib into
``EXT_PARENT_RESOURCE_MAPPING`` which is located in
``neutron.common.constants``.
The check, performed in the ``__call__`` method, works as follows: The check, performed in the ``__call__`` method, works as follows:
@ -192,7 +198,9 @@ The check, performed in the ``__call__`` method, works as follows:
* if the previous check failed, extract a parent resource type and a * if the previous check failed, extract a parent resource type and a
parent field name from the target field. For instance parent field name from the target field. For instance
``networks:tenant_id`` identifies the ``tenant_id`` attribute of the ``networks:tenant_id`` identifies the ``tenant_id`` attribute of the
``network`` resource; ``network`` resource. For extension parent resource case,
``ext_parent:tenant_id`` identifies the ``tenant_id`` attribute of the
registered extension resource in ``EXT_PARENT_RESOURCE_MAPPING``;
* if no parent resource or target field could be identified raise a * if no parent resource or target field could be identified raise a
``PolicyCheckError`` exception; ``PolicyCheckError`` exception;
* Retrieve a 'parent foreign key' from the ``_RESOURCE_FOREIGN_KEYS`` data * Retrieve a 'parent foreign key' from the ``_RESOURCE_FOREIGN_KEYS`` data

View File

@ -13,6 +13,7 @@
"shared_address_scopes": "field:address_scopes:shared=True", "shared_address_scopes": "field:address_scopes:shared=True",
"external": "field:networks:router:external=True", "external": "field:networks:router:external=True",
"default": "rule:admin_or_owner", "default": "rule:admin_or_owner",
"admin_or_ext_parent_owner": "rule:context_is_admin or tenant_id:%(ext_parent:tenant_id)s",
"create_subnet": "rule:admin_or_network_owner", "create_subnet": "rule:admin_or_network_owner",
"create_subnet:segment_id": "rule:admin_only", "create_subnet:segment_id": "rule:admin_only",
@ -250,9 +251,9 @@
"update_log": "rule:admin_only", "update_log": "rule:admin_only",
"delete_log": "rule:admin_only", "delete_log": "rule:admin_only",
"create_floatingip_port_forwarding": "rule:regular_user", "create_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"get_floatingip_port_forwarding": "rule:regular_user", "get_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"get_floatingip_port_forwardings": "rule:regular_user", "get_floatingip_port_forwardings": "rule:admin_or_ext_parent_owner",
"update_floatingip_port_forwarding": "rule:regular_user", "update_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"delete_floatingip_port_forwarding": "rule:regular_user" "delete_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner"
} }

View File

@ -310,12 +310,15 @@ class Controller(object):
# FIXME(salvatore-orlando): obj_getter might return references to # FIXME(salvatore-orlando): obj_getter might return references to
# other resources. Must check authZ on them too. # other resources. Must check authZ on them too.
# Omit items from list that should not be visible # Omit items from list that should not be visible
obj_list = [obj for obj in obj_list tmp_list = []
if policy.check(request.context, for obj in obj_list:
self._plugin_handlers[self.SHOW], self._set_parent_id_into_ext_resources_request(
obj, request, obj, parent_id, is_get=True)
plugin=self._plugin, if policy.check(
pluralized=self._collection)] request.context, self._plugin_handlers[self.SHOW],
obj, plugin=self._plugin, pluralized=self._collection):
tmp_list.append(obj)
obj_list = tmp_list
# Use the first element in the list for discriminating which attributes # Use the first element in the list for discriminating which attributes
# should be filtered out because of authZ policies # should be filtered out because of authZ policies
# fields_to_add contains a list of attributes added for request policy # fields_to_add contains a list of attributes added for request policy
@ -346,6 +349,8 @@ class Controller(object):
kwargs[self._parent_id_name] = parent_id kwargs[self._parent_id_name] = parent_id
obj_getter = getattr(self._plugin, action) obj_getter = getattr(self._plugin, action)
obj = obj_getter(request.context, id, **kwargs) obj = obj_getter(request.context, id, **kwargs)
self._set_parent_id_into_ext_resources_request(
request, obj, parent_id, is_get=True)
# Check authz # Check authz
# FIXME(salvatore-orlando): obj_getter might return references to # FIXME(salvatore-orlando): obj_getter might return references to
# other resources. Must check authZ on them too. # other resources. Must check authZ on them too.
@ -457,6 +462,11 @@ class Controller(object):
for item in items: for item in items:
self._validate_network_tenant_ownership(request, self._validate_network_tenant_ownership(request,
item[self._resource]) item[self._resource])
# For ext resources policy check, we support two types, such as
# parent_id is in request body, another type is parent_id is in
# request url, which we can get from kwargs.
self._set_parent_id_into_ext_resources_request(
request, item[self._resource], parent_id)
policy.enforce(request.context, policy.enforce(request.context,
action, action,
item[self._resource], item[self._resource],
@ -632,6 +642,10 @@ class Controller(object):
# Ensure policy engine is initialized # Ensure policy engine is initialized
policy.init() policy.init()
parent_id = kwargs.get(self._parent_id_name) parent_id = kwargs.get(self._parent_id_name)
# If the parent_id exist, we should get orig_obj with
# self._parent_id_name field.
if parent_id and self._parent_id_name not in field_list:
field_list.append(self._parent_id_name)
orig_obj = self._item(request, id, field_list=field_list, orig_obj = self._item(request, id, field_list=field_list,
parent_id=parent_id) parent_id=parent_id)
orig_object_copy = copy.copy(orig_obj) orig_object_copy = copy.copy(orig_obj)
@ -640,6 +654,10 @@ class Controller(object):
# which attributes are set explicitly so that it can distinguish them # which attributes are set explicitly so that it can distinguish them
# from the ones that are set to their default values. # from the ones that are set to their default values.
orig_obj[n_const.ATTRIBUTES_TO_UPDATE] = body[self._resource].keys() orig_obj[n_const.ATTRIBUTES_TO_UPDATE] = body[self._resource].keys()
# Then get the ext_parent_id, format to ext_parent_parent_resource_id
if self._parent_id_name in orig_obj:
self._set_parent_id_into_ext_resources_request(
request, orig_obj, parent_id)
try: try:
policy.enforce(request.context, policy.enforce(request.context,
action, action,
@ -759,6 +777,37 @@ class Controller(object):
msg = _('The resource could not be found.') msg = _('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg) raise webob.exc.HTTPNotFound(msg)
def _set_parent_id_into_ext_resources_request(
self, request, resource_item, parent_id, is_get=False):
if not parent_id:
return
# This will pass most create/update/delete cases
if not is_get and (request.context.is_admin or
request.context.is_advsvc or
self.parent['member_name'] not in
n_const.EXT_PARENT_RESOURCE_MAPPING or
resource_item.get(self._parent_id_name)):
return
# Then we arrive here, that means the request or get obj contains
# ext_parent. If this func is called by list/get, and it contains
# _parent_id_name. We need to re-add the ex_parent prefix to policy.
if is_get:
if (not request.context.is_admin or
not request.context.is_advsvc and
self.parent['member_name'] in
n_const.EXT_PARENT_RESOURCE_MAPPING):
resource_item.setdefault(
"%s_%s" % (n_const.EXT_PARENT_PREFIX,
self._parent_id_name),
parent_id)
# If this func is called by create/update/delete, we just add.
else:
resource_item.setdefault(
"%s_%s" % (n_const.EXT_PARENT_PREFIX, self._parent_id_name),
parent_id)
def create_resource(collection, resource, plugin, params, allow_bulk=False, def create_resource(collection, resource, plugin, params, allow_bulk=False,
member_actions=None, parent=None, allow_pagination=False, member_actions=None, parent=None, allow_pagination=False,

View File

@ -13,8 +13,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from neutron_lib.api.definitions import l3
from neutron_lib import constants as lib_constants from neutron_lib import constants as lib_constants
from neutron_lib.plugins import constants as plugin_consts
ROUTER_PORT_OWNERS = lib_constants.ROUTER_INTERFACE_OWNERS_SNAT + \ ROUTER_PORT_OWNERS = lib_constants.ROUTER_INTERFACE_OWNERS_SNAT + \
(lib_constants.DEVICE_OWNER_ROUTER_GW,) (lib_constants.DEVICE_OWNER_ROUTER_GW,)
@ -230,3 +231,16 @@ IEC_BASE = 1024
# Port bindings handling # Port bindings handling
NO_ACTIVE_BINDING = 'no_active_binding' NO_ACTIVE_BINDING = 'no_active_binding'
# Registered extension parent resource check mapping
# If we want to register some service plugin resources into policy and check
# the owner when operating their subresources. We can write here to use
# existing policy engine for parent resource owner check.
# Each entry here should be PARENT_RESOURCE_NAME: SERVICE_PLUGIN_NAME,
# PARENT_RESOURCE_NAME is usually from api definition.
# SERVICE_PLUGIN_NAME is the service plugin which introduced the resource and
# registered the service plugin name in neutron-lib.
EXT_PARENT_RESOURCE_MAPPING = {
l3.FLOATINGIP: plugin_consts.L3
}
EXT_PARENT_PREFIX = 'ext_parent'

View File

@ -224,7 +224,11 @@ class OwnerCheck(policy.Check):
# resource is handled by the core plugin. It might be worth # resource is handled by the core plugin. It might be worth
# having a way to map resources to plugins so to make this # having a way to map resources to plugins so to make this
# check more general # check more general
f = getattr(directory.get_plugin(), 'get_%s' % resource_type) plugin = directory.get_plugin()
if resource_type in const.EXT_PARENT_RESOURCE_MAPPING:
plugin = directory.get_plugin(
const.EXT_PARENT_RESOURCE_MAPPING[resource_type])
f = getattr(plugin, 'get_%s' % resource_type)
# f *must* exist, if not found it is better to let neutron # f *must* exist, if not found it is better to let neutron
# explode. Check will be performed with admin context # explode. Check will be performed with admin context
try: try:
@ -272,6 +276,13 @@ class OwnerCheck(policy.Check):
reason=err_reason) reason=err_reason)
parent_foreign_key = _RESOURCE_FOREIGN_KEYS.get( parent_foreign_key = _RESOURCE_FOREIGN_KEYS.get(
"%ss" % parent_res, None) "%ss" % parent_res, None)
if parent_res == const.EXT_PARENT_PREFIX:
for resource in const.EXT_PARENT_RESOURCE_MAPPING:
key = "%s_%s_id" % (const.EXT_PARENT_PREFIX, resource)
if key in target:
parent_foreign_key = key
parent_res = resource
break
if not parent_foreign_key: if not parent_foreign_key:
err_reason = (_("Unable to verify match:%(match)s as the " err_reason = (_("Unable to verify match:%(match)s as the "
"parent resource: %(res)s was not found") % "parent resource: %(res)s was not found") %

View File

@ -13,6 +13,7 @@
"shared_address_scopes": "field:address_scopes:shared=True", "shared_address_scopes": "field:address_scopes:shared=True",
"external": "field:networks:router:external=True", "external": "field:networks:router:external=True",
"default": "rule:admin_or_owner", "default": "rule:admin_or_owner",
"admin_or_ext_parent_owner": "rule:context_is_admin or tenant_id:%(ext_parent:tenant_id)s",
"create_subnet": "rule:admin_or_network_owner", "create_subnet": "rule:admin_or_network_owner",
"create_subnet:segment_id": "rule:admin_only", "create_subnet:segment_id": "rule:admin_only",
@ -250,9 +251,9 @@
"update_log": "rule:admin_only", "update_log": "rule:admin_only",
"delete_log": "rule:admin_only", "delete_log": "rule:admin_only",
"create_floatingip_port_forwarding": "rule:regular_user", "create_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"get_floatingip_port_forwarding": "rule:regular_user", "get_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"get_floatingip_port_forwardings": "rule:regular_user", "get_floatingip_port_forwardings": "rule:admin_or_ext_parent_owner",
"update_floatingip_port_forwarding": "rule:regular_user", "update_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner",
"delete_floatingip_port_forwarding": "rule:regular_user" "delete_floatingip_port_forwarding": "rule:admin_or_ext_parent_owner"
} }

View File

@ -599,3 +599,26 @@ class NeutronPolicyTestCase(base.BaseTestCase):
result = policy._is_attribute_explicitly_set( result = policy._is_attribute_explicitly_set(
attr, resource, target, action) attr, resource, target, action)
self.assertFalse(result) self.assertFalse(result)
@mock.patch("neutron.common.constants.EXT_PARENT_RESOURCE_MAPPING",
{'parentresource': 'registered_plugin_name'})
@mock.patch("neutron_lib.plugins.directory.get_plugin")
def test_enforce_tenant_id_check_parent_resource_owner(
self, mock_get_plugin):
def fakegetparent(*args, **kwargs):
return {'tenant_id': 'fake'}
mock_plugin = mock.Mock()
mock_plugin.get_parentresource = fakegetparent
mock_get_plugin.return_value = mock_plugin
self._set_rules(
admin_or_ext_parent_owner="rule:context_is_admin or "
"tenant_id:%(ext_parent:tenant_id)s",
create_parentresource_subresource="rule:admin_or_ext_parent_owner")
self.fakepolicyinit()
action = 'create_parentresource_subresource'
target = {'ext_parent_parentresource_id': 'whatever', 'foo': 'bar'}
result = policy.enforce(self.context, action, target)
mock_get_plugin.assert_called_with('registered_plugin_name')
self.assertTrue(result)

View File

@ -0,0 +1,9 @@
---
features:
- |
Introduces extension parent resources owner check in
``neutron.policy.OwnerCheck``. It can be used by registering an extension
parent resource and service plugin which introduced the corresponding
parent resource into ``EXT_PARENT_RESOURCE_MAPPING`` located in
``neutron.common.constants``. And introduces a new policy role
``admin_or_ext_parent_owner`` into ``policy.json`` for this function.