Merge "Replace "target_tenant" with "target_project" in RBAC OVOs and models"

This commit is contained in:
Zuul 2021-12-08 13:34:29 +00:00 committed by Gerrit Code Review
commit 74e2956aa3
25 changed files with 312 additions and 124 deletions

View File

@ -621,7 +621,7 @@ Use the :command:`openstack network rbac show` command to see the details:
The output shows that the entry allows the action ``access_as_shared``
on object ``84a7e627-573b-49da-af66-c9a65244f3ce`` of type ``network``
to target_tenant ``*``, which is a wildcard that represents all projects.
to target_project ``*``, which is a wildcard that represents all projects.
Currently, the ``shared`` flag is just a mapping to the underlying
RBAC policies for a network. Setting the flag to ``True`` on a network

View File

@ -25,12 +25,14 @@ RESOURCE_PATH = '/rbac-policies/{id}'
rules = [
# TODO(ralonsoh): remove 'target_tenant=*' reference.
policy.RuleDefault(
name='restrict_wildcard',
check_str=base.policy_or(
'(not field:rbac_policy:target_tenant=*)',
'(not field:rbac_policy:target_tenant=* and '
'not field:rbac_policy:target_project=*)',
base.RULE_ADMIN_ONLY),
description='Definition of a wildcard target_tenant'),
description='Definition of a wildcard target_project'),
policy.DocumentedRuleDefault(
name='create_rbac_policy',
@ -49,11 +51,14 @@ rules = [
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.WALLABY)
),
# TODO(ralonsoh): change name to 'create_rbac_policy:target_project'
# and remove 'target_tenant=*' reference.
policy.DocumentedRuleDefault(
name='create_rbac_policy:target_tenant',
check_str=base.policy_or(
base.SYSTEM_ADMIN,
'(not field:rbac_policy:target_tenant=*)'),
'(not field:rbac_policy:target_tenant=* and '
'not field:rbac_policy:target_project=*)'),
description='Specify ``target_tenant`` when creating an RBAC policy',
operations=[
{
@ -85,11 +90,14 @@ rules = [
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.WALLABY)
),
# TODO(ralonsoh): change name to 'create_rbac_policy:target_project'
# and remove 'target_tenant=*' reference.
policy.DocumentedRuleDefault(
name='update_rbac_policy:target_tenant',
check_str=base.policy_or(
base.SYSTEM_ADMIN,
'(not field:rbac_policy:target_tenant=*)'),
'(not field:rbac_policy:target_tenant=* and '
'not field:rbac_policy:target_project=*)'),
description='Update ``target_tenant`` attribute of an RBAC policy',
operations=[
{

View File

@ -345,7 +345,7 @@ class DbBasePluginCommon(object):
matches = ('*',) + ((context.tenant_id,) if context else ())
for entry in rbac_entries:
if (entry.action == 'access_as_shared' and
entry.target_tenant in matches):
entry.target_project in matches):
return True
return False

View File

@ -218,16 +218,16 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
raise exc.InvalidInput(error_message=msg)
tenant_to_check = None
self_sharing = policy['target_tenant'] == net['tenant_id']
self_sharing = policy['target_project'] == net['tenant_id']
if self_sharing:
return
if event == events.BEFORE_UPDATE:
new_tenant = payload.request_body['target_tenant']
if policy['target_tenant'] != new_tenant:
tenant_to_check = policy['target_tenant']
new_tenant = payload.request_body['target_project']
if policy['target_project'] != new_tenant:
tenant_to_check = policy['target_project']
if event == events.BEFORE_DELETE:
tenant_to_check = policy['target_tenant']
tenant_to_check = policy['target_project']
if tenant_to_check:
self.ensure_no_tenant_ports_on_network(net['id'], net['tenant_id'],
@ -245,9 +245,9 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# the same tenant as the network owner is ok
other_rbac_objs = network_obj.NetworkRBAC.get_objects(
ctx_admin, object_id=network_id, action='access_as_shared')
allowed_tenants = [rbac['target_tenant'] for rbac
allowed_tenants = [rbac['target_project'] for rbac
in other_rbac_objs
if rbac.target_tenant != tenant_id]
if rbac.target_project != tenant_id]
allowed_tenants.append(net_tenant_id)
ports = ports.filter(
~models_v2.Port.tenant_id.in_(allowed_tenants))
@ -256,7 +256,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# allows any ports
if network_obj.NetworkRBAC.get_object(
ctx_admin, object_id=network_id, action='access_as_shared',
target_tenant='*'):
target_project='*'):
return
ports = ports.filter(models_v2.Port.project_id == tenant_id)
if ports.count():
@ -303,8 +303,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
ctx_admin = ctx.get_admin_context()
other_rbac_objs = network_obj.NetworkRBAC.get_objects(
ctx_admin, object_id=network.id, action='access_as_shared')
allowed_projects = {rbac['target_tenant'] for rbac in other_rbac_objs
if rbac.target_tenant != '*'}
allowed_projects = {rbac['target_project'] for rbac in other_rbac_objs
if rbac.target_project != '*'}
allowed_projects.add(network.project_id)
if project_ids - allowed_projects:
raise exc.InvalidSharedSetting(network=network.name)
@ -417,7 +417,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
np_rbac_args = {'project_id': network.project_id,
'object_id': network.id,
'action': 'access_as_shared',
'target_tenant': '*'}
'target_project': '*'}
np_rbac_obj = network_obj.NetworkRBAC(context, **np_rbac_args)
np_rbac_obj.create()
return network
@ -434,7 +434,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
entry = None
for item in network.rbac_entries:
if (item.action == 'access_as_shared' and
item.target_tenant == '*'):
item.target_project == '*'):
entry = item
break
setattr(network, 'shared', bool(entry))
@ -444,14 +444,14 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
np_rbac_args = {'project_id': network.project_id,
'object_id': network.id,
'action': 'access_as_shared',
'target_tenant': '*'}
'target_project': '*'}
np_rbac_obj = network_obj.NetworkRBAC(context,
**np_rbac_args)
np_rbac_obj.create()
elif not update_shared and entry:
network_obj.NetworkRBAC.delete_objects(
context, object_id=network.id,
action='access_as_shared', target_tenant='*')
action='access_as_shared', target_project='*')
# TODO(ihrachys) Below can be removed when we make sqlalchemy
# event listeners in neutron_lib/db/api.py to refresh expired

