[aim-mapping] Restrict auto-ptg access

This patch restricts GET and UPDATE for auto-ptg to the admin
via policy.json RBAC enforcement mechanism. When these rules are
in effect, policy_target creation in the auto_ptg is also restricted
to only the admin. These rules can however be relaxed if required by
modifying the policy.json file as follows:

Replace:
"get_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",
"update_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",

with:
"get_policy_target_group": "rule:admin_or_owner or rule:shared_ptg",

This patch adds a new driver extension attribute: is_auto_ptg to
facilitate specification of rules in policy.json. This has the added
benefit of supportying the specification of a filter for auto_ptgs
when retrieving policy_target_groups.

Change-Id: I6d9e873acb2b1b3bee8d78a45527bd4d5d437eca
This commit is contained in:
Sumit Naiksatam 2016-12-14 16:32:32 -08:00
parent 56293d4f12
commit 7a326ca1c1
10 changed files with 181 additions and 17 deletions

View File

@ -212,11 +212,18 @@
"shared_scs": "field:servicechain_specs:shared=True",
"shared_sp": "field:service_profiles:shared=True",
"auto_ptg": "field:policy_target_groups:is_auto_ptg=True",
"non_auto_ptg_shared": "rule:admin_or_owner or rule:shared_ptg",
"non_auto_ptg": "rule:non_auto_ptg_shared and not rule:auto_ptg",
"admin_auto_ptg_shared": "rule:admin_only or rule:shared_ptg",
"admin_auto_ptg": "rule:admin_auto_ptg_shared and rule:auto_ptg",
"create_policy_target_group": "",
"create_policy_target_group:shared": "rule:admin_only",
"create_policy_target_group:service_management": "rule:admin_only",
"create_policy_target_group:enforce_service_chains": "rule:admin_only",
"get_policy_target_group": "rule:admin_or_owner or rule:shared_ptg",
"get_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",
"update_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",
"update_policy_target_group:shared": "rule:admin_only",
"create_l2_policy": "",

View File

@ -0,0 +1,37 @@
# 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.db import model_base
import sqlalchemy as sa
class ApicAutoPtgDB(model_base.BASEV2):
__tablename__ = 'gp_apic_auto_ptg'
policy_target_group_id = sa.Column(
sa.String(36), sa.ForeignKey('gp_policy_target_groups.id',
ondelete='CASCADE'), primary_key=True)
is_auto_ptg = sa.Column(sa.Boolean, default=False, nullable=False)
class ApicAutoPtgDBMixin(object):
def get_is_auto_ptg(self, session, policy_target_group_id):
row = (session.query(ApicAutoPtgDB).filter_by(
policy_target_group_id=policy_target_group_id).one())
return row['is_auto_ptg']
def set_is_auto_ptg(self, session, policy_target_group_id,
is_auto_ptg=False):
with session.begin(subtransactions=True):
row = ApicAutoPtgDB(policy_target_group_id=policy_target_group_id,
is_auto_ptg=is_auto_ptg)
session.add(row)

View File

@ -1 +1 @@
ef5a69e5bcc5
ce662ded3ba5

View File

@ -0,0 +1,45 @@
# 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.
"""is_auto_ptg
Revision ID: ce662ded3ba5
Revises: ef5a69e5bcc5
Create Date: 2016-12-15 16:13:23.836874
"""
# revision identifiers, used by Alembic.
revision = 'ce662ded3ba5'
down_revision = 'ef5a69e5bcc5'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'gp_apic_auto_ptg',
sa.Column('policy_target_group_id', sa.String(length=36),
nullable=False),
sa.Column('is_auto_ptg', sa.Boolean,
server_default=sa.sql.false(), nullable=False),
sa.ForeignKeyConstraint(
['policy_target_group_id'], ['gp_policy_target_groups.id'],
name='gp_apic_auto_ptg_fk_ptgid', ondelete='CASCADE'),
sa.PrimaryKeyConstraint('policy_target_group_id')
)
def downgrade():
pass

View File

