Merge "Add portsecurity extension support"

This commit is contained in:
Jenkins 2015-03-18 16:48:40 +00:00 committed by Gerrit Code Review
commit 87a534f9b0
14 changed files with 531 additions and 162 deletions

View File

@ -33,3 +33,4 @@ INVALID_DROP = ("Drop packets that appear related to an existing connection "
ALLOW_ASSOC = ('Direct packets associated with a known session to the RETURN '
'chain.')
IPV6_RA_ALLOW = 'Allow IPv6 ICMP traffic to allow RA packets.'
PORT_SEC_ACCEPT = 'Accept all packets when port security is disabled.'

View File

@ -24,6 +24,7 @@ from neutron.agent.linux import iptables_comments as ic
from neutron.agent.linux import iptables_manager
from neutron.common import constants
from neutron.common import ipv6_utils
from neutron.extensions import portsecurity as psec
from neutron.i18n import _LI
@ -57,9 +58,11 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
self.ipset = ipset_manager.IpsetManager(namespace=namespace)
# list of port which has security group
self.filtered_ports = {}
self.unfiltered_ports = {}
self._add_fallback_chain_v4v6()
self._defer_apply = False
self._pre_defer_filtered_ports = None
self._pre_defer_unfiltered_ports = None
# List of security group rules for ports residing on this host
self.sg_rules = {}
self.pre_sg_rules = None
@ -71,7 +74,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
@property
def ports(self):
return self.filtered_ports
return dict(self.filtered_ports, **self.unfiltered_ports)
def update_security_group_rules(self, sg_id, sg_rules):
LOG.debug("Update rules of security group (%s)", sg_id)
@ -81,42 +84,72 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
LOG.debug("Update members of security group (%s)", sg_id)
self.sg_members[sg_id] = collections.defaultdict(list, sg_members)
def _ps_enabled(self, port):
return port.get(psec.PORTSECURITY, True)
def _set_ports(self, port):
if not self._ps_enabled(port):
self.unfiltered_ports[port['device']] = port
self.filtered_ports.pop(port['device'], None)
else:
self.filtered_ports[port['device']] = port
self.unfiltered_ports.pop(port['device'], None)
def _unset_ports(self, port):
self.unfiltered_ports.pop(port['device'], None)
self.filtered_ports.pop(port['device'], None)
def prepare_port_filter(self, port):
LOG.debug("Preparing device (%s) filter", port['device'])
self._remove_chains()
self.filtered_ports[port['device']] = port
self._set_ports(port)
# each security group has it own chains
self._setup_chains()
self.iptables.apply()
def update_port_filter(self, port):
LOG.debug("Updating device (%s) filter", port['device'])
if port['device'] not in self.filtered_ports:
if port['device'] not in self.ports:
LOG.info(_LI('Attempted to update port filter which is not '
'filtered %s'), port['device'])
return
self._remove_chains()
self.filtered_ports[port['device']] = port
self._set_ports(port)
self._setup_chains()
self.iptables.apply()
def remove_port_filter(self, port):
LOG.debug("Removing device (%s) filter", port['device'])
if not self.filtered_ports.get(port['device']):
if port['device'] not in self.ports:
LOG.info(_LI('Attempted to remove port filter which is not '
'filtered %r'), port)
return
self._remove_chains()
self.filtered_ports.pop(port['device'], None)
self._unset_ports(port)
self._setup_chains()
self.iptables.apply()
def _add_accept_rule_port_sec(self, port, direction):
self._update_port_sec_rules(port, direction, add=True)
def _remove_rule_port_sec(self, port, direction):
self._update_port_sec_rules(port, direction, add=False)
def _remove_rule_from_chain_v4v6(self, chain_name, ipv4_rules, ipv6_rules):
for rule in ipv4_rules:
self.iptables.ipv4['filter'].remove_rule(chain_name, rule)
for rule in ipv6_rules:
self.iptables.ipv6['filter'].remove_rule(chain_name, rule)
def _setup_chains(self):
"""Setup ingress and egress chain for a port."""
if not self._defer_apply:
self._setup_chains_apply(self.filtered_ports)
self._setup_chains_apply(self.filtered_ports,
self.unfiltered_ports)
def _setup_chains_apply(self, ports):
def _setup_chains_apply(self, ports, unfiltered_ports):
self._add_chain_by_name_v4v6(SG_CHAIN)
for port in ports.values():
self._setup_chain(port, INGRESS_DIRECTION)
@ -124,16 +157,24 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
self.iptables.ipv4['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
self.iptables.ipv6['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
for port in unfiltered_ports.values():
self._add_accept_rule_port_sec(port, INGRESS_DIRECTION)
self._add_accept_rule_port_sec(port, EGRESS_DIRECTION)
def _remove_chains(self):
"""Remove ingress and egress chain for a port."""
if not self._defer_apply:
self._remove_chains_apply(self.filtered_ports)
self._remove_chains_apply(self.filtered_ports,
self.unfiltered_ports)
def _remove_chains_apply(self, ports):
def _remove_chains_apply(self, ports, unfiltered_ports):
for port in ports.values():
self._remove_chain(port, INGRESS_DIRECTION)
self._remove_chain(port, EGRESS_DIRECTION)
self._remove_chain(port, SPOOF_FILTER)
for port in unfiltered_ports.values():
self._remove_rule_port_sec(port, INGRESS_DIRECTION)
self._remove_rule_port_sec(port, EGRESS_DIRECTION)
self._remove_chain_by_name_v4v6(SG_CHAIN)
def _setup_chain(self, port, DIRECTION):
@ -173,6 +214,30 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
def _get_device_name(self, port):
return port['device']
def _update_port_sec_rules(self, port, direction, add=False):
# add/remove rules in FORWARD and INPUT chain
device = self._get_device_name(port)
jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
'-j ACCEPT' % (self.IPTABLES_DIRECTION[direction],
device)]
if add:
self._add_rules_to_chain_v4v6(
'FORWARD', jump_rule, jump_rule, comment=ic.PORT_SEC_ACCEPT)
else:
self._remove_rule_from_chain_v4v6('FORWARD', jump_rule, jump_rule)
if direction == EGRESS_DIRECTION:
jump_rule = ['-m physdev --%s %s --physdev-is-bridged '
'-j ACCEPT' % (self.IPTABLES_DIRECTION[direction],
device)]
if add:
self._add_rules_to_chain_v4v6('INPUT', jump_rule, jump_rule,
comment=ic.PORT_SEC_ACCEPT)
else:
self._remove_rule_from_chain_v4v6(
'INPUT', jump_rule, jump_rule)
def _add_chain(self, port, direction):
chain_name = self._port_chain_name(port, direction)
self._add_chain_by_name_v4v6(chain_name)
@ -496,6 +561,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
if not self._defer_apply:
self.iptables.defer_apply_on()
self._pre_defer_filtered_ports = dict(self.filtered_ports)
self._pre_defer_unfiltered_ports = dict(self.unfiltered_ports)
self.pre_sg_members = dict(self.sg_members)
self.pre_sg_rules = dict(self.sg_rules)
self._defer_apply = True
@ -587,11 +653,14 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
def filter_defer_apply_off(self):
if self._defer_apply:
self._defer_apply = False
self._remove_chains_apply(self._pre_defer_filtered_ports)
self._setup_chains_apply(self.filtered_ports)
self._remove_chains_apply(self._pre_defer_filtered_ports,
self._pre_defer_unfiltered_ports)
self._setup_chains_apply(self.filtered_ports,
self.unfiltered_ports)
self.iptables.defer_apply_off()
self._remove_unused_security_group_info()
self._pre_defer_filtered_ports = None
self._pre_defer_unfiltered_ports = None
class OVSHybridIptablesFirewallDriver(IptablesFirewallDriver):

View File

@ -0,0 +1,44 @@
# Copyright 2015 OpenStack Foundation
#
# 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.
#
"""add port-security in ml2
Revision ID: 35a0f3365720
Revises: 341ee8a4ccb5
Create Date: 2014-09-30 09:41:14.146519
"""
# revision identifiers, used by Alembic.
revision = '35a0f3365720'
down_revision = '341ee8a4ccb5'
from alembic import op
def upgrade():
op.execute('INSERT INTO networksecuritybindings (network_id, '
'port_security_enabled) SELECT id, True FROM networks '
'WHERE id NOT IN (SELECT network_id FROM '
'networksecuritybindings);')
op.execute('INSERT INTO portsecuritybindings (port_id, '
'port_security_enabled) SELECT id, True FROM ports '
'WHERE id NOT IN (SELECT port_id FROM '
'portsecuritybindings);')
def downgrade():
pass

View File

@ -1 +1 @@
341ee8a4ccb5
35a0f3365720

View File

@ -12,71 +12,18 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc
from neutron.api.v2 import attributes as attrs
from neutron.db import db_base_plugin_v2
from neutron.db import model_base
from neutron.db import models_v2
from neutron.db import portsecurity_db_common
from neutron.extensions import portsecurity as psec
LOG = logging.getLogger(__name__)
class PortSecurityBinding(model_base.BASEV2):
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
port_security_enabled = sa.Column(sa.Boolean(), nullable=False)
# Add a relationship to the Port model in order to be to able to
# instruct SQLAlchemy to eagerly load port security binding
port = orm.relationship(
models_v2.Port,
backref=orm.backref("port_security", uselist=False,
cascade='delete', lazy='joined'))
class NetworkSecurityBinding(model_base.BASEV2):
network_id = sa.Column(sa.String(36),
sa.ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
port_security_enabled = sa.Column(sa.Boolean(), nullable=False)
# Add a relationship to the Port model in order to be able to instruct
# SQLAlchemy to eagerly load default port security setting for ports
# on this network
network = orm.relationship(
models_v2.Network,
backref=orm.backref("port_security", uselist=False,
cascade='delete', lazy='joined'))
class PortSecurityDbMixin(object):
"""Mixin class to add port security."""
def _process_network_port_security_create(
self, context, network_req, network_res):
with context.session.begin(subtransactions=True):
db = NetworkSecurityBinding(
network_id=network_res['id'],
port_security_enabled=network_req[psec.PORTSECURITY])
context.session.add(db)
network_res[psec.PORTSECURITY] = network_req[psec.PORTSECURITY]
return self._make_network_port_security_dict(db)
def _process_port_port_security_create(
self, context, port_req, port_res):
with context.session.begin(subtransactions=True):
db = PortSecurityBinding(
port_id=port_res['id'],
port_security_enabled=port_req[psec.PORTSECURITY])
context.session.add(db)
port_res[psec.PORTSECURITY] = port_req[psec.PORTSECURITY]
return self._make_port_security_dict(db)
class PortSecurityDbMixin(portsecurity_db_common.PortSecurityDbCommon):
# Register dict extend functions for ports and networks
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attrs.NETWORKS, ['_extend_port_security_dict'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attrs.PORTS, ['_extend_port_security_dict'])
def _extend_port_security_dict(self, response_data, db_data):
if ('port-security' in
@ -84,63 +31,6 @@ class PortSecurityDbMixin(object):
psec_value = db_data['port_security'][psec.PORTSECURITY]
response_data[psec.PORTSECURITY] = psec_value
def _get_network_security_binding(self, context, network_id):
try:
query = self._model_query(context, NetworkSecurityBinding)
binding = query.filter(
NetworkSecurityBinding.network_id == network_id).one()
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
return binding[psec.PORTSECURITY]
def _get_port_security_binding(self, context, port_id):
try:
query = self._model_query(context, PortSecurityBinding)
binding = query.filter(
PortSecurityBinding.port_id == port_id).one()
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
return binding[psec.PORTSECURITY]
def _process_port_port_security_update(
self, context, port_req, port_res):
if psec.PORTSECURITY in port_req:
port_security_enabled = port_req[psec.PORTSECURITY]
else:
return
try:
query = self._model_query(context, PortSecurityBinding)
port_id = port_res['id']
binding = query.filter(
PortSecurityBinding.port_id == port_id).one()
binding.port_security_enabled = port_security_enabled
port_res[psec.PORTSECURITY] = port_security_enabled
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
def _process_network_port_security_update(
self, context, network_req, network_res):
if psec.PORTSECURITY in network_req:
port_security_enabled = network_req[psec.PORTSECURITY]
else:
return
try:
query = self._model_query(context, NetworkSecurityBinding)
network_id = network_res['id']
binding = query.filter(
NetworkSecurityBinding.network_id == network_id).one()
binding.port_security_enabled = port_security_enabled
network_res[psec.PORTSECURITY] = port_security_enabled
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
def _make_network_port_security_dict(self, port_security, fields=None):
res = {'network_id': port_security['network_id'],
psec.PORTSECURITY: port_security[psec.PORTSECURITY]}
return self._fields(res, fields)
def _determine_port_security_and_has_ip(self, context, port):
"""Returns a tuple of booleans (port_security_enabled, has_ip).
@ -169,16 +59,5 @@ class PortSecurityDbMixin(object):
return (port_security_enabled, has_ip)
def _make_port_security_dict(self, port, fields=None):
res = {'port_id': port['port_id'],
psec.PORTSECURITY: port[psec.PORTSECURITY]}
return self._fields(res, fields)
def _ip_on_port(self, port):
return bool(port.get('fixed_ips'))
# Register dict extend functions for ports and networks
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attrs.NETWORKS, ['_extend_port_security_dict'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attrs.PORTS, ['_extend_port_security_dict'])

View File

@ -0,0 +1,139 @@
# Copyright 2013 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 oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc
from neutron.db import model_base
from neutron.db import models_v2
from neutron.extensions import portsecurity as psec
LOG = logging.getLogger(__name__)
class PortSecurityBinding(model_base.BASEV2):
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
port_security_enabled = sa.Column(sa.Boolean(), nullable=False)
# Add a relationship to the Port model in order to be to able to
# instruct SQLAlchemy to eagerly load port security binding
port = orm.relationship(
models_v2.Port,
backref=orm.backref("port_security", uselist=False,
cascade='delete', lazy='joined'))
class NetworkSecurityBinding(model_base.BASEV2):
network_id = sa.Column(sa.String(36),
sa.ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
port_security_enabled = sa.Column(sa.Boolean(), nullable=False)
# Add a relationship to the Port model in order to be able to instruct
# SQLAlchemy to eagerly load default port security setting for ports
# on this network
network = orm.relationship(
models_v2.Network,
backref=orm.backref("port_security", uselist=False,
cascade='delete', lazy='joined'))
class PortSecurityDbCommon(object):
"""Mixin class to add port security."""
def _process_network_port_security_create(
self, context, network_req, network_res):
with context.session.begin(subtransactions=True):
db = NetworkSecurityBinding(
network_id=network_res['id'],
port_security_enabled=network_req[psec.PORTSECURITY])
context.session.add(db)
network_res[psec.PORTSECURITY] = network_req[psec.PORTSECURITY]
return self._make_network_port_security_dict(db)
def _process_port_port_security_create(
self, context, port_req, port_res):
with context.session.begin(subtransactions=True):
db = PortSecurityBinding(
port_id=port_res['id'],
port_security_enabled=port_req[psec.PORTSECURITY])
context.session.add(db)
port_res[psec.PORTSECURITY] = port_req[psec.PORTSECURITY]
return self._make_port_security_dict(db)
def _get_network_security_binding(self, context, network_id):
try:
query = self._model_query(context, NetworkSecurityBinding)
binding = query.filter(
NetworkSecurityBinding.network_id == network_id).one()
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
return binding[psec.PORTSECURITY]
def _get_port_security_binding(self, context, port_id):
try:
query = self._model_query(context, PortSecurityBinding)
binding = query.filter(
PortSecurityBinding.port_id == port_id).one()
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
return binding[psec.PORTSECURITY]
def _process_port_port_security_update(
self, context, port_req, port_res):
if psec.PORTSECURITY in port_req:
port_security_enabled = port_req[psec.PORTSECURITY]
else:
return
try:
query = self._model_query(context, PortSecurityBinding)
port_id = port_res['id']
binding = query.filter(
PortSecurityBinding.port_id == port_id).one()
binding.port_security_enabled = port_security_enabled
port_res[psec.PORTSECURITY] = port_security_enabled
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
def _process_network_port_security_update(
self, context, network_req, network_res):
if psec.PORTSECURITY in network_req:
port_security_enabled = network_req[psec.PORTSECURITY]
else:
return
try:
query = self._model_query(context, NetworkSecurityBinding)
network_id = network_res['id']
binding = query.filter(
NetworkSecurityBinding.network_id == network_id).one()
binding.port_security_enabled = port_security_enabled
network_res[psec.PORTSECURITY] = port_security_enabled
except exc.NoResultFound:
raise psec.PortSecurityBindingNotFound()
def _make_network_port_security_dict(self, port_security, fields=None):
res = {'network_id': port_security['network_id'],
psec.PORTSECURITY: port_security[psec.PORTSECURITY]}
return self._fields(res, fields)
def _make_port_security_dict(self, port, fields=None):
res = {'port_id': port['port_id'],
psec.PORTSECURITY: port[psec.PORTSECURITY]}
return self._fields(res, fields)

View File

@ -0,0 +1,86 @@
# Copyright 2015 Intel Corporation.
# 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.v2 import attributes as attrs
from neutron.db import common_db_mixin
from neutron.db import portsecurity_db_common as ps_db_common
from neutron.extensions import portsecurity as psec
from neutron.i18n import _LI
from neutron.plugins.ml2 import driver_api as api
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class PortSecurityExtensionDriver(api.ExtensionDriver,
ps_db_common.PortSecurityDbCommon,
common_db_mixin.CommonDbMixin):
_supported_extension_alias = 'port-security'
def initialize(self):
LOG.info(_LI("PortSecurityExtensionDriver initialization complete"))
@property
def extension_alias(self):
return self._supported_extension_alias
def process_create_network(self, context, data, result):
# Create the network extension attributes.
if psec.PORTSECURITY in data:
self._process_network_port_security_create(context, data, result)
def process_update_network(self, context, data, result):
# Update the network extension attributes.
if psec.PORTSECURITY in data:
self._process_network_port_security_update(context, data, result)
def process_create_port(self, context, data, result):
# Create the port extension attributes.
data[psec.PORTSECURITY] = self._determine_port_security(context, data)
self._process_port_port_security_create(context, data, result)
def process_update_port(self, context, data, result):
if psec.PORTSECURITY in data:
self._process_port_port_security_update(
context, data, result)
def extend_network_dict(self, session, db_data, result):
self._extend_port_security_dict(result, db_data)
def extend_port_dict(self, session, db_data, result):
self._extend_port_security_dict(result, db_data)
def _extend_port_security_dict(self, response_data, db_data):
response_data[psec.PORTSECURITY] = (
db_data['port_security'][psec.PORTSECURITY])
def _determine_port_security(self, context, port):
"""Returns a boolean (port_security_enabled).
Port_security is the value associated with the port if one is present
otherwise the value associated with the network is returned.
"""
# we don't apply security groups for dhcp, router
if (port.get('device_owner') and
port['device_owner'].startswith('network:')):
return False
if attrs.is_attr_set(port.get(psec.PORTSECURITY)):
port_security_enabled = port[psec.PORTSECURITY]
else:
port_security_enabled = self._get_network_security_binding(
context, port['network_id'])
return port_security_enabled

View File

@ -57,7 +57,9 @@ from neutron.db import securitygroups_rpc_base as sg_db_rpc
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import portbindings
from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as provider
from neutron.extensions import securitygroup as ext_sg
from neutron.i18n import _LE, _LI, _LW
from neutron import manager
from neutron.openstack.common import uuidutils
@ -906,17 +908,39 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# the fact that an error occurred.
LOG.error(_LE("mechanism_manager.delete_subnet_postcommit failed"))
# TODO(yalei) - will be simplified after security group and address pair be
# converted to ext driver too.
def _portsec_ext_port_create_processing(self, context, port_data, port):
attrs = port[attributes.PORT]
port_security = ((port_data.get(psec.PORTSECURITY) is None) or
port_data[psec.PORTSECURITY])
# allowed address pair checks
if attributes.is_attr_set(attrs.get(addr_pair.ADDRESS_PAIRS)):
if not port_security:
raise addr_pair.AddressPairAndPortSecurityRequired()
else:
# remove ATTR_NOT_SPECIFIED
attrs[addr_pair.ADDRESS_PAIRS] = []
if port_security:
self._ensure_default_security_group_on_port(context, port)
elif attributes.is_attr_set(attrs.get(ext_sg.SECURITYGROUPS)):
raise psec.PortSecurityAndIPRequiredForSecurityGroups()
def _create_port_db(self, context, port):
attrs = port[attributes.PORT]
attrs['status'] = const.PORT_STATUS_DOWN
session = context.session
with session.begin(subtransactions=True):
self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port)
dhcp_opts = attrs.get(edo_ext.EXTRADHCPOPTS, [])
result = super(Ml2Plugin, self).create_port(context, port)
self.extension_manager.process_create_port(context, attrs, result)
self._portsec_ext_port_create_processing(context, result, port)
# sgids must be got after portsec checked with security group
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, result, sgids)
network = self.get_network(context, result['network_id'])
binding = db.add_port_binding(session, result['id'])
@ -993,6 +1017,47 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
resource_ids)
self._delete_objects(context, attributes.PORT, objects)
# TODO(yalei) - will be simplified after security group and address pair be
# converted to ext driver too.
def _portsec_ext_port_update_processing(self, updated_port, context, port,
id):
port_security = ((updated_port.get(psec.PORTSECURITY) is None) or
updated_port[psec.PORTSECURITY])
if port_security:
return
# check the address-pairs
if self._check_update_has_allowed_address_pairs(port):
# has address pairs in request
raise addr_pair.AddressPairAndPortSecurityRequired()
elif (not
self._check_update_deletes_allowed_address_pairs(port)):
# not a request for deleting the address-pairs
updated_port[addr_pair.ADDRESS_PAIRS] = (
self.get_allowed_address_pairs(context, id))
# check if address pairs has been in db, if address pairs could
# be put in extension driver, we can refine here.
if updated_port[addr_pair.ADDRESS_PAIRS]:
raise addr_pair.AddressPairAndPortSecurityRequired()
# checks if security groups were updated adding/modifying
# security groups, port security is set
if self._check_update_has_security_groups(port):
raise psec.PortSecurityAndIPRequiredForSecurityGroups()
elif (not
self._check_update_deletes_security_groups(port)):
# Update did not have security groups passed in. Check
# that port does not have any security groups already on it.
filters = {'port_id': [id]}
security_groups = (
super(Ml2Plugin, self)._get_port_security_group_bindings(
context, filters)
)
if security_groups:
raise psec.PortSecurityPortHasSecurityGroup()
def update_port(self, context, id, port):
attrs = port[attributes.PORT]
need_port_update_notify = False
@ -1015,6 +1080,14 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
port)
self.extension_manager.process_update_port(context, attrs,
updated_port)
self._portsec_ext_port_update_processing(updated_port, context,
port, id)
if (psec.PORTSECURITY in attrs) and (
original_port[psec.PORTSECURITY] !=
updated_port[psec.PORTSECURITY]):
need_port_update_notify = True
if addr_pair.ADDRESS_PAIRS in attrs:
need_port_update_notify |= (
self.update_address_pairs_on_port(context, id, port,

View File

@ -16,12 +16,26 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_firewall
from neutron.agent import securitygroups_rpc as sg_cfg
from neutron.tests.functional.agent.linux import base
from neutron.tests.functional.agent.linux import helpers
from oslo_config import cfg
class IptablesFirewallTestCase(base.BaseBridgeTestCase):
MAC_REAL = "fa:16:3e:9a:2f:49"
MAC_SPOOFED = "fa:16:3e:9a:2f:48"
FAKE_SECURITY_GROUP_ID = "fake_sg_id"
def _set_src_mac(self, mac):
self.src_veth.link.set_down()
self.src_veth.link.set_address(mac)
self.src_veth.link.set_up()
def setUp(self):
cfg.CONF.register_opts(sg_cfg.security_group_opts, 'SECURITYGROUP')
super(IptablesFirewallTestCase, self).setUp()
self.bridge = self.create_bridge()
@ -40,8 +54,39 @@ class IptablesFirewallTestCase(base.BaseBridgeTestCase):
self.firewall = iptables_firewall.IptablesFirewallDriver(
namespace=self.bridge.namespace)
# TODO(yamahata): add tests...
self._set_src_mac(self.MAC_REAL)
self.src_port = {'admin_state_up': True,
'device': self.src_br_veth.name,
'device_owner': 'compute:None',
'fixed_ips': [self.SRC_ADDRESS],
'mac_address': self.MAC_REAL,
'port_security_enabled': True,
'security_groups': [self.FAKE_SECURITY_GROUP_ID],
'status': 'ACTIVE'}
# setup firewall on bridge and send packet from src_veth and observe
# if sent packet can be observed on dst_veth
def test_firewall(self):
pass
def test_port_sec_within_firewall(self):
pinger = helpers.Pinger(ip_lib.IPWrapper(self.src_veth.namespace))
# update the sg_group to make ping pass
sg_rules = [{'ethertype': 'IPv4', 'direction': 'ingress',
'source_ip_prefix': '0.0.0.0/0', 'protocol': 'icmp'},
{'ethertype': 'IPv4', 'direction': 'egress'}]
with self.firewall.defer_apply():
self.firewall.update_security_group_rules(
self.FAKE_SECURITY_GROUP_ID,
sg_rules)
self.firewall.prepare_port_filter(self.src_port)
pinger.assert_ping(self.DST_ADDRESS)
# modify the src_veth's MAC and test again
self._set_src_mac(self.MAC_SPOOFED)
pinger.assert_no_ping(self.DST_ADDRESS)
# update the port's port_security_enabled value and test again
self.src_port['port_security_enabled'] = False
self.firewall.update_port_filter(self.src_port)
pinger.assert_ping(self.DST_ADDRESS)

View File

@ -0,0 +1,29 @@
# Copyright (c) 2015 OpenStack Foundation.
# 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.plugins.ml2 import config
from neutron.tests.unit.ml2 import test_ml2_plugin
from neutron.tests.unit import test_extension_portsecurity as test_psec
class PSExtDriverTestCase(test_ml2_plugin.Ml2PluginV2TestCase,
test_psec.TestPortSecurity):
_extension_drivers = ['port_security']
def setUp(self):
config.cfg.CONF.set_override('extension_drivers',
self._extension_drivers,
group='ml2')
super(PSExtDriverTestCase, self).setUp()

View File

@ -166,7 +166,7 @@ class PortSecurityTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
class PortSecurityDBTestCase(PortSecurityTestCase):
def setUp(self, plugin=None):
def setUp(self, plugin=None, service_plugins=None):
plugin = plugin or DB_PLUGIN_KLASS
super(PortSecurityDBTestCase, self).setUp(plugin)
@ -279,7 +279,9 @@ class TestPortSecurity(PortSecurityDBTestCase):
'json', self._create_security_group(self.fmt, 'asdf', 'asdf'))
security_group_id = security_group['security_group']['id']
res = self._create_port('json', net['network']['id'],
arg_list=('security_groups',),
arg_list=('security_groups',
'port_security_enabled'),
port_security_enabled=True,
security_groups=[security_group_id])
port = self.deserialize('json', res)
self.assertEqual(port['port'][psec.PORTSECURITY], True)

View File

@ -1213,12 +1213,12 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase):
self.firewall.prepare_port_filter(port_prepare)
self.firewall.update_port_filter(port_update)
self.firewall.remove_port_filter(port_update)
chain_applies.assert_has_calls([mock.call.remove({}),
mock.call.setup({'d1': port_prepare}),
mock.call.remove({'d1': port_prepare}),
mock.call.setup({'d1': port_update}),
mock.call.remove({'d1': port_update}),
mock.call.setup({})])
chain_applies.assert_has_calls([mock.call.remove({}, {}),
mock.call.setup({'d1': port_prepare}, {}),
mock.call.remove({'d1': port_prepare}, {}),
mock.call.setup({'d1': port_update}, {}),
mock.call.remove({'d1': port_update}, {}),
mock.call.setup({}, {})])
def test_defer_chain_apply_need_pre_defer_copy(self):
chain_applies = self._mock_chain_applies()
@ -1227,10 +1227,10 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase):
self.firewall.prepare_port_filter(port)
with self.firewall.defer_apply():
self.firewall.remove_port_filter(port)
chain_applies.assert_has_calls([mock.call.remove({}),
mock.call.setup(device2port),
mock.call.remove(device2port),
mock.call.setup({})])
chain_applies.assert_has_calls([mock.call.remove({}, {}),
mock.call.setup(device2port, {}),
mock.call.remove(device2port, {}),
mock.call.setup({}, {})])
def test_defer_chain_apply_coalesce_simple(self):
chain_applies = self._mock_chain_applies()
@ -1239,8 +1239,8 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase):
self.firewall.prepare_port_filter(port)
self.firewall.update_port_filter(port)
self.firewall.remove_port_filter(port)
chain_applies.assert_has_calls([mock.call.remove({}),
mock.call.setup({})])
chain_applies.assert_has_calls([mock.call.remove({}, {}),
mock.call.setup({}, {})])
def test_defer_chain_apply_coalesce_multiple_ports(self):
chain_applies = self._mock_chain_applies()
@ -1250,8 +1250,8 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase):
with self.firewall.defer_apply():
self.firewall.prepare_port_filter(port1)
self.firewall.prepare_port_filter(port2)
chain_applies.assert_has_calls([mock.call.remove({}),
mock.call.setup(device2port)])
chain_applies.assert_has_calls([mock.call.remove({}, {}),
mock.call.setup(device2port, {})])
def test_ip_spoofing_filter_with_multiple_ips(self):
port = {'device': 'tapfake_dev',
@ -1642,6 +1642,7 @@ class IptablesFirewallEnhancedIpsetTestCase(BaseIptablesFirewallTestCase):
port = self._fake_port()
self.firewall.filtered_ports['tapfake_dev'] = port
self.firewall._pre_defer_filtered_ports = {}
self.firewall._pre_defer_unfiltered_ports = {}
self.firewall.filter_defer_apply_off()
calls = [mock.call.destroy('fake_sgid', 'IPv4')]

View File

@ -189,6 +189,7 @@ neutron.ml2.mechanism_drivers =
neutron.ml2.extension_drivers =
test = neutron.tests.unit.ml2.drivers.ext_test:TestExtensionDriver
testdb = neutron.tests.unit.ml2.drivers.ext_test:TestDBExtensionDriver
port_security = neutron.plugins.ml2.extensions.port_security:PortSecurityExtensionDriver
neutron.openstack.common.cache.backends =
memory = neutron.openstack.common.cache._backends.memory:MemoryBackend
# These are for backwards compat with Icehouse notification_driver configuration values