View File

@ -47,8 +47,8 @@ def _network_filter_hook(context, original_model, conditions):
rbac_model = original_model.rbac_entries.property.mapper.class_
tenant_allowed = (
(rbac_model.action == 'access_as_external') &
(rbac_model.target_tenant == context.tenant_id) |
(rbac_model.target_tenant == '*'))
(rbac_model.target_project == context.tenant_id) |
(rbac_model.target_project == '*'))
conditions = expr.or_(tenant_allowed, *conditions)
return conditions
@ -100,7 +100,7 @@ class External_net_db_mixin(object):
net_rbac_args = {'project_id': net_data['tenant_id'],
'object_id': net_data['id'],
'action': 'access_as_external',
'target_tenant': '*'}
'target_project': '*'}
net_obj.NetworkRBAC(context, **net_rbac_args).create()
net_data[extnet_apidef.EXTERNAL] = external
@ -121,7 +121,7 @@ class External_net_db_mixin(object):
net_rbac_args = {'project_id': net_data['tenant_id'],
'object_id': net_id,
'action': 'access_as_external',
'target_tenant': '*'}
'target_project': '*'}
net_obj.NetworkRBAC(context, **net_rbac_args).create()
else:
# must make sure we do not have any external gateway ports
@ -196,24 +196,24 @@ class External_net_db_mixin(object):
return
new_project = None
if event == events.BEFORE_UPDATE:
new_project = payload.request_body['target_tenant']
if new_project == policy['target_tenant']:
new_project = payload.request_body['target_project']
if new_project == policy['target_project']:
# nothing to validate if the tenant didn't change
return
gw_ports = context.session.query(models_v2.Port.id).filter_by(
device_owner=constants.DEVICE_OWNER_ROUTER_GW,
network_id=policy['object_id'])
gw_ports = [gw_port[0] for gw_port in gw_ports]
if policy['target_tenant'] != '*':
if policy['target_project'] != '*':
filters = {
'gw_port_id': gw_ports,
'project_id': policy['target_tenant']
'project_id': policy['target_project']
}
# if there is a wildcard entry we can safely proceed without the
# router lookup because they will have access either way
if net_obj.NetworkRBAC.count(
context, object_id=policy['object_id'],
action='access_as_external', target_tenant='*'):
action='access_as_external', target_project='*'):
return
router_exist = l3_obj.Router.objects_exist(context, **filters)
else:

View File

@ -1 +1 @@
76df7844a8c6
1ffef8d6f371

View File

@ -0,0 +1,130 @@
# Copyright 2021 Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""migrate RBAC registers from "target_tenant" to "target_project"
Revision ID: 1ffef8d6f371
Revises: 76df7844a8c6
Create Date: 2021-10-28 14:10:20.097125
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '1ffef8d6f371'
down_revision = '76df7844a8c6'
_INSPECTOR = None
TABLES = ['networkrbacs', 'qospolicyrbacs', 'securitygrouprbacs',
'addressscoperbacs', 'subnetpoolrbacs', 'addressgrouprbacs']
DROPPED_UNIQUE_CONSTRAINTS = [
'uniq_networkrbacs0tenant_target0object_id0action',
'qospolicyrbacs_target_tenant_object_id_action_key', # PSQL
'target_tenant', # MySQL, name provided by mistake
'uniq_securitygrouprbacs0target_tenant0object_id0action',
'uniq_address_scopes_rbacs0target_tenant0object_id0action',
'uniq_subnetpools_rbacs0target_tenant0object_id0action',
'uniq_address_groups_rbacs0target_tenant0object_id0action']
def get_inspector():
global _INSPECTOR
if _INSPECTOR:
return _INSPECTOR
else:
bind = op.get_bind()
_INSPECTOR = sa.engine.reflection.Inspector.from_engine(bind)
return _INSPECTOR
def get_columns(table):
inspector = get_inspector()
return inspector.get_columns(table)
def get_data():
output = []
for table in TABLES:
for column in get_columns(table):
if column['name'] == 'target_tenant':
output.append((table, column))
return output
def delete_unique_constraint(table):
inspector = get_inspector()
unique_constraints = inspector.get_unique_constraints(table)
for constraint in unique_constraints:
op.drop_constraint(
constraint_name=constraint['name'],
table_name=table,
type_='unique')
def add_unique_constraint(table):
op.create_unique_constraint(
constraint_name='uniq_%s0target_project0object_id0action' % table,
table_name=table, columns=['target_project', 'object_id', 'action'])
def alter_column(table, column):
op.alter_column(
table_name=table,
column_name='target_tenant',
new_column_name='target_project',
existing_type=sa.String(length=255),
existing_nullable=column['nullable'])
def recreate_index(table):
inspector = get_inspector()
indexes = inspector.get_indexes(table)
index_name = 'ix_' + table + '_target_tenant'
for idx in (idx for idx in indexes if idx['name'] == index_name):
old_name = idx['name']
new_name = old_name.replace('target_tenant', 'target_project')
op.drop_index(op.f(old_name), table)
op.create_index(new_name, table, ['target_project'])
def upgrade():
for table, column in get_data():
delete_unique_constraint(table)
alter_column(table, column)
recreate_index(table)
add_unique_constraint(table)
def expand_drop_exceptions():
"""Drop the unique constraints and "*_target_tenant" keys
In order to rename the ``TABLES`` column name from "target_tenant" to
"target_project", it is needed to drop any constraint related to this
column. For all tables in ``TABLES``, this migration will drop:
- Unique constraint "uniq_<table>0target_tenant0object_id0action"
- Key "ix_<table>_target_tenant"
Once the column name is changed, both the unique constraint and the key are
created again.
"""
return {
sa.UniqueConstraint: DROPPED_UNIQUE_CONSTRAINTS,
sa.Index: TABLES
}