@ -30,6 +30,10 @@ EXTENDED_ATTRIBUTES_2_0 = {
'intra_ptg_allow': {
'allow_post': True, 'allow_put': True, 'default': True,
'convert_to': attr.convert_to_boolean, 'is_visible': True},
'is_auto_ptg': {
'allow_post': False, 'allow_put': False,
'convert_to': attr.convert_to_boolean, 'is_visible': True,
'enforce_policy': True},
},
gp.POLICY_RULES: {
cisco_apic.DIST_NAMES: {

View File

@ -23,6 +23,7 @@ from neutron.common import constants as n_constants
from neutron.common import exceptions as n_exc
from neutron import context as n_context
from neutron import manager
from neutron import policy
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import helpers as log
@ -616,11 +617,12 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
@log.log_method_call
def create_policy_target_precommit(self, context):
ptg = self._get_policy_target_group(
context._plugin_context, context.current['policy_target_group_id'],
clean_session=False)
policy.enforce(context._plugin_context, 'get_policy_target_group',
ptg, pluralized='policy_target_groups')
if not context.current['port_id']:
ptg = self._db_plugin(
context._plugin).get_policy_target_group(
context._plugin_context,
context.current['policy_target_group_id'])
subnets = self._get_subnets(
context._plugin_context, {'id': ptg['subnets']},
clean_session=False)

View File

@ -15,7 +15,9 @@ from neutron import manager as n_manager
from oslo_log import log as logging
from gbpservice.neutron.db.grouppolicy.extensions import (
apic_intra_ptg_db as db)
apic_auto_ptg_db as auto_ptg_db)
from gbpservice.neutron.db.grouppolicy.extensions import (
apic_intra_ptg_db as intra_ptg_db)
from gbpservice.neutron.db.grouppolicy import group_policy_db as gp_db
from gbpservice.neutron.extensions import cisco_apic_gbp
from gbpservice.neutron.extensions import group_policy as gpolicy
@ -26,7 +28,8 @@ LOG = logging.getLogger(__name__)
class AIMExtensionDriver(api.ExtensionDriver,
db.ApicIntraPtgDBMixin):
intra_ptg_db.ApicIntraPtgDBMixin,
auto_ptg_db.ApicAutoPtgDBMixin):
_supported_extension_alias = cisco_apic_gbp.ALIAS
_extension_dict = cisco_apic_gbp.EXTENDED_ATTRIBUTES_2_0
@ -71,12 +74,19 @@ class AIMExtensionDriver(api.ExtensionDriver,
def process_create_policy_target_group(self, session, data, result):
self._set_intra_ptg_allow(session, data, result)
result['is_auto_ptg'] = bool(
gpolicy.AUTO_PTG_REGEX.match(result['id']))
self.set_is_auto_ptg(
session, policy_target_group_id=result['id'],
is_auto_ptg=result['is_auto_ptg'])
def process_update_policy_target_group(self, session, data, result):
self._set_intra_ptg_allow(session, data, result)
def extend_policy_target_group_dict(self, session, result):
self._extend_ptg_dict_with_intra_ptg_allow(session, result)
result['is_auto_ptg'] = self.get_is_auto_ptg(
session, policy_target_group_id=result['id'])
self._pd.extend_policy_target_group_dict(session, result)
def extend_policy_rule_dict(self, session, result):

View File

@ -15,6 +15,7 @@ from neutron._i18n import _LI
from neutron.common import exceptions as n_exc
from oslo_config import cfg
from oslo_log import log
from oslo_policy import policy as oslo_policy
from oslo_utils import excutils
import stevedore
@ -121,7 +122,8 @@ class PolicyDriverManager(stevedore.named.NamedExtensionManager):
for driver in drivers:
try:
getattr(driver.obj, method_name)(context)
except (gp_exc.GroupPolicyException, n_exc.NeutronException):
except (gp_exc.GroupPolicyException, n_exc.NeutronException,
oslo_policy.PolicyNotAuthorized):
with excutils.save_and_reraise_exception():
LOG.exception(
_LE("Policy driver '%(name)s' failed in %(method)s"),

View File

@ -20,10 +20,16 @@
"shared_scn": "field:servicechain_nodes:shared=True",
"shared_scs": "field:servicechain_specs:shared=True",
"shared_sp": "field:service_profiles:shared=True",
"auto_ptg": "field:policy_target_groups:is_auto_ptg=True",
"non_auto_ptg_shared": "rule:admin_or_owner or rule:shared_ptg",
"non_auto_ptg": "rule:non_auto_ptg_shared and not rule:auto_ptg",
"admin_auto_ptg_shared": "rule:admin_only or rule:shared_ptg",
"admin_auto_ptg": "rule:admin_auto_ptg_shared and rule:auto_ptg",
"create_policy_target_group": "",
"create_policy_target_group:enforce_service_chains": "rule:admin_only",
"get_policy_target_group": "rule:admin_or_owner or rule:shared_ptg",
"get_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",
"update_policy_target_group": "rule:admin_auto_ptg or rule:non_auto_ptg",
"create_policy_target_group:service_management": "rule:admin_only",
"create_l2_policy": "",

View File

@ -458,8 +458,14 @@ class AIMBaseTestCase(test_nr_base.CommonNeutronBaseTestCase,
self._validate_router_interface_created()
ptg_id = ptg['id']
ptg_show = self.show_policy_target_group(
ptg_id, expected_res_status=200)['policy_target_group']
if ptg['id'].startswith(aimd.AUTO_PTG_PREFIX):
# the test policy.json restricts auto-ptg access to admin
ptg_show = self.show_policy_target_group(
ptg_id, is_admin_context=True,
expected_res_status=200)['policy_target_group']
else:
ptg_show = self.show_policy_target_group(
ptg_id, expected_res_status=200)['policy_target_group']
aim_epg_name = self.driver.apic_epg_name_for_policy_target_group(
self._neutron_context.session, ptg_id)
aim_tenant_name = str(self.name_mapper.tenant(
@ -1142,8 +1148,10 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
self.assertEqual(aimd.AUTO_PTG_NAME_PREFIX % l2p_id, str(ptg['name']))
self.assertEqual(shared, ptg['shared'])
prs_lists = self._get_provided_consumed_prs_lists(shared)
# the test policy.json restricts auto-ptg access to admin
ptg = self.update_policy_target_group(
ptg['id'], expected_res_status=webob.exc.HTTPOk.code,
ptg['id'], is_admin_context=True,
expected_res_status=webob.exc.HTTPOk.code,
name='new name', description='something-else',
provided_policy_rule_sets={prs_lists['provided']['id']:
'scope'},
@ -1151,8 +1159,9 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
'scope'})['policy_target_group']
self._test_policy_target_group_aim_mappings(
ptg, prs_lists, l2p)
# the test policy.json restricts auto-ptg access to admin
self.update_policy_target_group(
ptg['id'], shared=(not shared),
ptg['id'], is_admin_context=True, shared=(not shared),
expected_res_status=webob.exc.HTTPBadRequest.code)
# Auto PTG cannot be deleted by user
res = self.delete_policy_target_group(
@ -1167,6 +1176,7 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
aim_epg_display_name = aim_epg.display_name
ptg = self.update_policy_target_group(
ptg['id'], expected_res_status=webob.exc.HTTPOk.code,
is_admin_context=True,
name='new name', description='something-else',
provided_policy_rule_sets={},
consumed_policy_rule_sets={})['policy_target_group']
@ -1245,9 +1255,11 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
self._test_epg_policy_enforcement_attr(ptg)
auto_ptg_id = self.driver._get_auto_ptg_id(ptg['l2_policy_id'])
self.show_policy_target_group(
auto_ptg_id, expected_res_status=200)['policy_target_group']
self._test_epg_policy_enforcement_attr(ptg)
# the test policy.json restricts auto-ptg access to admin
auto_ptg = self.show_policy_target_group(
auto_ptg_id, is_admin_context=True,
expected_res_status=200)['policy_target_group']
self._test_epg_policy_enforcement_attr(auto_ptg)
self.delete_policy_target_group(ptg_id, expected_res_status=204)
self.show_policy_target_group(ptg_id, expected_res_status=404)
@ -1259,6 +1271,45 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
self.assertEqual([], self._plugin.get_subnetpools(self._context))
self.assertEqual([], self._l3_plugin.get_routers(self._context))
def test_auto_ptg_rbac(self):
ptg = self.create_policy_target_group()['policy_target_group']
# non-admin can create pt on non-auto-ptg
self.create_policy_target(policy_target_group_id=ptg['id'],
expected_res_status=201)
# admin can create pt on non-auto-ptg
self.create_policy_target(policy_target_group_id=ptg['id'],
is_admin_context=True,
expected_res_status=201)
# non-admin can retrieve and update non-auto-ptg
self.show_policy_target_group(ptg['id'], expected_res_status=200)
self.update_policy_target_group(
ptg['id'], expected_res_status=200, name='new_name')
# admin can retrieve and update non-auto-ptg
self.show_policy_target_group(ptg['id'], is_admin_context=True,
expected_res_status=200)
self.update_policy_target_group(
ptg['id'], is_admin_context=True, expected_res_status=200,
name='new_name')
auto_ptg_id = self.driver._get_auto_ptg_id(ptg['l2_policy_id'])
# non-admin cannot retrieve or update auto-ptg
self.show_policy_target_group(auto_ptg_id, expected_res_status=404)
self.update_policy_target_group(
auto_ptg_id, expected_res_status=403, name='new_name')
# admin can retrieve and update auto-ptg
self.show_policy_target_group(auto_ptg_id, is_admin_context=True,
expected_res_status=200)
self.update_policy_target_group(
auto_ptg_id, is_admin_context=True, expected_res_status=200,
name='new_name')
# admin can create pt on auto-ptg
self.create_policy_target(
policy_target_group_id=auto_ptg_id, is_admin_context=True,
expected_res_status=201)
# non-admin cannot create pt on auto-ptg
self.create_policy_target(policy_target_group_id=auto_ptg_id,
expected_res_status=403)
class TestL2PolicyRollback(TestL2PolicyBase):