Allow sharing of address groups via RBAC mechanism
Client: https://review.opendev.org/c/openstack/python-openstackclient/+/775045 Tempest tests: https://review.opendev.org/c/openstack/neutron-tempest-plugin/+/773274 Allow sharing of address groups via RBAC mechanism Change-Id: I9d9e2bd4add5bb6fa4105352bfda739340932571
This commit is contained in:
parent
77ee0847f5
commit
8094b524f6
|
@ -20,6 +20,7 @@ is supported by:
|
|||
* Binding security groups to ports (since Stein).
|
||||
* Assigning address scopes to subnet pools (since Ussuri).
|
||||
* Assigning subnet pools to subnets (since Ussuri).
|
||||
* Assigning address groups to security group rules (since Wallaby).
|
||||
|
||||
|
||||
Sharing an object with specific projects
|
||||
|
@ -444,6 +445,80 @@ the subnet pool is no longer in use:
|
|||
This process can be repeated any number of times to share a subnet pool
|
||||
with an arbitrary number of projects.
|
||||
|
||||
Sharing an address group with specific projects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Create an address group to share:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack address group create test-ag --address 10.1.1.1
|
||||
+-------------+--------------------------------------+
|
||||
| Field | Value |
|
||||
+-------------+--------------------------------------+
|
||||
| addresses | ['10.1.1.1/32'] |
|
||||
| description | |
|
||||
| id | cdb6eb3e-f9a0-4d52-8478-358eaa2c4737 |
|
||||
| name | test-ag |
|
||||
| project_id | 66c77cf262454777a8f455cce48c12c0 |
|
||||
+-------------+--------------------------------------+
|
||||
|
||||
|
||||
Create the RBAC policy entry using the :command:`openstack network rbac create`
|
||||
command (in this example, the ID of the project we want to share with is
|
||||
``bbd82892525d4372911390b984ed3265``):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack network rbac create --target-project \
|
||||
bbd82892525d4372911390b984ed3265 --action access_as_shared \
|
||||
--type address_group cdb6eb3e-f9a0-4d52-8478-358eaa2c4737
|
||||
+-------------------+--------------------------------------+
|
||||
| Field | Value |
|
||||
+-------------------+--------------------------------------+
|
||||
| action | access_as_shared |
|
||||
| id | c7414ac2-9a6b-420b-84c5-4158a6cca4f9 |
|
||||
| name | None |
|
||||
| object_id | cdb6eb3e-f9a0-4d52-8478-358eaa2c4737 |
|
||||
| object_type | address_group |
|
||||
| project_id | 66c77cf262454777a8f455cce48c12c0 |
|
||||
| target_project_id | bbd82892525d4372911390b984ed3265 |
|
||||
+-------------------+--------------------------------------+
|
||||
|
||||
|
||||
The ``target-project`` parameter specifies the project that requires
|
||||
access to the address group. The ``action`` parameter specifies what
|
||||
the project is allowed to do. The ``type`` parameter says
|
||||
that the target object is an address group. The final parameter is the ID of
|
||||
the address group we are granting access to.
|
||||
|
||||
Project ``bbd82892525d4372911390b984ed3265`` will now be able to see
|
||||
the address group when running :command:`openstack address group list` and
|
||||
:command:`openstack address group show` and will also be able to assign
|
||||
it to its security group rules. No other users (other than admins and the
|
||||
owner) will be able to see the address group.
|
||||
|
||||
To remove access for that project, delete the RBAC policy that allows
|
||||
it using the :command:`openstack network rbac delete` command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack network rbac delete c7414ac2-9a6b-420b-84c5-4158a6cca4f9
|
||||
|
||||
If that project has security group rules with the address group applied to
|
||||
them, the server will not delete the RBAC policy until the address group is no
|
||||
longer in use:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack network rbac delete c7414ac2-9a6b-420b-84c5-4158a6cca4f9
|
||||
RBAC policy on object cdb6eb3e-f9a0-4d52-8478-358eaa2c4737
|
||||
cannot be removed because other objects depend on it
|
||||
|
||||
This process can be repeated any number of times to share an address group
|
||||
with an arbitrary number of projects.
|
||||
|
||||
|
||||
How the 'shared' flag relates to these entries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import functools
|
||||
|
||||
from neutron_lib.api.definitions import rbac_address_groups as rbac_ag_apidef
|
||||
from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef
|
||||
from neutron_lib.api.definitions import security_groups_normalized_cidr
|
||||
from neutron_lib.api.definitions import security_groups_remote_address_group \
|
||||
|
@ -58,6 +59,7 @@ def disable_security_group_extension_by_config(aliases):
|
|||
_disable_extension('allowed-address-pairs', aliases)
|
||||
LOG.info('Disabled address-group extension.')
|
||||
_disable_extension('address-group', aliases)
|
||||
_disable_extension(rbac_ag_apidef.ALIAS, aliases)
|
||||
|
||||
|
||||
class SecurityGroupAgentRpc(object):
|
||||
|
|
|
@ -14,6 +14,7 @@ import importlib
|
|||
import inspect
|
||||
import itertools
|
||||
|
||||
from neutron.conf.policies import address_group
|
||||
from neutron.conf.policies import address_scope
|
||||
from neutron.conf.policies import agent
|
||||
from neutron.conf.policies import auto_allocated_topology
|
||||
|
@ -45,6 +46,7 @@ from neutron.conf.policies import trunk
|
|||
def list_rules():
|
||||
return itertools.chain(
|
||||
base.list_rules(),
|
||||
address_group.list_rules(),
|
||||
address_scope.list_rules(),
|
||||
agent.list_rules(),
|
||||
auto_allocated_topology.list_rules(),
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# 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.
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
from neutron.conf.policies import base
|
||||
|
||||
|
||||
AG_COLLECTION_PATH = '/address-groups'
|
||||
AG_RESOURCE_PATH = '/address-groups/{id}'
|
||||
|
||||
|
||||
rules = [
|
||||
policy.RuleDefault(
|
||||
'shared_address_groups',
|
||||
'field:address_groups:shared=True',
|
||||
'Definition of a shared address group'
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='get_address_group',
|
||||
check_str=base.policy_or(base.RULE_ADMIN_OR_OWNER,
|
||||
'rule:shared_address_groups'),
|
||||
description='Get an address group',
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': AG_COLLECTION_PATH,
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': AG_RESOURCE_PATH,
|
||||
},
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
|
@ -1 +1 @@
|
|||
1e0744e4ffea
|
||||
6135a7bd4425
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
"""add_rbac_support_for_address_group
|
||||
|
||||
Revision ID: 6135a7bd4425
|
||||
Revises: 1e0744e4ffea
|
||||
Create Date: 2021-01-22 11:24:07.435031
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6135a7bd4425'
|
||||
down_revision = '1e0744e4ffea'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'addressgrouprbacs', sa.MetaData(),
|
||||
sa.Column('project_id', sa.String(length=255), nullable=True,
|
||||
index=True),
|
||||
sa.Column('id', sa.String(length=36), nullable=False,
|
||||
primary_key=True),
|
||||
sa.Column('target_tenant', sa.String(length=255), nullable=False),
|
||||
sa.Column('action', sa.String(length=255), nullable=False),
|
||||
sa.Column('object_id', sa.String(length=36), nullable=False),
|
||||
sa.ForeignKeyConstraint(['object_id'], ['address_groups.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.UniqueConstraint('target_tenant', 'object_id', 'action',
|
||||
name='uniq_address_groups_rbacs0'
|
||||
'target_tenant0object_id0action')
|
||||
)
|
|
@ -16,6 +16,7 @@ from neutron_lib.db import model_base
|
|||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from neutron.db import rbac_db_models
|
||||
from neutron.db import standard_attr
|
||||
|
||||
|
||||
|
@ -43,4 +44,8 @@ class AddressGroup(standard_attr.HasStandardAttributes,
|
|||
load_on_pending=True),
|
||||
lazy='subquery',
|
||||
cascade='all, delete-orphan')
|
||||
rbac_entries = sa.orm.relationship(rbac_db_models.AddressGroupRBAC,
|
||||
backref='address_groups',
|
||||
lazy='subquery',
|
||||
cascade='all, delete, delete-orphan')
|
||||
api_collections = [ag.ALIAS]
|
||||
|
|
|
@ -149,3 +149,14 @@ class SubnetPoolRBAC(RBACColumns, model_base.BASEV2):
|
|||
@staticmethod
|
||||
def get_valid_actions():
|
||||
return (ACCESS_SHARED,)
|
||||
|
||||
|
||||
class AddressGroupRBAC(RBACColumns, model_base.BASEV2):
|
||||
"""RBAC table for address_group."""
|
||||
|
||||
object_id = _object_id_column('address_groups.id')
|
||||
object_type = 'address_group'
|
||||
|
||||
@staticmethod
|
||||
def get_valid_actions():
|
||||
return (ACCESS_SHARED,)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# 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.
|
||||
|
||||
from neutron_lib.api.definitions import rbac_address_groups as apidef
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
|
||||
class Rbac_address_group(extensions.APIExtensionDescriptor):
|
||||
"""Extension class supporting address group RBAC."""
|
||||
|
||||
api_definition = apidef
|
|
@ -16,15 +16,30 @@ from oslo_utils import versionutils
|
|||
from oslo_versionedobjects import fields as obj_fields
|
||||
|
||||
from neutron.db.models import address_group as models
|
||||
from neutron.db import rbac_db_models
|
||||
from neutron.objects import base
|
||||
from neutron.objects import rbac
|
||||
from neutron.objects import rbac_db
|
||||
from neutron.objects import securitygroup
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class AddressGroup(base.NeutronDbObject):
|
||||
class AddressGroupRBAC(rbac.RBACBaseObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = rbac_db_models.AddressGroupRBAC
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class AddressGroup(rbac_db.NeutronRbacObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Added standard attributes
|
||||
VERSION = '1.1'
|
||||
# Version 1.2: Added RBAC support
|
||||
VERSION = '1.2'
|
||||
|
||||
# required by RbacNeutronMetaclass
|
||||
rbac_db_cls = AddressGroupRBAC
|
||||
|
||||
db_model = models.AddressGroup
|
||||
|
||||
|
@ -32,6 +47,7 @@ class AddressGroup(base.NeutronDbObject):
|
|||
'id': common_types.UUIDField(),
|
||||
'name': obj_fields.StringField(nullable=True),
|
||||
'project_id': obj_fields.StringField(),
|
||||
'shared': obj_fields.BooleanField(default=False),
|
||||
'addresses': obj_fields.ListOfObjectsField('AddressAssociation',
|
||||
nullable=True)
|
||||
}
|
||||
|
@ -43,6 +59,14 @@ class AddressGroup(base.NeutronDbObject):
|
|||
standard_fields = ['revision_number', 'created_at', 'updated_at']
|
||||
for f in standard_fields:
|
||||
primitive.pop(f, None)
|
||||
if _target_version < (1, 2):
|
||||
primitive.pop('shared', None)
|
||||
|
||||
@classmethod
|
||||
def get_bound_tenant_ids(cls, context, obj_id):
|
||||
ag_objs = securitygroup.SecurityGroupRule.get_objects(
|
||||
context, remote_address_group_id=[obj_id])
|
||||
return {ag.tenant_id for ag in ag_objs}
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
|
|
|
@ -46,6 +46,7 @@ from neutron_lib.api.definitions import port_security as psec
|
|||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api.definitions import portbindings_extended as pbe_ext
|
||||
from neutron_lib.api.definitions import provider_net
|
||||
from neutron_lib.api.definitions import rbac_address_groups as rbac_ag_apidef
|
||||
from neutron_lib.api.definitions import rbac_address_scope
|
||||
from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef
|
||||
from neutron_lib.api.definitions import rbac_subnetpool
|
||||
|
@ -192,6 +193,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||
external_net.ALIAS, portbindings.ALIAS,
|
||||
"quotas", "security-group",
|
||||
rbac_address_scope.ALIAS,
|
||||
rbac_ag_apidef.ALIAS,
|
||||
rbac_sg_apidef.ALIAS,
|
||||
rbac_subnetpool.ALIAS,
|
||||
agent_apidef.ALIAS,
|
||||
|
|
|
@ -46,9 +46,10 @@ NETWORK_API_EXTENSIONS+=",qos-gateway-ip"
|
|||
NETWORK_API_EXTENSIONS+=",quotas"
|
||||
NETWORK_API_EXTENSIONS+=",quota_details"
|
||||
NETWORK_API_EXTENSIONS+=",rbac-policies"
|
||||
NETWORK_API_EXTENSIONS+=",rbac-address-scope""
|
||||
NETWORK_API_EXTENSIONS+=",rbac-security-groups""
|
||||
NETWORK_API_EXTENSIONS+=",rbac-subnetpool""
|
||||
NETWORK_API_EXTENSIONS+=",rbac-address-group"
|
||||
NETWORK_API_EXTENSIONS+=",rbac-address-scope"
|
||||
NETWORK_API_EXTENSIONS+=",rbac-security-groups"
|
||||
NETWORK_API_EXTENSIONS+=",rbac-subnetpool"
|
||||
NETWORK_API_EXTENSIONS+=",router"
|
||||
NETWORK_API_EXTENSIONS+=",router-admin-state-down-before-update"
|
||||
NETWORK_API_EXTENSIONS+=",router_availability_zone"
|
||||
|
|
|
@ -94,7 +94,9 @@ class SecurityGroupServerAPIShimTestCase(base.BaseTestCase):
|
|||
self.rcache.record_resource_update(self.ctx, 'Port', p)
|
||||
return p
|
||||
|
||||
def _make_address_group_ovo(self):
|
||||
@mock.patch.object(address_group.AddressGroup, 'is_shared_with_tenant',
|
||||
return_value=False)
|
||||
def _make_address_group_ovo(self, *args, **kwargs):
|
||||
id = uuidutils.generate_uuid()
|
||||
address_associations = [
|
||||
address_group.AddressAssociation(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
from neutron.objects import address_group
|
||||
from neutron.tests.unit.objects import test_base as obj_test_base
|
||||
from neutron.tests.unit.objects import test_rbac
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
|
@ -46,6 +47,36 @@ class AddressGroupDbObjectTestCase(
|
|||
self.assertIn('description',
|
||||
ag_obj_1_0['versioned_object.data'])
|
||||
|
||||
def test_object_version_degradation_1_2_to_1_1_no_shared(self):
|
||||
ag_obj = self._create_test_address_group()
|
||||
ag_obj_1_1 = ag_obj.obj_to_primitive('1.1')
|
||||
self.assertNotIn('shared', ag_obj_1_1['versioned_object.data'])
|
||||
|
||||
|
||||
class AddressGroupRBACDbObjectTestCase(test_rbac.TestRBACObjectMixin,
|
||||
obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = address_group.AddressGroupRBAC
|
||||
|
||||
def setUp(self):
|
||||
super(AddressGroupRBACDbObjectTestCase, self).setUp()
|
||||
for obj in self.db_objs:
|
||||
ag_obj = address_group.AddressGroup(self.context,
|
||||
id=obj['object_id'],
|
||||
project_id=obj['project_id'])
|
||||
ag_obj.create()
|
||||
|
||||
def _create_test_address_group_rbac(self):
|
||||
self.objs[0].create()
|
||||
return self.objs[0]
|
||||
|
||||
|
||||
class AddressGroupRBACIfaceObjectTestCase(
|
||||
test_rbac.TestRBACObjectMixin, obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = address_group.AddressGroupRBAC
|
||||
|
||||
|
||||
class AddressAssociationIfaceObjectTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
|
|
@ -27,7 +27,8 @@ from neutron.tests import base as test_base
|
|||
# alphabetic order.
|
||||
object_data = {
|
||||
'AddressAssociation': '1.0-b92160a3dd2fb7b951adcd2e6ae1665a',
|
||||
'AddressGroup': '1.1-78c35b6ac495407be56b8fcdbeda4d67',
|
||||
'AddressGroup': '1.2-1ddbf0a9f61785033ce31818ac62687e',
|
||||
'AddressGroupRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
|
||||
'AddressScope': '1.1-dd0dfdb67775892d3adc090e28e43bd8',
|
||||
'AddressScopeRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
|
||||
'Agent': '1.1-64b670752d57b3c7602cb136e0338507',
|
||||
|
|
|
@ -13,6 +13,7 @@ import random
|
|||
|
||||
from unittest import mock
|
||||
|
||||
from neutron.objects import address_group
|
||||
from neutron.objects import address_scope
|
||||
from neutron.objects import network
|
||||
from neutron.objects.qos import policy
|
||||
|
@ -37,7 +38,8 @@ class TestRBACObjectMixin(object):
|
|||
class RBACBaseObjectTestCase(neutron_test_base.BaseTestCase):
|
||||
|
||||
def test_get_type_class_map(self):
|
||||
class_map = {'address_scope': address_scope.AddressScopeRBAC,
|
||||
class_map = {'address_group': address_group.AddressGroupRBAC,
|
||||
'address_scope': address_scope.AddressScopeRBAC,
|
||||
'qos_policy': policy.QosPolicyRBAC,
|
||||
'network': network.NetworkRBAC,
|
||||
'security_group': securitygroup.SecurityGroupRBAC,
|
||||
|
|
Loading…
Reference in New Issue