View File

@ -36,6 +36,12 @@ class RbacPluginMixin(object):
@db_api.retry_if_session_inactive()
def create_rbac_policy(self, context, rbac_policy):
e = rbac_policy['rbac_policy']
# NOTE(ralonsoh): remove this conversion when "bp/keystone-v3" is
# widely implemented in all OpenStack projects.
try:
e['target_project'] = e.pop('target_tenant')
except KeyError:
pass
try:
registry.publish(resources.RBAC_POLICY, events.BEFORE_CREATE, self,
payload=events.DBEventPayload(
@ -49,7 +55,7 @@ class RbacPluginMixin(object):
rbac_args = {'project_id': e['project_id'],
'object_id': e['object_id'],
'action': e['action'],
'target_tenant': e['target_tenant']}
'target_project': e['target_project']}
_rbac_obj = rbac_class(context, **rbac_args)
_rbac_obj.create()
except o_exc.NeutronDbObjectDuplicateEntry:
@ -58,14 +64,22 @@ class RbacPluginMixin(object):
@staticmethod
def _make_rbac_policy_dict(entry, fields=None):
res = {f: entry[f] for f in ('id', 'project_id', 'target_tenant',
res = {f: entry[f] for f in ('id', 'project_id', 'target_project',
'action', 'object_id')}
# TODO(ralonsoh): remove once all calls refer to "target_project"
res['target_tenant'] = res['target_project']
res['object_type'] = entry.db_model.object_type
return db_utils.resource_fields(res, fields)
@db_api.retry_if_session_inactive()
def update_rbac_policy(self, context, id, rbac_policy):
pol = rbac_policy['rbac_policy']
# NOTE(ralonsoh): remove this conversion when "bp/keystone-v3" is
# widely implemented in all OpenStack projects.
try:
pol['target_project'] = pol.pop('target_tenant')
except KeyError:
pass
entry = self._get_rbac_policy(context, id)
object_type = entry.db_model.object_type
try:
@ -122,6 +136,12 @@ class RbacPluginMixin(object):
pager = base_obj.Pager(sorts, limit, page_reverse)
filters = filters or {}
object_types = filters.pop('object_type', None)
# NOTE(ralonsoh): remove this conversion when "bp/keystone-v3" is
# widely implemented in all OpenStack projects.
try:
filters['target_project'] = filters.pop('target_tenant')
except KeyError:
pass
rbac_classes_to_query = [
o for t, o in rbac_obj.RBACBaseObject.get_type_class_map().items()
if not object_types or t in object_types]

View File

@ -21,6 +21,7 @@ from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory
import sqlalchemy as sa
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from sqlalchemy.orm import validates
from neutron._i18n import _
@ -43,11 +44,11 @@ class RBACColumns(model_base.HasId, model_base.HasProject):
RBAC types.
"""
# the target_tenant is the subject that the policy will affect. this may
# the target_project is the subject that the policy will affect. this may
# also be a wildcard '*' to indicate all tenants or it may be a role if
# neutron gets better integration with keystone
target_tenant = sa.Column(sa.String(db_const.PROJECT_ID_FIELD_SIZE),
nullable=False, index=True)
target_project = sa.Column(sa.String(db_const.PROJECT_ID_FIELD_SIZE),
nullable=False, index=True)
action = sa.Column(sa.String(255), nullable=False, index=True)
@ -61,7 +62,7 @@ class RBACColumns(model_base.HasId, model_base.HasProject):
@declarative.declared_attr
def __table_args__(cls):
return (
sa.UniqueConstraint('target_tenant', 'object_id', 'action'),
sa.UniqueConstraint('target_project', 'object_id', 'action'),
model_base.BASEV2.__table_args__
)
@ -80,6 +81,20 @@ class RBACColumns(model_base.HasId, model_base.HasProject):
# with the valid actions rbac entries
pass
def get_target_tenant(self):
return self.target_project
def set_target_tenant(self, value):
self.target_project = value
# TODO(ralonsoh): remove once the neutron-lib code is modified and the
# minimum required version of this library set.
@declarative.declared_attr
def target_tenant(cls):
return orm.synonym(
'target_project',
descriptor=property(cls.get_target_tenant, cls.set_target_tenant))
def get_type_model_map():
return {table.object_type: table for table in RBACColumns.__subclasses__()}

View File

@ -26,7 +26,8 @@ from neutron.objects import securitygroup
@base.NeutronObjectRegistry.register
class AddressGroupRBAC(rbac.RBACBaseObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Changed 'target_tenant' to 'target_project'
VERSION = '1.1'
db_model = rbac_db_models.AddressGroupRBAC

View File

@ -27,7 +27,8 @@ from neutron.objects import subnetpool
@base.NeutronObjectRegistry.register
class AddressScopeRBAC(rbac.RBACBaseObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Changed 'target_tenant' to 'target_project'
VERSION = '1.1'
db_model = rbac_db_models.AddressScopeRBAC

View File

@ -41,8 +41,8 @@ class NetworkRBAC(rbac.RBACBaseObject):
# Version 1.1: Added 'id' and 'project_id'
# Version 1.2: Inherit from rbac.RBACBaseObject; changed 'object_id' from
# StringField to UUIDField
VERSION = '1.2'
# Version 1.3: Changed 'target_tenant' to 'target_project'
VERSION = '1.3'
db_model = rbac_db_models.NetworkRBAC

View File

@ -42,8 +42,8 @@ class QosPolicyRBAC(rbac.RBACBaseObject):
# Version 1.1: Inherit from rbac_db.RBACBaseObject; added 'id' and
# 'project_id'; changed 'object_id' from StringField to
# UUIDField
VERSION = '1.1'
# Version 1.2: Changed 'target_tenant' to 'target_project'
VERSION = '1.2'
db_model = rbac_db_models.QosPolicyRBAC

View File

@ -16,6 +16,7 @@
import abc
from neutron_lib.objects import common_types
from oslo_utils import versionutils
from oslo_versionedobjects import fields as obj_fields
from sqlalchemy import and_
@ -24,15 +25,14 @@ from neutron.objects import base
class RBACBaseObject(base.NeutronDbObject, metaclass=abc.ABCMeta):
# Version 1.0: Initial version
# Version 1.1: Changed 'target_tenant' to 'target_project'
VERSION = '1.1'
VERSION = '1.0'
# TODO(ralonsoh): move 'target_tenant' to 'target_project'.
fields = {
'id': common_types.UUIDField(),
'project_id': obj_fields.StringField(),
'object_id': common_types.UUIDField(),
'target_tenant': obj_fields.StringField(),
'target_project': obj_fields.StringField(),
'action': obj_fields.StringField(),
}
@ -40,15 +40,15 @@ class RBACBaseObject(base.NeutronDbObject, metaclass=abc.ABCMeta):
@classmethod
def get_projects(cls, context, object_id=None, action=None,
target_tenant=None):
target_project=None):
clauses = []
if object_id:
clauses.append(cls.db_model.object_id == object_id)
if action:
clauses.append(cls.db_model.action == action)
if target_tenant:
clauses.append(cls.db_model.target_tenant == target_tenant)
query = context.session.query(cls.db_model.target_tenant)
if target_project:
clauses.append(cls.db_model.target_project == target_project)
query = context.session.query(cls.db_model.target_project)
if clauses:
query = query.filter(and_(*clauses))
return [data[0] for data in query]
@ -57,3 +57,8 @@ class RBACBaseObject(base.NeutronDbObject, metaclass=abc.ABCMeta):
def get_type_class_map(cls):
return {klass.db_model.object_type: klass
for klass in cls.__subclasses__()}
def obj_make_compatible(self, primitive, target_version):
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 1): # NOTE(ralonsoh): remove in Yoga + 4.
primitive['target_tenant'] = primitive.pop('target_project')

View File

@ -58,7 +58,7 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
matches = ('*',) + ((context.project_id,) if context else ())
for entry in rbac_entries:
if (entry.action == models.ACCESS_SHARED and
entry.target_tenant in matches):
entry.target_project in matches):
return True
return False
@ -70,7 +70,7 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
return (db_utils.model_query(context, rbac_db_model).filter(
and_(rbac_db_model.object_id == obj_id,
rbac_db_model.action == models.ACCESS_SHARED,
rbac_db_model.target_tenant.in_(
rbac_db_model.target_project.in_(
['*', project_id]))).count() != 0)
@classmethod
@ -98,13 +98,13 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
def _get_projects_with_shared_access_to_db_obj(cls, context, obj_id):
rbac_db_model = cls.rbac_db_cls.db_model
return set(itertools.chain.from_iterable(context.session.query(
rbac_db_model.target_tenant).filter(
rbac_db_model.target_project).filter(
and_(rbac_db_model.object_id == obj_id,
rbac_db_model.action == models.ACCESS_SHARED,
rbac_db_model.target_tenant != '*'))))
rbac_db_model.target_project != '*'))))
@classmethod
def _validate_rbac_policy_delete(cls, context, obj_id, target_tenant):
def _validate_rbac_policy_delete(cls, context, obj_id, target_project):
ctx_admin = context.elevated()
rb_model = cls.rbac_db_cls.db_model
bound_project_ids = cls.get_bound_project_ids(ctx_admin, obj_id)
@ -114,24 +114,24 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
def raise_policy_in_use():
raise ext_rbac.RbacPolicyInUse(
object_id=obj_id,
details='project_id={}'.format(target_tenant))
details='project_id={}'.format(target_project))
if target_tenant != '*':
if target_project != '*':
# if there is a wildcard rule, we can return early because it
# shares the object globally
wildcard_sharing_entries = db_obj_sharing_entries.filter(
rb_model.target_tenant == '*')
rb_model.target_project == '*')
if wildcard_sharing_entries.count():
return
if target_tenant in bound_project_ids:
if target_project in bound_project_ids:
raise_policy_in_use()
return
# for the wildcard we need to query all of the rbac entries to
# see if any allow the object sharing
other_target_tenants = cls._get_projects_with_shared_access_to_db_obj(
other_target_projects = cls._get_projects_with_shared_access_to_db_obj(
ctx_admin, obj_id)
if not bound_project_ids.issubset(other_target_tenants):
if not bound_project_ids.issubset(other_target_projects):
raise_policy_in_use()
@classmethod
@ -146,14 +146,14 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
if policy['action'] != models.ACCESS_SHARED:
return
target_tenant = policy['target_tenant']
target_project = policy['target_project']
db_obj = obj_db_api.get_object(
cls, context.elevated(), id=policy['object_id'])
if db_obj.project_id == target_tenant:
if db_obj.project_id == target_project:
return
cls._validate_rbac_policy_delete(context=context,
obj_id=policy['object_id'],
target_tenant=target_tenant)
target_project=target_project)
@classmethod
def validate_rbac_policy_create(cls, resource, event, trigger,
@ -171,8 +171,8 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
"""
policy = payload.latest_state
prev_project = policy['target_tenant']
new_project = payload.request_body['target_tenant']
prev_project = policy['target_project']
new_project = payload.request_body['target_project']
if prev_project == new_project:
return
if new_project != '*':
@ -214,10 +214,10 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
return callback_map[event](resource, event, trigger,
payload=payload)
def attach_rbac(self, obj_id, project_id, target_tenant='*'):
def attach_rbac(self, obj_id, project_id, target_project='*'):
obj_type = self.rbac_db_cls.db_model.object_type
rbac_policy = {'rbac_policy': {'object_id': obj_id,
'target_tenant': target_tenant,
'target_project': target_project,
'project_id': project_id,
'object_type': obj_type,
'action': models.ACCESS_SHARED}}
@ -227,7 +227,7 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
admin_context = self.obj_context.elevated()
shared_prev = obj_db_api.get_object(self.rbac_db_cls, admin_context,
object_id=obj_id,
target_tenant='*',
target_project='*',
action=models.ACCESS_SHARED)
is_shared_prev = bool(shared_prev)
if is_shared_prev == is_shared_new:
@ -239,7 +239,7 @@ class RbacNeutronDbObjectMixin(rbac_db_mixin.RbacPluginMixin,
return
# 'shared' goes True -> False is actually an attempt to delete
# rbac rule for sharing obj_id with target_tenant = '*'
# rbac rule for sharing obj_id with target_project = '*'
self._validate_rbac_policy_delete(self.obj_context, obj_id, '*')
return self.obj_context.session.delete(shared_prev)

View File

@ -28,7 +28,8 @@ from neutron.objects import rbac_db
@base.NeutronObjectRegistry.register
class SecurityGroupRBAC(rbac.RBACBaseObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Changed 'target_tenant' to 'target_project'
VERSION = '1.1'
db_model = rbac_db_models.SecurityGroupRBAC

View File

@ -33,7 +33,8 @@ from neutron.objects import subnet
@base.NeutronObjectRegistry.register
class SubnetPoolRBAC(rbac.RBACBaseObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Changed 'target_tenant' to 'target_project'
VERSION = '1.1'
db_model = rbac_db_models.SubnetPoolRBAC
@ -127,8 +128,8 @@ class SubnetPool(rbac_db.NeutronRbacObject):
# Ensure that target project has access to AS
shared_to_target_project_or_to_all = (
sa.and_(
rbac_as_model.target_tenant.in_(
["*", policy['target_tenant']]
rbac_as_model.target_project.in_(
["*", policy['target_project']]
),
rbac_as_model.object_id == db_obj["address_scope_id"]
)

View File

@ -97,7 +97,7 @@ class NetworkRBACTestCase(testlib_api.SqlTestCase):
else:
action = 'access_as_shared'
rbac = network_obj.NetworkRBAC.get_object(
self.cxt, object_id=network_id, action=action, target_tenant='*')
self.cxt, object_id=network_id, action=action, target_project='*')
if is_none:
self.assertIsNone(rbac)
else:

View File

@ -14,27 +14,33 @@
# limitations under the License.
from oslo_policy import policy as base_policy
import testscenarios
from neutron import policy
from neutron.tests.unit.conf.policies import base
class RbacAPITestCase(base.PolicyBaseTestCase):
class RbacAPITestCase(testscenarios.WithScenarios, base.PolicyBaseTestCase):
scenarios = [
('target_tenant', {'_target_label': 'target_tenant'}),
('target_project', {'_target_label': 'target_project'})
]
def setUp(self):
super(RbacAPITestCase, self).setUp()
self.target = {
'project_id': self.project_id,
'target_tenant': 'other-project'}
self._target_label: 'other-project'}
self.alt_target = {
'project_id': self.alt_project_id,
'target_tenant': 'other-project'}
self._target_label: 'other-project'}
self.wildcard_target = {
'project_id': self.project_id,
'target_tenant': '*'}
self._target_label: '*'}
self.wildcard_alt_target = {
'project_id': self.alt_project_id,
'target_tenant': '*'}
self._target_label: '*'}
class SystemAdminTests(RbacAPITestCase):

View File

@ -2795,12 +2795,12 @@ class TestNetworksV2(NeutronDbPluginV2TestCase):
ctx, object_id=network['network']['id'],
action='access_as_shared',
project_id=network['network']['tenant_id'],
target_tenant='somebody_else').create()
target_project='somebody_else').create()
network_obj.NetworkRBAC(
ctx, object_id=network['network']['id'],
action='access_as_shared',
project_id=network['network']['tenant_id'],
target_tenant='one_more_somebody_else').create()
target_project='one_more_somebody_else').create()
res1 = self._create_port(self.fmt,
network['network']['id'],
webob.exc.HTTPCreated.code,
@ -6560,7 +6560,7 @@ class DbModelMixin(object):
ctx, object_id=network.id,
action='access_as_shared',
project_id=network.project_id,
target_tenant='*').create()
target_project='*').create()
net2 = models_v2.Network(name="net_net2", status="OK",
admin_state_up=True,
mtu=1500)

