diff --git a/neutron/agent/linux/iptables_firewall.py b/neutron/agent/linux/iptables_firewall.py index 73881b734e..027298de53 100644 --- a/neutron/agent/linux/iptables_firewall.py +++ b/neutron/agent/linux/iptables_firewall.py @@ -28,10 +28,10 @@ LOG = logging.getLogger(__name__) SG_CHAIN = 'sg-chain' INGRESS_DIRECTION = 'ingress' EGRESS_DIRECTION = 'egress' -IP_SPOOF_FILTER = 'ip-spoof-filter' +SPOOF_FILTER = 'spoof-filter' CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i', EGRESS_DIRECTION: 'o', - IP_SPOOF_FILTER: 's'} + SPOOF_FILTER: 's'} LINUX_DEV_LEN = 14 @@ -106,7 +106,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver): for port in ports.values(): self._remove_chain(port, INGRESS_DIRECTION) self._remove_chain(port, EGRESS_DIRECTION) - self._remove_chain(port, IP_SPOOF_FILTER) + self._remove_chain(port, SPOOF_FILTER) self._remove_chain_by_name_v4v6(SG_CHAIN) def _setup_chain(self, port, DIRECTION): @@ -186,34 +186,58 @@ class IptablesFirewallDriver(firewall.FirewallDriver): if rule['direction'] == direction] def _arp_spoofing_rule(self, port): - return ['-m mac ! --mac-source %s -j DROP' % port['mac_address']] + return '-m mac ! --mac-source %s -j DROP' % port['mac_address'] - def _setup_ip_spoof_filter_chain(self, port, table, addresses, rules): - if len(addresses) == 1: - rules.append('! -s %s -j DROP' % addresses[0]) - elif addresses: - chain_name = self._port_chain_name(port, IP_SPOOF_FILTER) + def _setup_spoof_filter_chain(self, port, table, mac_ip_pairs, rules): + if mac_ip_pairs: + chain_name = self._port_chain_name(port, SPOOF_FILTER) table.add_chain(chain_name) - for ip in addresses: - table.add_rule(chain_name, '-s %s -j RETURN' % ip) + for mac, ip in mac_ip_pairs: + if ip is None: + # If fixed_ips is [] this rule will be added to the end + # of the list after the allowed_address_pair rules. + table.add_rule(chain_name, + '-m mac --mac-source %s -j RETURN' + % mac) + else: + table.add_rule(chain_name, + '-m mac --mac-source %s -s %s -j RETURN' + % (mac, ip)) table.add_rule(chain_name, '-j DROP') rules.append('-j $%s' % chain_name) - def _ip_spoofing_rule(self, port, ipv4_rules, ipv6_rules): + def _build_ipv4v6_mac_ip_list(self, mac, ip_address, mac_ipv4_pairs, + mac_ipv6_pairs): + if netaddr.IPNetwork(ip_address).version == 4: + mac_ipv4_pairs.append((mac, ip_address)) + else: + mac_ipv6_pairs.append((mac, ip_address)) + + def _spoofing_rule(self, port, ipv4_rules, ipv6_rules): #Note(nati) allow dhcp or RA packet ipv4_rules += ['-p udp -m udp --sport 68 --dport 67 -j RETURN'] ipv6_rules += ['-p icmpv6 -j RETURN'] - ipv4_addresses = [] - ipv6_addresses = [] + mac_ipv4_pairs = [] + mac_ipv6_pairs = [] + + if isinstance(port.get('allowed_address_pairs'), list): + for address_pair in port['allowed_address_pairs']: + self._build_ipv4v6_mac_ip_list(address_pair['mac_address'], + address_pair['ip_address'], + mac_ipv4_pairs, + mac_ipv6_pairs) + for ip in port['fixed_ips']: - if netaddr.IPAddress(ip).version == 4: - ipv4_addresses.append(ip) - else: - ipv6_addresses.append(ip) - self._setup_ip_spoof_filter_chain(port, self.iptables.ipv4['filter'], - ipv4_addresses, ipv4_rules) - self._setup_ip_spoof_filter_chain(port, self.iptables.ipv6['filter'], - ipv6_addresses, ipv6_rules) + self._build_ipv4v6_mac_ip_list(port['mac_address'], ip, + mac_ipv4_pairs, mac_ipv6_pairs) + if not port['fixed_ips']: + mac_ipv4_pairs.append((port['mac_address'], None)) + mac_ipv6_pairs.append((port['mac_address'], None)) + + self._setup_spoof_filter_chain(port, self.iptables.ipv4['filter'], + mac_ipv4_pairs, ipv4_rules) + self._setup_spoof_filter_chain(port, self.iptables.ipv6['filter'], + mac_ipv6_pairs, ipv6_rules) def _drop_dhcp_rule(self): #Note(nati) Drop dhcp packet from VM @@ -231,11 +255,9 @@ class IptablesFirewallDriver(firewall.FirewallDriver): ipv4_iptables_rule = [] ipv6_iptables_rule = [] if direction == EGRESS_DIRECTION: - ipv4_iptables_rule += self._arp_spoofing_rule(port) - ipv6_iptables_rule += self._arp_spoofing_rule(port) - self._ip_spoofing_rule(port, - ipv4_iptables_rule, - ipv6_iptables_rule) + self._spoofing_rule(port, + ipv4_iptables_rule, + ipv6_iptables_rule) ipv4_iptables_rule += self._drop_dhcp_rule() ipv4_iptables_rule += self._convert_sgr_to_iptables_rules( ipv4_sg_rules) diff --git a/neutron/db/allowedaddresspairs_db.py b/neutron/db/allowedaddresspairs_db.py new file mode 100644 index 0000000000..1c9977dd22 --- /dev/null +++ b/neutron/db/allowedaddresspairs_db.py @@ -0,0 +1,121 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Nicira Networks, 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. +# +# @author: Aaron Rosen, Nicira, Inc + +import sqlalchemy as sa +from sqlalchemy import orm + +from neutron.api.v2 import attributes as attr +from neutron.db import db_base_plugin_v2 +from neutron.db import model_base +from neutron.db import models_v2 +from neutron.extensions import allowedaddresspairs as addr_pair + + +class AllowedAddressPair(model_base.BASEV2): + port_id = sa.Column(sa.String(36), + sa.ForeignKey('ports.id', ondelete="CASCADE"), + primary_key=True) + mac_address = sa.Column(sa.String(32), nullable=False, primary_key=True) + ip_address = sa.Column(sa.String(64), nullable=False, primary_key=True) + + port = orm.relationship( + models_v2.Port, + backref=orm.backref("allowed_address_pairs", + lazy="joined", cascade="delete")) + + +class AllowedAddressPairsMixin(object): + """Mixin class for allowed address pairs.""" + + def _process_create_allowed_address_pairs(self, context, port, + allowed_address_pairs): + if not attr.is_attr_set(allowed_address_pairs): + return [] + with context.session.begin(subtransactions=True): + for address_pair in allowed_address_pairs: + # use port.mac_address if no mac address in address pair + if 'mac_address' not in address_pair: + address_pair['mac_address'] = port['mac_address'] + for fixed_ip in port['fixed_ips']: + if ((fixed_ip['ip_address'] == address_pair['ip_address']) + and (port['mac_address'] == + address_pair['mac_address'])): + #TODO(arosen) - need to query for address pairs + # to check for same condition if fixed_ips change to + # be an address pair. + raise addr_pair.AddressPairMatchesPortFixedIPAndMac() + db_pair = AllowedAddressPair( + port_id=port['id'], + mac_address=address_pair['mac_address'], + ip_address=address_pair['ip_address']) + context.session.add(db_pair) + + return allowed_address_pairs + + def get_allowed_address_pairs(self, context, port_id): + pairs = (context.session.query(AllowedAddressPair). + filter_by(port_id=port_id)) + return [self._make_allowed_address_pairs_dict(pair) + for pair in pairs] + + def _extend_port_dict_allowed_address_pairs(self, port_res, port_db): + # If port_db is provided, allowed address pairs will be accessed via + # sqlalchemy models. As they're loaded together with ports this + # will not cause an extra query. + allowed_address_pairs = [ + self._make_allowed_address_pairs_dict(address_pair) for + address_pair in port_db.allowed_address_pairs] + port_res[addr_pair.ADDRESS_PAIRS] = allowed_address_pairs + return port_res + + # Register dict extend functions for ports + db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( + attr.PORTS, ['_extend_port_dict_allowed_address_pairs']) + + def _delete_allowed_address_pairs(self, context, id): + query = self._model_query(context, AllowedAddressPair) + with context.session.begin(subtransactions=True): + query.filter(AllowedAddressPair.port_id == id).delete() + + def _make_allowed_address_pairs_dict(self, allowed_address_pairs, + fields=None): + res = {'mac_address': allowed_address_pairs['mac_address'], + 'ip_address': allowed_address_pairs['ip_address']} + return self._fields(res, fields) + + def _has_address_pairs(self, port): + return (attr.is_attr_set(port['port'][addr_pair.ADDRESS_PAIRS]) + and port['port'][addr_pair.ADDRESS_PAIRS] != []) + + def _check_update_has_allowed_address_pairs(self, port): + """Determine if request has an allowed address pair. + + Return True if the port parameter has a non-empty + 'allowed_address_pairs' attribute. Otherwise returns False. + """ + return (addr_pair.ADDRESS_PAIRS in port['port'] and + self._has_address_pairs(port)) + + def _check_update_deletes_allowed_address_pairs(self, port): + """Determine if request deletes address pair. + + Return True if port has as a allowed address pair and its value + is either [] or not is_attr_set, otherwise return False + """ + return (addr_pair.ADDRESS_PAIRS in port['port'] and + not self._has_address_pairs(port)) diff --git a/neutron/db/migration/alembic_migrations/versions/1efb85914233_allowedaddresspairs.py b/neutron/db/migration/alembic_migrations/versions/1efb85914233_allowedaddresspairs.py new file mode 100644 index 0000000000..c01e60df56 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/1efb85914233_allowedaddresspairs.py @@ -0,0 +1,63 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 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. +# + +"""allowedaddresspairs + +Revision ID: 1efb85914233 +Revises: 51b4de912379 +Create Date: 2013-07-23 12:56:00.402855 + +""" + +# revision identifiers, used by Alembic. +revision = '1efb85914233' +down_revision = '51b4de912379' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2', + 'neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2', + 'neutron.plugins.cisco.network_plugin.PluginV2', + 'neutron.plugins.ml2.plugin.Ml2Plugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'allowedaddresspairs', + sa.Column('port_id', sa.String(length=36), nullable=False), + sa.Column('mac_address', sa.String(length=32), nullable=False), + sa.Column('ip_address', sa.String(length=64), nullable=False), + sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('port_id', 'mac_address', 'ip_address'), + ) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('allowedaddresspairs') diff --git a/neutron/db/securitygroups_rpc_base.py b/neutron/db/securitygroups_rpc_base.py index 5fb2174077..cf17430e94 100644 --- a/neutron/db/securitygroups_rpc_base.py +++ b/neutron/db/securitygroups_rpc_base.py @@ -174,12 +174,20 @@ class SecurityGroupServerRpcCallbackMixin(object): sg_binding_sgid = sg_db.SecurityGroupPortBinding.security_group_id query = context.session.query(sg_binding_sgid, + models_v2.Port, models_v2.IPAllocation.ip_address) query = query.join(models_v2.IPAllocation, ip_port == sg_binding_port) + query = query.join(models_v2.Port, + ip_port == models_v2.Port.id) query = query.filter(sg_binding_sgid.in_(remote_group_ids)) - for security_group_id, ip_address in query: + for security_group_id, port, ip_address in query: ips_by_group[security_group_id].append(ip_address) + # if there are allowed_address_pairs add them + if getattr(port, 'allowed_address_pairs', None): + for address_pair in port.allowed_address_pairs: + ips_by_group[security_group_id].append( + address_pair['ip_address']) return ips_by_group def _select_remote_group_ids(self, ports): @@ -231,12 +239,12 @@ class SecurityGroupServerRpcCallbackMixin(object): if ip in port.get('fixed_ips', []): continue ip_rule = base_rule.copy() - version = netaddr.IPAddress(ip).version + version = netaddr.IPNetwork(ip).version ethertype = 'IPv%s' % version if base_rule['ethertype'] != ethertype: continue - ip_rule[direction_ip_prefix] = "%s/%s" % ( - ip, IP_MASK[ethertype]) + ip_rule[direction_ip_prefix] = str( + netaddr.IPNetwork(ip).cidr) updated_rule.append(ip_rule) port['security_group_rules'] = updated_rule return ports diff --git a/neutron/extensions/allowedaddresspairs.py b/neutron/extensions/allowedaddresspairs.py new file mode 100644 index 0000000000..ad4f310b74 --- /dev/null +++ b/neutron/extensions/allowedaddresspairs.py @@ -0,0 +1,122 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Nicira Networks, 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. +# +# @author: Aaron Rosen, Nicira, Inc + +import webob.exc + +from neutron.api.v2 import attributes as attr +from neutron.common import exceptions as nexception + + +class AllowedAddressPairsMissingIP(nexception.InvalidInput): + message = _("AllowedAddressPair must contain ip_address") + + +class AddressPairAndPortSecurityRequired(nexception.Conflict): + message = _("Port Security must be enabled in order to have allowed " + "address pairs on a port.") + + +class DuplicateAddressPairInRequest(nexception.InvalidInput): + message = _("Request contains duplicate address pair: " + "mac_address %(mac_address)s ip_address %(ip_address)s.") + + +class AddressPairMatchesPortFixedIPAndMac(nexception.InvalidInput): + message = _("Port's Fixed IP and Mac Address match an address pair entry.") + + +def _validate_allowed_address_pairs(address_pairs, valid_values=None): + unique_check = {} + for address_pair in address_pairs: + # mac_address is optional, if not set we use the mac on the port + if 'mac_address' in address_pair: + msg = attr._validate_mac_address(address_pair['mac_address']) + if msg: + raise webob.exc.HTTPBadRequest(msg) + if 'ip_address' not in address_pair: + raise AllowedAddressPairsMissingIP() + + mac = address_pair.get('mac_address') + ip_address = address_pair['ip_address'] + if (mac, ip_address) not in unique_check: + unique_check[(mac, ip_address)] = None + else: + raise DuplicateAddressPairInRequest(mac_address=mac, + ip_address=ip_address) + + invalid_attrs = set(address_pair.keys()) - set(['mac_address', + 'ip_address']) + if invalid_attrs: + msg = (_("Unrecognized attribute(s) '%s'") % + ', '.join(set(address_pair.keys()) - + set(['mac_address', 'ip_address']))) + raise webob.exc.HTTPBadRequest(msg) + + if '/' in ip_address: + msg = attr._validate_subnet(ip_address) + else: + msg = attr._validate_ip_address(ip_address) + if msg: + raise webob.exc.HTTPBadRequest(msg) + +attr.validators['type:validate_allowed_address_pairs'] = ( + _validate_allowed_address_pairs) + +ADDRESS_PAIRS = 'allowed_address_pairs' +EXTENDED_ATTRIBUTES_2_0 = { + 'ports': { + ADDRESS_PAIRS: {'allow_post': True, 'allow_put': True, + 'convert_list_to': + attr.convert_kvp_list_to_dict, + 'validate': {'type:validate_allowed_address_pairs': + None}, + 'enforce_policy': True, + 'default': attr.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + } +} + + +class Allowedaddresspairs(object): + """Extension class supporting allowed address pairs.""" + + @classmethod + def get_name(cls): + return "Allowed Address Pairs" + + @classmethod + def get_alias(cls): + return "allowed-address-pairs" + + @classmethod + def get_description(cls): + return "Provides allowed address pairs" + + @classmethod + def get_namespace(cls): + return "http://docs.openstack.org/ext/allowedaddresspairs/api/v2.0" + + @classmethod + def get_updated(cls): + return "2013-07-23T10:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 9d9b01f671..e821511860 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -23,12 +23,14 @@ from neutron.common import constants as const from neutron.common import exceptions as exc from neutron.common import topics from neutron.db import agentschedulers_db +from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import db_base_plugin_v2 from neutron.db import extraroute_db from neutron.db import l3_gwmode_db from neutron.db import models_v2 from neutron.db import quota_db # noqa from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import multiprovidernet as mpnet from neutron.extensions import portbindings from neutron.extensions import providernet as provider @@ -57,7 +59,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, l3_gwmode_db.L3_NAT_db_mixin, sg_db_rpc.SecurityGroupServerRpcMixin, agentschedulers_db.L3AgentSchedulerDbMixin, - agentschedulers_db.DhcpAgentSchedulerDbMixin): + agentschedulers_db.DhcpAgentSchedulerDbMixin, + addr_pair_db.AllowedAddressPairsMixin): """Implement the Neutron L2 abstractions using modules. Ml2Plugin is a Neutron plugin based on separately extensible sets @@ -79,7 +82,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, "binding", "quotas", "security-group", "agent", "l3_agent_scheduler", "dhcp_agent_scheduler", "ext-gw-mode", - "multi-provider"] + "multi-provider", "allowed-address-pairs"] @property def supported_extension_aliases(self): @@ -452,6 +455,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, mech_context = driver_context.PortContext(self, context, result, network) self._process_port_binding(mech_context, attrs) + result[addr_pair.ADDRESS_PAIRS] = ( + self._process_create_allowed_address_pairs( + context, result, + attrs.get(addr_pair.ADDRESS_PAIRS))) self.mechanism_manager.create_port_precommit(mech_context) try: @@ -473,7 +480,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, original_port = super(Ml2Plugin, self).get_port(context, id) updated_port = super(Ml2Plugin, self).update_port(context, id, port) - need_port_update_notify = self.update_security_group_on_port( + if addr_pair.ADDRESS_PAIRS in port['port']: + self._delete_allowed_address_pairs(context, id) + self._process_create_allowed_address_pairs( + context, updated_port, + port['port'][addr_pair.ADDRESS_PAIRS]) + need_port_update_notify = True + need_port_update_notify |= self.update_security_group_on_port( context, id, port, original_port, updated_port) network = self.get_network(context, original_port['network_id']) mech_context = driver_context.PortContext( diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index f3c1d1eae1..af4a6ff4f7 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -34,6 +34,7 @@ from neutron.common import exceptions as q_exc from neutron.common import utils from neutron import context as q_context from neutron.db import agentschedulers_db +from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import api as db from neutron.db import db_base_plugin_v2 from neutron.db import extraroute_db @@ -44,6 +45,7 @@ from neutron.db import portbindings_db from neutron.db import portsecurity_db from neutron.db import quota_db # noqa from neutron.db import securitygroups_db +from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import extraroute from neutron.extensions import l3 from neutron.extensions import multiprovidernet as mpnet @@ -110,7 +112,8 @@ def create_nvp_cluster(cluster_opts, concurrent_connections, return cluster -class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, +class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, + agentschedulers_db.DhcpAgentSchedulerDbMixin, db_base_plugin_v2.NeutronDbPluginV2, dhcpmeta_modes.DhcpMetadataAccess, dist_rtr.DistributedRouter_mixin, @@ -130,6 +133,7 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, """ supported_extension_aliases = ["agent", + "allowed-address-pairs", "binding", "dhcp_agent_scheduler", "dist-router", @@ -421,7 +425,8 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, port_data[psec.PORTSECURITY], port_data[ext_sg.SECURITYGROUPS], port_data[ext_qos.QUEUE], - port_data.get(mac_ext.MAC_LEARNING)) + port_data.get(mac_ext.MAC_LEARNING), + port_data.get(addr_pair.ADDRESS_PAIRS)) def _handle_create_port_exception(self, context, port_id, ls_uuid, lp_uuid): @@ -1128,6 +1133,18 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, port_data[psec.PORTSECURITY] = port_security self._process_port_port_security_create( context, port_data, neutron_db) + # allowed address pair checks + if attr.is_attr_set(port_data.get(addr_pair.ADDRESS_PAIRS)): + if not port_security: + raise addr_pair.AddressPairAndPortSecurityRequired() + else: + self._process_create_allowed_address_pairs( + context, neutron_db, + port_data[addr_pair.ADDRESS_PAIRS]) + else: + # remove ATTR_NOT_SPECIFIED + port_data[addr_pair.ADDRESS_PAIRS] = None + # security group extension checks if port_security and has_ip: self._ensure_default_security_group_on_port(context, port) @@ -1185,6 +1202,9 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, delete_security_groups = self._check_update_deletes_security_groups( port) has_security_groups = self._check_update_has_security_groups(port) + delete_addr_pairs = self._check_update_deletes_allowed_address_pairs( + port) + has_addr_pairs = self._check_update_has_allowed_address_pairs(port) with context.session.begin(subtransactions=True): ret_port = super(NvpPluginV2, self).update_port( @@ -1198,7 +1218,28 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, ret_port.update(port['port']) tenant_id = self._get_tenant_id_for_create(context, ret_port) + # populate port_security setting + if psec.PORTSECURITY not in port['port']: + ret_port[psec.PORTSECURITY] = self._get_port_security_binding( + context, id) has_ip = self._ip_on_port(ret_port) + # validate port security and allowed address pairs + if not ret_port[psec.PORTSECURITY]: + # has address pairs in request + if has_addr_pairs: + raise addr_pair.AddressPairAndPortSecurityRequired() + elif not delete_addr_pairs: + # check if address pairs are in db + ret_port[addr_pair.ADDRESS_PAIRS] = ( + self.get_allowed_address_pairs(context, id)) + if ret_port[addr_pair.ADDRESS_PAIRS]: + raise addr_pair.AddressPairAndPortSecurityRequired() + + if (delete_addr_pairs or has_addr_pairs): + # delete address pairs and read them in + self._delete_allowed_address_pairs(context, id) + self._process_create_allowed_address_pairs( + context, ret_port, ret_port[addr_pair.ADDRESS_PAIRS]) # checks if security groups were updated adding/modifying # security groups, port security is set and port has ip if not (has_ip and ret_port[psec.PORTSECURITY]): @@ -1251,7 +1292,8 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin, ret_port[psec.PORTSECURITY], ret_port[ext_sg.SECURITYGROUPS], ret_port[ext_qos.QUEUE], - ret_port.get(mac_ext.MAC_LEARNING)) + ret_port.get(mac_ext.MAC_LEARNING), + ret_port.get(addr_pair.ADDRESS_PAIRS)) # Update the port status from nvp. If we fail here hide it # since the port was successfully updated but we were not diff --git a/neutron/plugins/nicira/nvplib.py b/neutron/plugins/nicira/nvplib.py index 646a214909..dd63d5bc3f 100644 --- a/neutron/plugins/nicira/nvplib.py +++ b/neutron/plugins/nicira/nvplib.py @@ -696,7 +696,8 @@ def get_port(cluster, network, port, relations=None): def _configure_extensions(lport_obj, mac_address, fixed_ips, port_security_enabled, security_profiles, - queue_id, mac_learning_enabled): + queue_id, mac_learning_enabled, + allowed_address_pairs): lport_obj['allowed_address_pairs'] = [] if port_security_enabled: for fixed_ip in fixed_ips: @@ -714,13 +715,17 @@ def _configure_extensions(lport_obj, mac_address, fixed_ips, if mac_learning_enabled is not None: lport_obj["mac_learning"] = mac_learning_enabled lport_obj["type"] = "LogicalSwitchPortConfig" + for address_pair in list(allowed_address_pairs or []): + lport_obj['allowed_address_pairs'].append( + {'mac_address': address_pair['mac_address'], + 'ip_address': address_pair['ip_address']}) def update_port(cluster, lswitch_uuid, lport_uuid, neutron_port_id, tenant_id, display_name, device_id, admin_status_enabled, mac_address=None, fixed_ips=None, port_security_enabled=None, security_profiles=None, queue_id=None, - mac_learning_enabled=None): + mac_learning_enabled=None, allowed_address_pairs=None): # device_id can be longer than 40 so we rehash it hashed_device_id = hashlib.sha1(device_id).hexdigest() lport_obj = dict( @@ -733,7 +738,8 @@ def update_port(cluster, lswitch_uuid, lport_uuid, neutron_port_id, tenant_id, _configure_extensions(lport_obj, mac_address, fixed_ips, port_security_enabled, security_profiles, - queue_id, mac_learning_enabled) + queue_id, mac_learning_enabled, + allowed_address_pairs) path = "/ws.v1/lswitch/" + lswitch_uuid + "/lport/" + lport_uuid try: @@ -753,7 +759,7 @@ def create_lport(cluster, lswitch_uuid, tenant_id, neutron_port_id, display_name, device_id, admin_status_enabled, mac_address=None, fixed_ips=None, port_security_enabled=None, security_profiles=None, queue_id=None, - mac_learning_enabled=None): + mac_learning_enabled=None, allowed_address_pairs=None): """Creates a logical port on the assigned logical switch.""" # device_id can be longer than 40 so we rehash it hashed_device_id = hashlib.sha1(device_id).hexdigest() @@ -769,7 +775,8 @@ def create_lport(cluster, lswitch_uuid, tenant_id, neutron_port_id, _configure_extensions(lport_obj, mac_address, fixed_ips, port_security_enabled, security_profiles, - queue_id, mac_learning_enabled) + queue_id, mac_learning_enabled, + allowed_address_pairs) path = _build_uri_path(LSWITCHPORT_RESOURCE, parent_resource_id=lswitch_uuid) diff --git a/neutron/plugins/openvswitch/ovs_neutron_plugin.py b/neutron/plugins/openvswitch/ovs_neutron_plugin.py index 31312bf4ea..de87bfd09c 100644 --- a/neutron/plugins/openvswitch/ovs_neutron_plugin.py +++ b/neutron/plugins/openvswitch/ovs_neutron_plugin.py @@ -36,6 +36,7 @@ from neutron.common import topics from neutron.common import utils from neutron.db import agents_db from neutron.db import agentschedulers_db +from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import db_base_plugin_v2 from neutron.db import dhcp_rpc_base from neutron.db import extradhcpopt_db @@ -45,6 +46,7 @@ from neutron.db import l3_rpc_base from neutron.db import portbindings_db from neutron.db import quota_db # noqa 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 providernet as provider @@ -225,7 +227,8 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, agentschedulers_db.L3AgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, portbindings_db.PortBindingMixin, - extradhcpopt_db.ExtraDhcpOptMixin): + extradhcpopt_db.ExtraDhcpOptMixin, + addr_pair_db.AllowedAddressPairsMixin): """Implement the Neutron abstractions using Open vSwitch. @@ -256,7 +259,8 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, "agent", "extraroute", "l3_agent_scheduler", "dhcp_agent_scheduler", - "extra_dhcp_opt"] + "extra_dhcp_opt", + "allowed-address-pairs"] @property def supported_extension_aliases(self): @@ -547,6 +551,10 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, self._process_port_create_security_group(context, port, sgids) self._process_port_create_extra_dhcp_opts(context, port, dhcp_opts) + port[addr_pair.ADDRESS_PAIRS] = ( + self._process_create_allowed_address_pairs( + context, port, + port_data.get(addr_pair.ADDRESS_PAIRS))) self.notify_security_groups_member_updated(context, port) return port @@ -558,7 +566,14 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, context, id) updated_port = super(OVSNeutronPluginV2, self).update_port( context, id, port) - need_port_update_notify = self.update_security_group_on_port( + if addr_pair.ADDRESS_PAIRS in port['port']: + self._delete_allowed_address_pairs(context, id) + self._process_create_allowed_address_pairs( + context, updated_port, + port['port'][addr_pair.ADDRESS_PAIRS]) + need_port_update_notify = True + + need_port_update_notify |= self.update_security_group_on_port( context, id, port, original_port, updated_port) self._process_portbindings_create_and_update(context, port['port'], diff --git a/neutron/tests/unit/ml2/test_security_group.py b/neutron/tests/unit/ml2/test_security_group.py index 8a0e2261b8..7e041027de 100644 --- a/neutron/tests/unit/ml2/test_security_group.py +++ b/neutron/tests/unit/ml2/test_security_group.py @@ -87,3 +87,15 @@ class TestMl2SecurityGroups(Ml2SecurityGroupsTestCase, class TestMl2SecurityGroupsXML(TestMl2SecurityGroups): fmt = 'xml' + + +class TestMl2SGServerRpcCallBack( + Ml2SecurityGroupsTestCase, + test_sg_rpc.SGServerRpcCallBackMixinTestCase): + pass + + +class TestMl2SGServerRpcCallBackXML( + Ml2SecurityGroupsTestCase, + test_sg_rpc.SGServerRpcCallBackMixinTestCaseXML): + pass diff --git a/neutron/tests/unit/nicira/test_nicira_plugin.py b/neutron/tests/unit/nicira/test_nicira_plugin.py index 918ee7b646..2be1b37f78 100644 --- a/neutron/tests/unit/nicira/test_nicira_plugin.py +++ b/neutron/tests/unit/nicira/test_nicira_plugin.py @@ -53,6 +53,7 @@ from neutron.tests.unit.nicira import PLUGIN_NAME from neutron.tests.unit.nicira import STUBS_PATH import neutron.tests.unit.nicira.test_networkgw as test_l2_gw import neutron.tests.unit.test_db_plugin as test_plugin +import neutron.tests.unit.test_extension_allowedaddresspairs as test_addr_pair import neutron.tests.unit.test_extension_ext_gw_mode as test_ext_gw_mode import neutron.tests.unit.test_extension_portsecurity as psec import neutron.tests.unit.test_extension_security_group as ext_sg @@ -352,6 +353,11 @@ class TestNiciraPortSecurity(NiciraPortSecurityTestCase, pass +class TestNiciraAllowedAddressPairs(test_addr_pair.TestAllowedAddressPairs, + NiciraPluginV2TestCase): + pass + + class NiciraSecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase): def setUp(self): diff --git a/neutron/tests/unit/openvswitch/test_ovs_security_group.py b/neutron/tests/unit/openvswitch/test_ovs_security_group.py index 83f8755cf4..5985ae23e4 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_security_group.py +++ b/neutron/tests/unit/openvswitch/test_ovs_security_group.py @@ -53,6 +53,18 @@ class OpenvswitchSecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase): attributes.RESOURCE_ATTRIBUTE_MAP = self._attribute_map_bk_ +class TestOpenvswitchSGServerRpcCallBack( + OpenvswitchSecurityGroupsTestCase, + test_sg_rpc.SGServerRpcCallBackMixinTestCase): + pass + + +class TestOpenvswitchSGServerRpcCallBackXML( + OpenvswitchSecurityGroupsTestCase, + test_sg_rpc.SGServerRpcCallBackMixinTestCaseXML): + pass + + class TestOpenvswitchSecurityGroups(OpenvswitchSecurityGroupsTestCase, test_sg.TestSecurityGroups, test_sg_rpc.SGNotificationTestMixin): diff --git a/neutron/tests/unit/test_extension_allowedaddresspairs.py b/neutron/tests/unit/test_extension_allowedaddresspairs.py new file mode 100644 index 0000000000..ab380e9267 --- /dev/null +++ b/neutron/tests/unit/test_extension_allowedaddresspairs.py @@ -0,0 +1,268 @@ +# Copyright (c) 2013 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. + + +from neutron.api.v2 import attributes as attr +from neutron.common.test_lib import test_config +from neutron.db import allowedaddresspairs_db as addr_pair_db +from neutron.db import db_base_plugin_v2 +from neutron.db import portsecurity_db +from neutron.extensions import allowedaddresspairs as addr_pair +from neutron.extensions import portsecurity as psec +from neutron.manager import NeutronManager +from neutron.tests.unit import test_db_plugin + +DB_PLUGIN_KLASS = ('neutron.tests.unit.test_extension_allowedaddresspairs.' + 'AllowedAddressPairTestPlugin') + + +class AllowedAddressPairTestCase(test_db_plugin.NeutronDbPluginV2TestCase): + def setUp(self, plugin=None): + super(AllowedAddressPairTestCase, self).setUp() + + # Check if a plugin supports security groups + plugin_obj = NeutronManager.get_plugin() + self._skip_port_security = ('port-security' not in + plugin_obj.supported_extension_aliases) + + +class AllowedAddressPairTestPlugin(portsecurity_db.PortSecurityDbMixin, + db_base_plugin_v2.NeutronDbPluginV2, + addr_pair_db.AllowedAddressPairsMixin): + + """Test plugin that implements necessary calls on create/delete port for + associating ports with port security and allowed address pairs. + """ + + supported_extension_aliases = ["allowed-address-pairs"] + + def create_port(self, context, port): + p = port['port'] + with context.session.begin(subtransactions=True): + neutron_db = super(AllowedAddressPairTestPlugin, self).create_port( + context, port) + p.update(neutron_db) + if attr.is_attr_set(p.get(addr_pair.ADDRESS_PAIRS)): + self._process_create_allowed_address_pairs( + context, p, + p[addr_pair.ADDRESS_PAIRS]) + else: + p[addr_pair.ADDRESS_PAIRS] = None + + return port['port'] + + def update_port(self, context, id, port): + delete_addr_pairs = self._check_update_deletes_allowed_address_pairs( + port) + has_addr_pairs = self._check_update_has_allowed_address_pairs(port) + + with context.session.begin(subtransactions=True): + ret_port = super(AllowedAddressPairTestPlugin, self).update_port( + context, id, port) + # copy values over - but not fixed_ips + port['port'].pop('fixed_ips', None) + ret_port.update(port['port']) + + if (delete_addr_pairs or has_addr_pairs): + # delete address pairds and readd them + self._delete_allowed_address_pairs(context, id) + self._process_create_allowed_address_pairs( + context, ret_port, + ret_port[addr_pair.ADDRESS_PAIRS]) + + return ret_port + + +class AllowedAddressPairDBTestCase(AllowedAddressPairTestCase): + def setUp(self, plugin=None): + test_config['plugin_name_v2'] = DB_PLUGIN_KLASS + super(AllowedAddressPairDBTestCase, self).setUp() + + def tearDown(self): + del test_config['plugin_name_v2'] + super(AllowedAddressPairDBTestCase, self).tearDown() + + +class AllowedAddressPairDBTestCaseXML(AllowedAddressPairDBTestCase): + fmt = 'xml' + + +class TestAllowedAddressPairs(AllowedAddressPairDBTestCase): + + def test_create_port_allowed_address_pairs(self): + with self.network() as net: + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=(addr_pair.ADDRESS_PAIRS,), + allowed_address_pairs=address_pairs) + port = self.deserialize(self.fmt, res) + self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS], + address_pairs) + self._delete('ports', port['port']['id']) + + def test_create_port_security_true_allowed_address_pairs(self): + if self._skip_port_security: + self.skipTest("Plugin does not implement port-security extension") + + with self.network() as net: + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=('port_security_enabled', + addr_pair.ADDRESS_PAIRS,), + port_security_enabled=True, + allowed_address_pairs=address_pairs) + port = self.deserialize(self.fmt, res) + self.assertEqual(port['port'][psec.PORTSECURITY], True) + self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS], + address_pairs) + self._delete('ports', port['port']['id']) + + def test_create_port_security_false_allowed_address_pairs(self): + if self._skip_port_security: + self.skipTest("Plugin does not implement port-security extension") + + with self.network() as net: + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=('port_security_enabled', + addr_pair.ADDRESS_PAIRS,), + port_security_enabled=False, + allowed_address_pairs=address_pairs) + self.deserialize(self.fmt, res) + self.assertEqual(res.status_int, 409) + + def test_create_port_bad_mac(self): + address_pairs = [{'mac_address': 'invalid_mac', + 'ip_address': '10.0.0.1'}] + self._create_port_with_address_pairs(address_pairs, 400) + + def test_create_port_bad_ip(self): + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1222'}] + self._create_port_with_address_pairs(address_pairs, 400) + + def test_create_missing_ip_field(self): + address_pairs = [{'mac_address': '00:00:00:00:00:01'}] + self._create_port_with_address_pairs(address_pairs, 400) + + def test_create_duplicate_mac_ip(self): + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}, + {'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + self._create_port_with_address_pairs(address_pairs, 400) + + def test_create_port_extra_args(self): + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1', + 'icbb': 'agreed'}] + self._create_port_with_address_pairs(address_pairs, 400) + + def _create_port_with_address_pairs(self, address_pairs, ret_code): + with self.network() as net: + res = self._create_port(self.fmt, net['network']['id'], + arg_list=(addr_pair.ADDRESS_PAIRS,), + allowed_address_pairs=address_pairs) + self.deserialize(self.fmt, res) + self.assertEqual(res.status_int, ret_code) + + def test_update_add_address_pairs(self): + with self.network() as net: + res = self._create_port(self.fmt, net['network']['id']) + port = self.deserialize(self.fmt, res) + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + update_port = {'port': {addr_pair.ADDRESS_PAIRS: + address_pairs}} + req = self.new_update_request('ports', update_port, + port['port']['id']) + port = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS], + address_pairs) + self._delete('ports', port['port']['id']) + + def test_create_address_gets_port_mac(self): + with self.network() as net: + address_pairs = [{'ip_address': '23.23.23.23'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=('port_security_enabled', + addr_pair.ADDRESS_PAIRS,), + allowed_address_pairs=address_pairs) + port = self.deserialize(self.fmt, res)['port'] + port_addr_mac = port[addr_pair.ADDRESS_PAIRS][0]['mac_address'] + self.assertEqual(port_addr_mac, + port['mac_address']) + self._delete('ports', port['id']) + + def test_update_address_pair_to_match_fixed_ip_and_mac(self): + with self.network() as net: + with self.subnet(network=net): + res = self._create_port(self.fmt, net['network']['id']) + port = self.deserialize(self.fmt, res)['port'] + address_pairs = [{'mac_address': port['mac_address'], + 'ip_address': + port['fixed_ips'][0]['ip_address']}] + + update_port = {'port': {addr_pair.ADDRESS_PAIRS: + address_pairs}} + req = self.new_update_request('ports', update_port, + port['id']) + res = req.get_response(self.api) + self.assertEqual(res.status_int, 400) + self._delete('ports', port['id']) + + def test_update_port_security_off_address_pairs(self): + if self._skip_port_security: + self.skipTest("Plugin does not implement port-security extension") + with self.network() as net: + with self.subnet(network=net): + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=('port_security_enabled', + addr_pair.ADDRESS_PAIRS,), + port_security_enabled=True, + allowed_address_pairs=address_pairs) + port = self.deserialize(self.fmt, res) + print port + update_port = {'port': {psec.PORTSECURITY: False}} + # If plugin implements security groups we also need to remove + # the security group on port. + plugin_obj = NeutronManager.get_plugin() + if 'security-groups' in plugin_obj.supported_extension_aliases: + update_port['port']['security_groups'] = [] + req = self.new_update_request('ports', update_port, + port['port']['id']) + res = req.get_response(self.api) + self.assertEqual(res.status_int, 409) + self._delete('ports', port['port']['id']) + + def test_create_port_remove_allowed_address_pairs(self): + with self.network() as net: + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.1'}] + res = self._create_port(self.fmt, net['network']['id'], + arg_list=(addr_pair.ADDRESS_PAIRS,), + allowed_address_pairs=address_pairs) + port = self.deserialize(self.fmt, res) + update_port = {'port': {addr_pair.ADDRESS_PAIRS: []}} + req = self.new_update_request('ports', update_port, + port['port']['id']) + port = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS], []) + self._delete('ports', port['port']['id']) diff --git a/neutron/tests/unit/test_iptables_firewall.py b/neutron/tests/unit/test_iptables_firewall.py index edd755646a..004c533af4 100644 --- a/neutron/tests/unit/test_iptables_firewall.py +++ b/neutron/tests/unit/test_iptables_firewall.py @@ -97,12 +97,15 @@ class IptablesFirewallTestCase(base.BaseTestCase): '-m physdev --physdev-in tapfake_dev ' '--physdev-is-bridged ' '-j $ofake_dev'), + call.add_chain('sfake_dev'), call.add_rule( - 'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + 'sfake_dev', '-m mac --mac-source ff:ff:ff:ff ' + '-s 10.0.0.1 -j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 68 --dport 67 -j RETURN'), - call.add_rule('ofake_dev', '! -s 10.0.0.1 -j DROP'), + call.add_rule('ofake_dev', '-j $sfake_dev'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 67 --dport 68 -j DROP'), @@ -767,12 +770,14 @@ class IptablesFirewallTestCase(base.BaseTestCase): '-m physdev --physdev-in tapfake_dev ' '--physdev-is-bridged ' '-j $ofake_dev'), + call.add_chain('sfake_dev'), call.add_rule( - 'ofake_dev', - '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -s %s -j RETURN' + % prefix), + call.add_rule('sfake_dev', '-j DROP'), dhcp_rule, - call.add_rule('ofake_dev', '! -s %s -j DROP' % prefix)] - + call.add_rule('ofake_dev', '-j $sfake_dev')] if ethertype == 'IPv4': calls.append(call.add_rule( 'ofake_dev', @@ -836,15 +841,16 @@ class IptablesFirewallTestCase(base.BaseTestCase): 'INPUT', '-m physdev --physdev-in tapfake_dev ' '--physdev-is-bridged -j $ofake_dev'), + call.add_chain('sfake_dev'), call.add_rule( - 'ofake_dev', - '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 ' + '-j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 68 --dport 67 -j RETURN'), - call.add_rule( - 'ofake_dev', - '! -s 10.0.0.1 -j DROP'), + call.add_rule('ofake_dev', '-j $sfake_dev'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 67 --dport 68 -j DROP'), @@ -889,14 +895,15 @@ class IptablesFirewallTestCase(base.BaseTestCase): 'INPUT', '-m physdev --physdev-in tapfake_dev ' '--physdev-is-bridged -j $ofake_dev'), + call.add_chain('sfake_dev'), call.add_rule( - 'ofake_dev', - '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 -j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 68 --dport 67 -j RETURN'), - call.add_rule( - 'ofake_dev', '! -s 10.0.0.1 -j DROP'), + call.add_rule('ofake_dev', '-j $sfake_dev'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 67 --dport 68 -j DROP'), @@ -1039,11 +1046,71 @@ class IptablesFirewallTestCase(base.BaseTestCase): '--physdev-is-bridged ' '-j $ofake_dev'), call.add_chain('sfake_dev'), - call.add_rule('sfake_dev', '-s 10.0.0.1 -j RETURN'), - call.add_rule('sfake_dev', '-s 10.0.0.2 -j RETURN'), - call.add_rule('sfake_dev', '-j DROP'), call.add_rule( - 'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 -j RETURN'), + call.add_rule( + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -s 10.0.0.2 -j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), + call.add_rule( + 'ofake_dev', + '-p udp -m udp --sport 68 --dport 67 -j RETURN'), + call.add_rule('ofake_dev', '-j $sfake_dev'), + call.add_rule( + 'ofake_dev', + '-p udp -m udp --sport 67 --dport 68 -j DROP'), + call.add_rule( + 'ofake_dev', '-m state --state INVALID -j DROP'), + call.add_rule( + 'ofake_dev', + '-m state --state RELATED,ESTABLISHED -j RETURN'), + call.add_rule('ofake_dev', '-j $sg-fallback'), + call.add_rule('sg-chain', '-j ACCEPT')] + self.v4filter_inst.assert_has_calls(calls) + + def test_ip_spoofing_no_fixed_ips(self): + port = {'device': 'tapfake_dev', + 'mac_address': 'ff:ff:ff:ff', + 'fixed_ips': []} + self.firewall.prepare_port_filter(port) + calls = [call.add_chain('sg-fallback'), + call.add_rule('sg-fallback', '-j DROP'), + call.ensure_remove_chain('sg-chain'), + call.add_chain('sg-chain'), + call.add_chain('ifake_dev'), + call.add_rule('FORWARD', + '-m physdev --physdev-out tapfake_dev ' + '--physdev-is-bridged ' + '-j $sg-chain'), + call.add_rule('sg-chain', + '-m physdev --physdev-out tapfake_dev ' + '--physdev-is-bridged ' + '-j $ifake_dev'), + call.add_rule( + 'ifake_dev', '-m state --state INVALID -j DROP'), + call.add_rule( + 'ifake_dev', + '-m state --state RELATED,ESTABLISHED -j RETURN'), + call.add_rule('ifake_dev', '-j $sg-fallback'), + call.add_chain('ofake_dev'), + call.add_rule('FORWARD', + '-m physdev --physdev-in tapfake_dev ' + '--physdev-is-bridged ' + '-j $sg-chain'), + call.add_rule('sg-chain', + '-m physdev --physdev-in tapfake_dev ' + '--physdev-is-bridged ' + '-j $ofake_dev'), + call.add_rule('INPUT', + '-m physdev --physdev-in tapfake_dev ' + '--physdev-is-bridged ' + '-j $ofake_dev'), + call.add_chain('sfake_dev'), + call.add_rule( + 'sfake_dev', + '-m mac --mac-source ff:ff:ff:ff -j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), call.add_rule( 'ofake_dev', '-p udp -m udp --sport 68 --dport 67 -j RETURN'), diff --git a/neutron/tests/unit/test_security_groups_rpc.py b/neutron/tests/unit/test_security_groups_rpc.py index 0a24b7ad3e..02f50777a8 100644 --- a/neutron/tests/unit/test_security_groups_rpc.py +++ b/neutron/tests/unit/test_security_groups_rpc.py @@ -28,7 +28,9 @@ from neutron.agent import rpc as agent_rpc from neutron.agent import securitygroups_rpc as sg_rpc from neutron import context from neutron.db import securitygroups_rpc_base as sg_db_rpc +from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import securitygroup as ext_sg +from neutron.manager import NeutronManager from neutron.openstack.common.rpc import proxy from neutron.tests import base from neutron.tests.unit import test_extension_security_group as test_sg @@ -47,7 +49,7 @@ class FakeSGCallback(sg_db_rpc.SecurityGroupServerRpcCallbackMixin): class SGServerRpcCallBackMixinTestCase(test_sg.SecurityGroupDBTestCase): - def setUp(self): + def setUp(self, plugin=None): super(SGServerRpcCallBackMixinTestCase, self).setUp() self.rpc = FakeSGCallback() @@ -103,6 +105,69 @@ class SGServerRpcCallBackMixinTestCase(test_sg.SecurityGroupDBTestCase): expected) self._delete('ports', port_id1) + def test_security_group_rules_for_devices_ipv4_ingress_addr_pair(self): + plugin_obj = NeutronManager.get_plugin() + if ('allowed-address-pairs' + not in plugin_obj.supported_extension_aliases): + self.skipTest("Test depeneds on allowed-address-pairs extension") + fake_prefix = test_fw.FAKE_PREFIX['IPv4'] + with self.network() as n: + with nested(self.subnet(n), + self.security_group()) as (subnet_v4, + sg1): + sg1_id = sg1['security_group']['id'] + rule1 = self._build_security_group_rule( + sg1_id, + 'ingress', 'tcp', '22', + '22') + rule2 = self._build_security_group_rule( + sg1_id, + 'ingress', 'tcp', '23', + '23', fake_prefix) + rules = { + 'security_group_rules': [rule1['security_group_rule'], + rule2['security_group_rule']]} + res = self._create_security_group_rule(self.fmt, rules) + self.deserialize(self.fmt, res) + self.assertEqual(res.status_int, 201) + address_pairs = [{'mac_address': '00:00:00:00:00:01', + 'ip_address': '10.0.0.0/24'}, + {'mac_address': '00:00:00:00:00:01', + 'ip_address': '11.0.0.1'}] + res1 = self._create_port( + self.fmt, n['network']['id'], + security_groups=[sg1_id], + arg_list=(addr_pair.ADDRESS_PAIRS,), + allowed_address_pairs=address_pairs) + ports_rest1 = self.deserialize(self.fmt, res1) + port_id1 = ports_rest1['port']['id'] + self.rpc.devices = {port_id1: ports_rest1['port']} + devices = [port_id1, 'no_exist_device'] + ctx = context.get_admin_context() + ports_rpc = self.rpc.security_group_rules_for_devices( + ctx, devices=devices) + port_rpc = ports_rpc[port_id1] + expected = [{'direction': 'egress', 'ethertype': 'IPv4', + 'security_group_id': sg1_id}, + {'direction': 'egress', 'ethertype': 'IPv6', + 'security_group_id': sg1_id}, + {'direction': 'ingress', + 'protocol': 'tcp', 'ethertype': 'IPv4', + 'port_range_max': 22, + 'security_group_id': sg1_id, + 'port_range_min': 22}, + {'direction': 'ingress', 'protocol': 'tcp', + 'ethertype': 'IPv4', + 'port_range_max': 23, 'security_group_id': sg1_id, + 'port_range_min': 23, + 'source_ip_prefix': fake_prefix}, + ] + self.assertEqual(port_rpc['security_group_rules'], + expected) + self.assertEqual(port_rpc['allowed_address_pairs'], + address_pairs) + self._delete('ports', port_id1) + def test_security_group_rules_for_devices_ipv4_egress(self): fake_prefix = test_fw.FAKE_PREFIX['IPv4'] with self.network() as n: @@ -616,8 +681,8 @@ COMMIT """ % IPTABLES_ARG CHAINS_EMPTY = 'FORWARD|INPUT|OUTPUT|local|sg-chain|sg-fallback' -CHAINS_1 = CHAINS_EMPTY + '|i_port1|o_port1' -CHAINS_2 = CHAINS_1 + '|i_port2|o_port2' +CHAINS_1 = CHAINS_EMPTY + '|i_port1|o_port1|s_port1' +CHAINS_2 = CHAINS_1 + '|i_port2|o_port2|s_port2' IPTABLES_ARG['chains'] = CHAINS_1 @@ -632,6 +697,7 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] [0:0] -A FORWARD -j neutron-filter-top [0:0] -A OUTPUT -j neutron-filter-top [0:0] -A neutron-filter-top -j %(bn)s-local @@ -645,8 +711,8 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port1 [0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback [0:0] -A %(bn)s-FORWARD %(physdev_mod)s --physdev-EGRESS tap_port1 \ @@ -655,9 +721,11 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP +[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \ +RETURN +[0:0] -A %(bn)s-s_port1 -j DROP [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP +[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1 [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -668,6 +736,7 @@ COMMIT # Completed by iptables_manager """ % IPTABLES_ARG + IPTABLES_FILTER_1_2 = """# Generated by iptables_manager *filter :neutron-filter-top - [0:0] @@ -679,6 +748,7 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] [0:0] -A FORWARD -j neutron-filter-top [0:0] -A OUTPUT -j neutron-filter-top [0:0] -A neutron-filter-top -j %(bn)s-local @@ -692,8 +762,8 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port1 [0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN [0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback @@ -703,9 +773,11 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP +[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \ +RETURN +[0:0] -A %(bn)s-s_port1 -j DROP [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP +[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1 [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -731,6 +803,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] [0:0] -A FORWARD -j neutron-filter-top [0:0] -A OUTPUT -j neutron-filter-top [0:0] -A neutron-filter-top -j %(bn)s-local @@ -744,8 +818,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port1 [0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN [0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback @@ -755,9 +829,11 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP +[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 \ +-j RETURN +[0:0] -A %(bn)s-s_port1 -j DROP [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP +[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1 [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -769,8 +845,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port2 [0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN [0:0] -A %(bn)s-i_port2 -j %(bn)s-sg-fallback @@ -780,9 +856,11 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port2 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \ %(physdev_is_bridged)s -j %(bn)s-o_port2 -[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP +[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 \ +-j RETURN +[0:0] -A %(bn)s-s_port2 -j DROP [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP +[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2 [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN @@ -806,6 +884,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] [0:0] -A FORWARD -j neutron-filter-top [0:0] -A OUTPUT -j neutron-filter-top [0:0] -A neutron-filter-top -j %(bn)s-local @@ -819,8 +899,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port1 [0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback [0:0] -A %(bn)s-FORWARD %(physdev_mod)s --physdev-EGRESS tap_port1 \ @@ -829,9 +909,11 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP +[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \ +RETURN +[0:0] -A %(bn)s-s_port1 -j DROP [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP +[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1 [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -843,8 +925,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port2 [0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN [0:0] -A %(bn)s-i_port2 -j %(bn)s-sg-fallback @@ -854,9 +936,11 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port2 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \ %(physdev_is_bridged)s -j %(bn)s-o_port2 -[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP +[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 -j \ +RETURN +[0:0] -A %(bn)s-s_port2 -j DROP [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP +[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2 [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN @@ -880,6 +964,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] :%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] +:%(bn)s-(%(chains)s) - [0:0] [0:0] -A FORWARD -j neutron-filter-top [0:0] -A OUTPUT -j neutron-filter-top [0:0] -A neutron-filter-top -j %(bn)s-local @@ -893,8 +979,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port1 [0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN [0:0] -A %(bn)s-i_port1 -p icmp -j RETURN @@ -905,9 +991,11 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP +[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \ +RETURN +[0:0] -A %(bn)s-s_port1 -j DROP [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP +[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1 [0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -919,8 +1007,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-i_port2 [0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN -[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \ --j RETURN +[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \ +RETURN [0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN [0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN [0:0] -A %(bn)s-i_port2 -p icmp -j RETURN @@ -931,9 +1019,11 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port2 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \ %(physdev_is_bridged)s -j %(bn)s-o_port2 -[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP +[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 -j \ +RETURN +[0:0] -A %(bn)s-s_port2 -j DROP [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN -[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP +[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2 [0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP [0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN @@ -944,6 +1034,7 @@ COMMIT # Completed by iptables_manager """ % IPTABLES_ARG + IPTABLES_ARG['chains'] = CHAINS_EMPTY IPTABLES_FILTER_EMPTY = """# Generated by iptables_manager *filter @@ -997,7 +1088,6 @@ IPTABLES_FILTER_V6_1 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP [0:0] -A %(bn)s-o_port1 -p icmpv6 -j RETURN [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -1007,7 +1097,9 @@ COMMIT # Completed by iptables_manager """ % IPTABLES_ARG + IPTABLES_ARG['chains'] = CHAINS_2 + IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager *filter :neutron-filter-top - [0:0] @@ -1041,7 +1133,6 @@ IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port1 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-o_port1 -[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP [0:0] -A %(bn)s-o_port1 -p icmpv6 -j RETURN [0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN @@ -1059,7 +1150,6 @@ IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager %(physdev_is_bridged)s -j %(bn)s-o_port2 [0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \ %(physdev_is_bridged)s -j %(bn)s-o_port2 -[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP [0:0] -A %(bn)s-o_port2 -p icmpv6 -j RETURN [0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP [0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN @@ -1246,7 +1336,6 @@ class TestSecurityGroupAgentWithIptables(base.BaseTestCase): self.agent.security_groups_member_updated(['security_group1']) self.agent.remove_devices_filter(['tap_port2']) self.agent.remove_devices_filter(['tap_port1']) - self.mox.VerifyAll() def test_security_group_rule_updated(self): @@ -1325,6 +1414,7 @@ class TestSecurityGroupAgentWithOVSIptables( value = value.replace('tap_port', 'taptap_port') value = value.replace('o_port', 'otap_port') value = value.replace('i_port', 'itap_port') + value = value.replace('s_port', 'stap_port') return super( TestSecurityGroupAgentWithOVSIptables, self)._regex(value)