Extending security-group ingress rule

This adds an extension to the security-group API, using this extension will
allow a user to define rules with the notation of local-prefix-ip, which
matches on the destination address of packets going into the port.
One may use this extended API in order to specify a specific set of
multicast groups addresses in which a port (or group of ports) should
be allowed to accept packets from.

Change-Id: I9756cb27395b7b936dbfa94f403d98ac43c2e872
This commit is contained in:
Roey Chen 2015-06-18 02:33:36 -07:00
parent 42342b3cfa
commit c60f22384c
8 changed files with 335 additions and 12 deletions

View File

@ -0,0 +1,67 @@
# Copyright 2016 VMware, Inc.
# All Rights Reserved
#
# 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 sqlalchemy.orm import exc
from neutron.api.v2 import attributes as attr
from neutron.db import securitygroups_db as secgroup_db
from neutron_lib import exceptions as nexception
from vmware_nsx._i18n import _
from vmware_nsx.db import nsxv_models
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as ext_loip
class NotIngressRule(nexception.BadRequest):
message = _("Specifying local_ip_prefix is supported "
"with ingress rules only.")
class ExtendedSecurityGroupRuleMixin(object):
def _check_local_ip_prefix(self, context, rule):
rule_specify_local_ip_prefix = attr.is_attr_set(
rule.get(ext_loip.LOCAL_IP_PREFIX))
if rule_specify_local_ip_prefix and rule['direction'] != 'ingress':
raise NotIngressRule()
return rule_specify_local_ip_prefix
def _save_extended_rule_properties(self, context, rule):
if not attr.is_attr_set(rule.get(ext_loip.LOCAL_IP_PREFIX)):
return
with context.session.begin(subtransactions=True):
properties = nsxv_models.NsxvExtendedSecurityGroupRuleProperties(
rule_id=rule['id'],
local_ip_prefix=rule[ext_loip.LOCAL_IP_PREFIX])
context.session.add(properties)
def _get_security_group_rule_properties(self, context, sgr):
try:
properties = (context.session.query(
nsxv_models.NsxvExtendedSecurityGroupRuleProperties).filter_by(
rule_id=sgr['id']).one())
except exc.NoResultFound:
sgr[ext_loip.LOCAL_IP_PREFIX] = None
else:
sgr[ext_loip.LOCAL_IP_PREFIX] = properties.local_ip_prefix
return sgr
def _make_security_group_rule_dict(self, rule_db, fields=None):
res = secgroup_db.SecurityGroupDbMixin._make_security_group_rule_dict(
self, rule_db, fields=None)
if rule_db.ext_properties:
res[ext_loip.LOCAL_IP_PREFIX] = (
rule_db.ext_properties.local_ip_prefix)
else:
res[ext_loip.LOCAL_IP_PREFIX] = None
return self._fields(res, fields)

View File

@ -1 +1 @@
20483029f1ff 4c45bcadccf9

View File

@ -0,0 +1,38 @@
# Copyright 2016 VMware, 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.
"""extend_secgroup_rule
Revision ID: 4c45bcadccf9
Revises: 20483029f1ff
Create Date: 2016-03-01 06:12:09.450116
"""
# revision identifiers, used by Alembic.
revision = '4c45bcadccf9'
down_revision = '20483029f1ff'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'nsxv_extended_security_group_rule_properties',
sa.Column('rule_id', sa.String(36), nullable=False),
sa.Column('local_ip_prefix', sa.String(255), nullable=False),
sa.ForeignKeyConstraint(['rule_id'], ['securitygrouprules.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('rule_id'))

View File

@ -21,6 +21,7 @@ from sqlalchemy import orm
from neutron.db import l3_db from neutron.db import l3_db
from neutron.db import model_base from neutron.db import model_base
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import securitygroups_db
from vmware_nsx.common import nsxv_constants from vmware_nsx.common import nsxv_constants
@ -327,3 +328,23 @@ class NsxvSubnetExtAttributes(model_base.BASEV2):
models_v2.Subnet, models_v2.Subnet,
backref=orm.backref("nsxv_subnet_attributes", lazy='joined', backref=orm.backref("nsxv_subnet_attributes", lazy='joined',
uselist=False, cascade='delete')) uselist=False, cascade='delete'))
class NsxvExtendedSecurityGroupRuleProperties(model_base.BASEV2):
"""Persist security group rule properties for the
extended-security-group-rule extension.
"""
__tablename__ = 'nsxv_extended_security_group_rule_properties'
rule_id = sa.Column(sa.String(36),
sa.ForeignKey('securitygrouprules.id',
ondelete='CASCADE'),
primary_key=True,
nullable=False)
local_ip_prefix = sa.Column(sa.String(255), nullable=False)
rule = orm.relationship(
securitygroups_db.SecurityGroupRule,
backref=orm.backref('ext_properties', lazy='joined',
uselist=False, cascade='delete'))