View File

@ -40,11 +40,11 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
'object_id': network['network']['id'],
'object_type': 'network',
'action': action,
'target_tenant': target}}
'target_project': target}}
return policy
def _setup_networkrbac_and_port(self, network, target_tenant):
policy = self._make_networkrbac(network, target_tenant)
def _setup_networkrbac_and_port(self, network, target_project):
policy = self._make_networkrbac(network, target_project)
netrbac = self.plugin.create_rbac_policy(self.context, policy)
test_port = {'port': {'name': 'test-port',
@ -54,8 +54,8 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
'admin_state_up': True,
'device_id': 'device_id',
'device_owner': 'device_owner',
'project_id': target_tenant,
'tenant_id': target_tenant}}
'project_id': target_project,
'tenant_id': target_project}}
port = self.plugin.create_port(self.context, test_port)
return netrbac, port
@ -98,13 +98,13 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
orig_target,
'access_as_external')
netrbac = self.plugin.create_rbac_policy(self.context, policy)
update_policy = {'rbac_policy': {'target_tenant': new_target}}
update_policy = {'rbac_policy': {'target_project': new_target}}
netrbac2 = self.plugin.update_rbac_policy(self.context,
netrbac['id'],
update_policy)
policy['rbac_policy']['target_tenant'] = new_target
policy['rbac_policy']['target_project'] = new_target
for k, v in policy['rbac_policy'].items():
self.assertEqual(netrbac2[k], v)
@ -157,20 +157,20 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
with self.network() as net:
policy = self._make_networkrbac(net, orig_target)
netrbac = self.plugin.create_rbac_policy(self.context, policy)
update_policy = {'rbac_policy': {'target_tenant': new_target}}
update_policy = {'rbac_policy': {'target_project': new_target}}
netrbac2 = self.plugin.update_rbac_policy(self.context,
netrbac['id'],
update_policy)
policy['rbac_policy']['target_tenant'] = new_target
policy['rbac_policy']['target_project'] = new_target
for k, v in policy['rbac_policy'].items():
self.assertEqual(netrbac2[k], v)
def test_delete_networkrbac_in_use_fail(self):
with self.network() as net:
netrbac, _ = self._setup_networkrbac_and_port(
network=net, target_tenant='test-tenant-2')
network=net, target_project='test-tenant-2')
self.assertRaises(ext_rbac.RbacPolicyInUse,
self.plugin.delete_rbac_policy,
@ -179,7 +179,7 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
def test_port_presence_prevents_network_rbac_policy_deletion(self):
with self.network() as net:
netrbac, port = self._setup_networkrbac_and_port(
network=net, target_tenant='alice')
network=net, target_project='alice')
self.assertRaises(ext_rbac.RbacPolicyInUse,
self.plugin.delete_rbac_policy,
self.context, netrbac['id'])
@ -198,7 +198,7 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
self.context, wild_policy['id'])
# similarly, we can't update the policy to a different tenant
update_policy = {'rbac_policy': {'target_tenant': 'bob'}}
update_policy = {'rbac_policy': {'target_project': 'bob'}}
self.assertRaises(ext_rbac.RbacPolicyInUse,
self.plugin.update_rbac_policy,
self.context, wild_policy['id'],
@ -253,7 +253,7 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
get_net.return_value = net['network']
payload = events.DBEventPayload(
self.context, states=(policy,),
request_body={'target_tenant': 'new-target-tenant'},
request_body={'target_project': 'new-target-tenant'},
metadata={'object_type': 'network'})
self.plugin.validate_network_rbac_policy_change(
None, events.BEFORE_UPDATE, None,
@ -264,7 +264,7 @@ class NetworkRbacTestcase(test_plugin.NeutronDbPluginV2TestCase):
return _class(id=uuidutils.generate_uuid(),
project_id='project_id',
object_id=uuidutils.generate_uuid(),
target_tenant='target_tenant',
target_project='target_project',
action=rbac_db_models.ACCESS_SHARED)
@mock.patch.object(qos_policy_obj.QosPolicyRBAC, 'get_objects')

View File

@ -143,13 +143,13 @@ class ExtNetDBTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
ctx = context.Context('edinson', 'cavani')
model = models_v2.Network
txt = ("networkrbacs.action = :action_1 AND "
"networkrbacs.target_tenant = :target_tenant_1 OR "
"networkrbacs.target_tenant = :target_tenant_2")
"networkrbacs.target_project = :target_project_1 OR "
"networkrbacs.target_project = :target_project_2")
conditions = external_net_db._network_filter_hook(ctx, model, [])
self.assertEqual(conditions.__str__(), txt)
# Try to concatenate conditions
txt2 = (txt.replace('tenant_1', 'tenant_3').
replace('tenant_2', 'tenant_4').
txt2 = (txt.replace('project_1', 'project_3').
replace('project_2', 'project_4').
replace('action_1', 'action_2'))
conditions = external_net_db._network_filter_hook(ctx, model,
conditions)

View File

@ -28,9 +28,9 @@ from neutron.tests import base as test_base
object_data = {
'AddressAssociation': '1.0-b92160a3dd2fb7b951adcd2e6ae1665a',
'AddressGroup': '1.2-1ddbf0a9f61785033ce31818ac62687e',
'AddressGroupRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'AddressGroupRBAC': '1.1-be82ed54376b85ee4f963d479ac48c91',
'AddressScope': '1.1-dd0dfdb67775892d3adc090e28e43bd8',
'AddressScopeRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'AddressScopeRBAC': '1.1-be82ed54376b85ee4f963d479ac48c91',
'Agent': '1.1-64b670752d57b3c7602cb136e0338507',
'AllowedAddressPair': '1.0-9f9186b6f952fbf31d257b0458b852c0',
'AutoAllocatedTopology': '1.0-74642e58c53bf3610dc224c59f81b242',
@ -68,7 +68,7 @@ object_data = {
'NetworkDhcpAgentBinding': '1.1-d9443c88809ffa4c45a0a5a48134b54a',
'NetworkDNSDomain': '1.0-420db7910294608534c1e2e30d6d8319',
'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'NetworkRBAC': '1.2-192845c5ed0718e1c54fac36936fcd7d',
'NetworkRBAC': '1.3-be82ed54376b85ee4f963d479ac48c91',
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
'NetworkSegmentRange': '1.0-bdec1fffc9058ea676089b1f2f2b3cf3',
'NetworkSubnetLock': '1.0-140de39d4b86ae346dc3d70b885bea53',
@ -89,7 +89,7 @@ object_data = {
'QosMinimumBandwidthRule': '1.5-314c3419f4799067cc31cc319080adff',
'QosMinimumPacketRateRule': '1.5-d0516c55aa2f310a2646c7d243cb8620',
'QosPacketRateLimitRule': '1.5-18411fa95f54602b8c8a5da2d3194b31',
'QosPolicyRBAC': '1.1-192845c5ed0718e1c54fac36936fcd7d',
'QosPolicyRBAC': '1.2-be82ed54376b85ee4f963d479ac48c91',
'QosRuleType': '1.5-ea51a164013e05d5956d8bf538622b33',
'QosRuleTypeDriver': '1.0-7d8cb9f0ef661ac03700eae97118e3db',
'QosPolicy': '1.10-4adb0cde3102c10d8970ec9487fd7fe7',
@ -110,7 +110,7 @@ object_data = {
'RouterRoute': '1.0-07fc5337c801fb8c6ccfbcc5afb45907',
'SecurityGroup': '1.5-7eb8e44c327512e7bb1759ab41ede44b',
'SecurityGroupPortBinding': '1.0-6879d5c0af80396ef5a72934b6a6ef20',
'SecurityGroupRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'SecurityGroupRBAC': '1.1-be82ed54376b85ee4f963d479ac48c91',
'SecurityGroupRule': '1.2-27793368d4ac35f2ed6e0bb653c6aaad',
'SegmentHostMapping': '1.0-521597cf82ead26217c3bd10738f00f0',
'ServiceProfile': '1.0-9beafc9e7d081b8258f3c5cb66ac5eed',
@ -119,7 +119,7 @@ object_data = {
'SubnetDNSPublishFixedIP': '1.0-db22af6fa20b143986f0cbe06cbfe0ea',
'SubnetPool': '1.1-a0e03895d1a6e7b9d4ab7b0ca13c3867',
'SubnetPoolPrefix': '1.0-13c15144135eb869faa4a76dc3ee3b6c',
'SubnetPoolRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'SubnetPoolRBAC': '1.1-be82ed54376b85ee4f963d479ac48c91',
'SubnetServiceType': '1.0-05ae4cdb2a9026a697b143926a1add8c',
'SubPort': '1.0-72c8471068db1f0491b5480fe49b52bb',
'Tag': '1.0-1a0d20379920ffa3cebfd3e016d2f7a0',

View File

@ -49,7 +49,7 @@ class FakeNeutronRbacObject(base.NeutronDbObject):
fields = {
'object_id': obj_fields.StringField(),
'target_tenant': obj_fields.StringField(),
'target_project': obj_fields.StringField(),
'action': obj_fields.StringField(),
}
@ -202,10 +202,10 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
def test_validate_rbac_policy_delete_skips_db_object_owner(self,
mock_get_object):
policy = {'action': rbac_db_models.ACCESS_SHARED,
'target_tenant': 'fake_project_id',
'target_project': 'fake_project_id',
'object_id': 'fake_obj_id',
'project_id': 'fake_project_id'}
mock_get_object.return_value.project_id = policy['target_tenant']
mock_get_object.return_value.project_id = policy['target_project']
self._test_validate_rbac_policy_delete_handles_policy(policy)
@mock.patch.object(obj_db_api, 'get_object')
@ -214,14 +214,14 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
def test_validate_rbac_policy_delete_fails_single_project_and_in_use(
self, get_bound_project_ids_mock, mock_get_object):
policy = {'action': rbac_db_models.ACCESS_SHARED,
'target_tenant': 'project_id_shared_with',
'target_project': 'project_id_shared_with',
'project_id': 'object_owner_project_id',
'object_id': 'fake_obj_id'}
context = mock.Mock()
with mock.patch.object(
self._test_class,
'_get_db_obj_rbac_entries') as target_tenants_mock:
filter_mock = target_tenants_mock.return_value.filter
'_get_db_obj_rbac_entries') as target_projects_mock:
filter_mock = target_projects_mock.return_value.filter
filter_mock.return_value.count.return_value = 0
payload = events.DBEventPayload(
context,
@ -251,7 +251,7 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
self._test_class._validate_rbac_policy_delete(
context=context,
obj_id='fake_obj_id',
target_tenant='fake_tid1')
target_project='fake_tid1')
sh_tids.assert_not_called()
@mock.patch.object(_test_class, '_get_db_obj_rbac_entries')
@ -264,7 +264,7 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
self, get_bound_project_ids_mock, mock_projects_with_shared_access,
_get_db_obj_rbac_entries_mock):
policy = {'action': rbac_db_models.ACCESS_SHARED,
'target_tenant': '*',
'target_project': '*',
'project_id': 'object_owner_project_id',
'object_id': 'fake_obj_id'}
context = mock.Mock()
@ -294,7 +294,7 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
obj.update_shared(is_shared_new=True, obj_id=obj_id)
get_object_mock.assert_called_with(
obj.rbac_db_cls, mock.ANY, object_id=obj_id,
target_tenant='*', action=rbac_db_models.ACCESS_SHARED)
target_project='*', action=rbac_db_models.ACCESS_SHARED)
self.assertFalse(mock_validate_delete.called)
self.assertFalse(attach_rbac_mock.called)
@ -309,7 +309,7 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
test_neutron_obj.update_shared(is_shared_new=True, obj_id=obj_id)
get_object_mock.assert_called_with(
test_neutron_obj.rbac_db_cls, mock.ANY, object_id=obj_id,
target_tenant='*', action=rbac_db_models.ACCESS_SHARED)
target_project='*', action=rbac_db_models.ACCESS_SHARED)
attach_rbac_mock.assert_called_with(
obj_id, test_neutron_obj.obj_context.project_id)
@ -329,7 +329,7 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
obj.update_shared(is_shared_new=False, obj_id=obj_id)
get_object_mock.assert_called_with(
obj.rbac_db_cls, mock.ANY, object_id=obj_id,
target_tenant='*', action=rbac_db_models.ACCESS_SHARED)
target_project='*', action=rbac_db_models.ACCESS_SHARED)
self.assertFalse(attach_rbac_mock.attach_rbac.called)
mock_validate_delete.assert_called_with(mock.ANY, obj_id, '*')
@ -338,12 +338,12 @@ class RbacNeutronDbObjectTestCase(test_rbac.RBACBaseObjectIfaceTestCase,
def test_attach_rbac_returns_type(self, create_rbac_mock):
obj_id = 'fake_obj_id'
project_id = 'fake_project_id'
target_tenant = 'fake_target_project'
target_project = 'fake_target_project'
self._test_class(mock.Mock()).attach_rbac(obj_id, project_id,
target_tenant)
target_project)
rbac_pol = create_rbac_mock.call_args_list[0][0][1]['rbac_policy']
self.assertEqual(rbac_pol['object_id'], obj_id)
self.assertEqual(rbac_pol['target_tenant'], target_tenant)
self.assertEqual(rbac_pol['target_project'], target_project)
self.assertEqual(rbac_pol['action'], rbac_db_models.ACCESS_SHARED)
self.assertEqual(rbac_pol['object_type'],
self._test_class.rbac_db_cls.db_model.object_type)

View File

@ -100,7 +100,7 @@ class SubnetPoolDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
address_scope_id):
filter_mock.assert_called_once()
self.assertEqual(
"addressscoperbacs.target_tenant IN ('*', '%(project_id)s') "
"addressscoperbacs.target_project IN ('*', '%(project_id)s') "
"AND addressscoperbacs.object_id = '%(address_scope_id)s'" % {
"project_id": project_id,
"address_scope_id": address_scope_id,
@ -119,7 +119,7 @@ class SubnetPoolDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
payload = mock.Mock(
context=context, request_body=dict(
object_id="fake_id",
target_tenant=fake_project_id
target_project=fake_project_id
)
)
fake_address_scope_id = "fake_as_id"
@ -151,7 +151,7 @@ class SubnetPoolDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
payload = mock.Mock(
context=context, request_body=dict(
object_id="fake_id",
target_tenant=fake_project_id
target_project=fake_project_id
)
)
fake_address_scope_id = "fake_as_id"