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.'
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
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
@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)
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)
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):
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)
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
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):
--- /dev/null
+# 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
-341ee8a4ccb5
+35a0f3365720
# 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
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).
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'])
--- /dev/null
+# 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)
--- /dev/null
+# 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
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
# 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'])
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
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,
# 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()
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)
--- /dev/null
+# 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()
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)
'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)
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()
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()
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()
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',
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')]
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