View File

@ -0,0 +1,63 @@
# Copyright 2016 VMware, Inc. All rights reserved.
#
# 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.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.extensions import securitygroup
LOCAL_IP_PREFIX = 'local_ip_prefix'
RESOURCE_ATTRIBUTE_MAP = {
'security_group_rules': {
LOCAL_IP_PREFIX: {
'allow_post': True,
'allow_put': False,
'convert_to': securitygroup.convert_ip_prefix_to_cidr,
'default': attr.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True}
}
}
class Secgroup_rule_local_ip_prefix(extensions.ExtensionDescriptor):
"""Extension class to add support for specifying local-ip-prefix in a
security-group rule.
"""
@classmethod
def get_name(cls):
return "Security Group rule local ip prefix"
@classmethod
def get_alias(cls):
return "secgroup-rule-local-ip-prefix"
@classmethod
def get_description(cls):
return ("Enable to specify the 'local-ip-prefix' when creating a "
"security-group rule.")
@classmethod
def get_updated(cls):
return "2016-03-01T10:00:00-00:00"
def get_required_extensions(self):
return ["security-group"]
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -63,6 +63,8 @@ from vmware_nsx.common import locking
from vmware_nsx.common import nsx_constants from vmware_nsx.common import nsx_constants
from vmware_nsx.common import nsxv_constants from vmware_nsx.common import nsxv_constants
from vmware_nsx.common import utils as c_utils from vmware_nsx.common import utils as c_utils
from vmware_nsx.db import (
extended_security_group_rule as extend_sg_rule)
from vmware_nsx.db import ( from vmware_nsx.db import (
routertype as rt_rtr) routertype as rt_rtr)
from vmware_nsx.db import db as nsx_db from vmware_nsx.db import db as nsx_db
@ -74,6 +76,7 @@ from vmware_nsx.extensions import (
vnicindex as ext_vnic_idx) vnicindex as ext_vnic_idx)
from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain
from vmware_nsx.extensions import routersize from vmware_nsx.extensions import routersize
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as ext_loip
from vmware_nsx.plugins.nsx_v import managers from vmware_nsx.plugins.nsx_v import managers
from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy
from vmware_nsx.plugins.nsx_v.vshield.common import ( from vmware_nsx.plugins.nsx_v.vshield.common import (
@ -99,6 +102,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
l3_gwmode_db.L3_NAT_db_mixin, l3_gwmode_db.L3_NAT_db_mixin,
portbindings_db.PortBindingMixin, portbindings_db.PortBindingMixin,
portsecurity_db.PortSecurityDbMixin, portsecurity_db.PortSecurityDbMixin,
extend_sg_rule.ExtendedSecurityGroupRuleMixin,
securitygroups_db.SecurityGroupDbMixin, securitygroups_db.SecurityGroupDbMixin,
vnic_index_db.VnicIndexDbMixin, vnic_index_db.VnicIndexDbMixin,
dns_db.DNSDbMixin): dns_db.DNSDbMixin):
@ -117,6 +121,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"extraroute", "extraroute",
"router", "router",
"security-group", "security-group",
"secgroup-rule-local-ip-prefix",
"nsxv-router-type", "nsxv-router-type",
"nsxv-router-size", "nsxv-router-size",
"vnic-index", "vnic-index",
@ -2029,9 +2034,12 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Get source and destination containers from rule # Get source and destination containers from rule
if rule['direction'] == 'ingress': if rule['direction'] == 'ingress':
if rule.get(ext_loip.LOCAL_IP_PREFIX):
dest = self.nsx_sg_utils.get_remote_container(
None, rule[ext_loip.LOCAL_IP_PREFIX])
src = self.nsx_sg_utils.get_remote_container( src = self.nsx_sg_utils.get_remote_container(
remote_nsx_sg_id, rule['remote_ip_prefix']) remote_nsx_sg_id, rule['remote_ip_prefix'])
dest = self.nsx_sg_utils.get_container(nsx_sg_id) dest = dest or self.nsx_sg_utils.get_container(nsx_sg_id)
flags['direction'] = 'in' flags['direction'] = 'in'
else: else:
dest = self.nsx_sg_utils.get_remote_container( dest = self.nsx_sg_utils.get_remote_container(
@ -2075,13 +2083,16 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
:param security_group_rules: list of rules to create :param security_group_rules: list of rules to create
""" """
sg_rules = security_group_rules['security_group_rules']
ruleids = set() ruleids = set()
nsx_rules = [] nsx_rules = []
self._validate_security_group_rules(context, security_group_rules) self._validate_security_group_rules(context, security_group_rules)
# Translating Neutron rules to Nsx DFW rules # Translating Neutron rules to Nsx DFW rules
for r in security_group_rules['security_group_rules']: for r in sg_rules:
rule = r['security_group_rule'] rule = r['security_group_rule']
if not self._check_local_ip_prefix(context, rule):
rule[ext_loip.LOCAL_IP_PREFIX] = None
rule['id'] = uuidutils.generate_uuid() rule['id'] = uuidutils.generate_uuid()
ruleids.add(rule['id']) ruleids.add(rule['id'])
nsx_rules.append(self._create_nsx_rule(context, rule)) nsx_rules.append(self._create_nsx_rule(context, rule))
@ -2110,6 +2121,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
if neutron_rule_id in ruleids: if neutron_rule_id in ruleids:
nsxv_db.add_neutron_nsx_rule_mapping( nsxv_db.add_neutron_nsx_rule_mapping(
context.session, neutron_rule_id, nsx_rule_id) context.session, neutron_rule_id, nsx_rule_id)
for i, r in enumerate(sg_rules):
self._save_extended_rule_properties(context, rule)
self._get_security_group_rule_properties(context,
new_rule_list[i])
except Exception: except Exception:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
for nsx_rule_id in [p['nsx_id'] for p in rule_pairs]: for nsx_rule_id in [p['nsx_id'] for p in rule_pairs]:

View File

@ -0,0 +1,124 @@
# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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.
import mock
import webob.exc
from oslo_utils import uuidutils
from neutron.api.v2 import attributes
from neutron.db import db_base_plugin_v2
from neutron.db import securitygroups_db
from neutron import manager
from neutron.tests.unit.extensions import test_securitygroup
from neutron_lib import constants as const
from vmware_nsx.db import extended_security_group_rule as ext_rule_db
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as ext_loip
from vmware_nsx.plugins.nsx_v.vshield import securitygroup_utils
from vmware_nsx.tests.unit.nsx_v import test_plugin as test_nsxv_plugin
PLUGIN_NAME = ('vmware_nsx.tests.unit.extensions.'
'test_secgroup_rule_local_ip_prefix.ExtendedRuleTestPlugin')
_uuid = uuidutils.generate_uuid
class ExtendedRuleTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
ext_rule_db.ExtendedSecurityGroupRuleMixin,
securitygroups_db.SecurityGroupDbMixin):
supported_extension_aliases = ["security-group",
"secgroup-rule-local-ip-prefix"]
def create_security_group_rule(self, context, security_group_rule):
rule = security_group_rule['security_group_rule']
rule['id'] = _uuid()
self._check_local_ip_prefix(context, rule)
with context.session.begin(subtransactions=True):
res = super(ExtendedRuleTestPlugin,
self).create_security_group_rule(
context, security_group_rule)
self._save_extended_rule_properties(context, rule)
self._get_security_group_rule_properties(context, res)
return res
class LocalIPPrefixExtTestCase(test_securitygroup.SecurityGroupDBTestCase):
def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None):
super(LocalIPPrefixExtTestCase, self).setUp(
plugin=plugin, ext_mgr=ext_mgr)
attributes.RESOURCE_ATTRIBUTE_MAP['security_group_rules'].update(
ext_loip.RESOURCE_ATTRIBUTE_MAP['security_group_rules'])
def tearDown(self):
# Remove attributes which were written to global attr map, they may
# interfer with tests for other plugins which doesn't support this
# extension.
del attributes.RESOURCE_ATTRIBUTE_MAP[
'security_group_rules']['local_ip_prefix']
super(LocalIPPrefixExtTestCase, self).tearDown()
def _build_ingress_rule_with_local_ip_prefix(self, security_group_id,
local_ip_prefix,
remote_ip_prefix,
direction='ingress'):
rule = self._build_security_group_rule(
security_group_id, remote_ip_prefix=remote_ip_prefix,
direction=direction, proto=const.PROTO_NAME_UDP)
rule['security_group_rule']['local_ip_prefix'] = local_ip_prefix
return rule
def test_raise_rule_not_ingress_when_local_ip_specified(self):
local_ip_prefix = '239.255.0.0/16'
remote_ip_prefix = '10.0.0.0/24'
with self.security_group() as sg:
rule = self._build_ingress_rule_with_local_ip_prefix(
sg['security_group']['id'], local_ip_prefix,
remote_ip_prefix, direction='egress')
res = self._create_security_group_rule(self.fmt, rule)
self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int)
def test_create_rule_with_local_ip_prefix(self):
local_ip_prefix = '239.255.0.0/16'
remote_ip_prefix = '10.0.0.0/24'
with self.security_group() as sg:
rule = self._build_ingress_rule_with_local_ip_prefix(
sg['security_group']['id'], local_ip_prefix, remote_ip_prefix)
res = self._make_security_group_rule(self.fmt, rule)
self.assertEqual(local_ip_prefix,
res['security_group_rule']['local_ip_prefix'])
class TestNsxVExtendedSGRule(test_nsxv_plugin.NsxVSecurityGroupsTestCase,
LocalIPPrefixExtTestCase):
def test_create_rule_with_local_ip_prefix(self):
sg_utils = securitygroup_utils.NsxSecurityGroupUtils(None)
local_ip_prefix = '239.255.0.0/16'
plugin = manager.NeutronManager.get_plugin()
dest = {'type': 'Ipv4Address', 'value': local_ip_prefix}
def _assert_destination_as_expected(*args, **kwargs):
self.assertEqual(dest, kwargs['destination'])
return sg_utils.get_rule_config(*args, **kwargs)
plugin.nsx_sg_utils.get_rule_config = mock.Mock(
side_effect=sg_utils.get_rule_config)
super(TestNsxVExtendedSGRule,
self).test_create_rule_with_local_ip_prefix()
plugin.nsx_sg_utils.get_rule_config.assert_called_with(
destination=dest, applied_to_ids=mock.ANY, name=mock.ANY,
services=mock.ANY, source=mock.ANY, flags=mock.ANY)

View File

@ -30,7 +30,6 @@ from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as pnet from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as secgrp from neutron.extensions import securitygroup as secgrp
from neutron import manager from neutron import manager
from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import _test_extension_portbindings as test_bindings
import neutron.tests.unit.db.test_allowedaddresspairs_db as test_addr_pair import neutron.tests.unit.db.test_allowedaddresspairs_db as test_addr_pair
import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin
@ -49,14 +48,10 @@ from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsxv_exc from vmware_nsx.common import exceptions as nsxv_exc
from vmware_nsx.common import nsx_constants from vmware_nsx.common import nsx_constants
from vmware_nsx.db import nsxv_db from vmware_nsx.db import nsxv_db
from vmware_nsx.extensions import ( from vmware_nsx.extensions import routersize as router_size
routersize as router_size) from vmware_nsx.extensions import routertype as router_type
from vmware_nsx.extensions import ( from vmware_nsx.extensions import vnicindex as ext_vnic_idx
routertype as router_type) from vmware_nsx.plugins.nsx_v.vshield.common import constants as vcns_const
from vmware_nsx.extensions import (
vnicindex as ext_vnic_idx)
from vmware_nsx.plugins.nsx_v.vshield.common import (
constants as vcns_const)
from vmware_nsx.plugins.nsx_v.vshield import edge_utils from vmware_nsx.plugins.nsx_v.vshield import edge_utils
from vmware_nsx.tests import unit as vmware from vmware_nsx.tests import unit as vmware
from vmware_nsx.tests.unit.extensions import test_vnic_index from vmware_nsx.tests.unit.extensions import test_vnic_index