From 662731e9b70e650c22da33ca8c3b29d5593fd023 Mon Sep 17 00:00:00 2001 From: Sridar Kandaswamy Date: Mon, 11 Aug 2014 12:39:13 -0700 Subject: [PATCH] Changes to support FWaaS in a DVR based environment Implementation of Spec to address the changes required for FWaaS to work with DVR to handle: * Perimeter Firewall support on N - S traffic * Ensure that E - W DVR traffic is not broken. DocImpact Change-Id: Iba78e534ccf347ea6270aabc939a489dd40a7b9e Implements: blueprint neutron-dvr-fwaas --- neutron/agent/l3_agent.py | 6 +- .../agents/l3reference/firewall_l3_agent.py | 11 +- .../firewall/drivers/linux/iptables_fwaas.py | 127 ++++++++++++------ .../l3reference/test_firewall_l3_agent.py | 4 + .../drivers/linux/test_iptables_fwaas.py | 58 ++++++-- 5 files changed, 148 insertions(+), 58 deletions(-) diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index 11fcbdbf2c..fdde3c9b6b 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -732,7 +732,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): for c, r in self.metadata_nat_rules(): ri.iptables_manager.ipv4['nat'].add_rule(c, r) ri.iptables_manager.apply() - super(L3NATAgent, self).process_router_add(ri) + self.process_router_add(ri) if self.conf.enable_metadata_proxy: self._spawn_metadata_proxy(ri.router_id, ri.ns_name) @@ -1198,6 +1198,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): root_helper=self.root_helper, namespace=snat_ns_name, use_ipv6=self.use_ipv6) + # kicks the FW Agent to add rules for the snat namespace + self.process_router_add(ri) def external_gateway_added(self, ri, ex_gw_port, interface_name): if ri.router['distributed']: @@ -1520,6 +1522,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): device.route.add_gateway(str(fip_2_rtr.ip), table=FIP_RT_TBL) #setup the NAT rules and chains self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules') + # kicks the FW Agent to add rules for the IR namespace if configured + self.process_router_add(ri) def floating_ip_added_dist(self, ri, fip): """Add floating IP to FIP namespace.""" diff --git a/neutron/services/firewall/agents/l3reference/firewall_l3_agent.py b/neutron/services/firewall/agents/l3reference/firewall_l3_agent.py index cfcdb4b17d..689209419a 100644 --- a/neutron/services/firewall/agents/l3reference/firewall_l3_agent.py +++ b/neutron/services/firewall/agents/l3reference/firewall_l3_agent.py @@ -140,6 +140,7 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): # call into the driver try: self.fwaas_driver.__getattribute__(func_name)( + self.conf.agent_mode, router_info_list, fw) if fw['admin_state_up']: @@ -174,7 +175,10 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): """ if fw['status'] == constants.PENDING_DELETE: try: - self.fwaas_driver.delete_firewall(router_info_list, fw) + self.fwaas_driver.delete_firewall( + self.conf.agent_mode, + router_info_list, + fw) self.fwplugin_rpc.firewall_deleted( ctx, fw['id']) @@ -189,7 +193,10 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): else: # PENDING_UPDATE, PENDING_CREATE, ... try: - self.fwaas_driver.update_firewall(router_info_list, fw) + self.fwaas_driver.update_firewall( + self.conf.agent_mode, + router_info_list, + fw) if fw['admin_state_up']: status = constants.ACTIVE else: diff --git a/neutron/services/firewall/drivers/linux/iptables_fwaas.py b/neutron/services/firewall/drivers/linux/iptables_fwaas.py index 8dc3fd9f6f..1a04a77bc9 100644 --- a/neutron/services/firewall/drivers/linux/iptables_fwaas.py +++ b/neutron/services/firewall/drivers/linux/iptables_fwaas.py @@ -39,6 +39,10 @@ IPV6 = 'ipv6' IP_VER_TAG = {IPV4: 'v4', IPV6: 'v6'} +INTERNAL_DEV_PREFIX = 'qr-' +SNAT_INT_DEV_PREFIX = 'sg-' +ROUTER_2_FIP_DEV_PREFIX = 'rfp-' + class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): """IPTables driver for Firewall As A Service.""" @@ -46,99 +50,132 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): def __init__(self): LOG.debug(_("Initializing fwaas iptables driver")) - def create_firewall(self, apply_list, firewall): + def create_firewall(self, agent_mode, apply_list, firewall): LOG.debug(_('Creating firewall %(fw_id)s for tenant %(tid)s)'), {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) try: if firewall['admin_state_up']: - self._setup_firewall(apply_list, firewall) + self._setup_firewall(agent_mode, apply_list, firewall) else: - self.apply_default_policy(apply_list, firewall) + self.apply_default_policy(agent_mode, apply_list, firewall) except (LookupError, RuntimeError): # catch known library exceptions and raise Fwaas generic exception LOG.exception(_("Failed to create firewall: %s"), firewall['id']) raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) - def delete_firewall(self, apply_list, firewall): + def _get_ipt_mgrs_with_if_prefix(self, agent_mode, router_info): + """Gets the iptables manager along with the if prefix to apply rules. + + With DVR we can have differing namespaces depending on which agent + (on Network or Compute node). Also, there is an associated i/f for + each namespace. The iptables on the relevant namespace and matching + i/f are provided. On the Network node we could have both the snat + namespace and a fip so this is provided back as a list - so in that + scenario rules can be applied on both. + """ + if not router_info.router['distributed']: + return [{'ipt': router_info.iptables_manager, + 'if_prefix': INTERNAL_DEV_PREFIX}] + ipt_mgrs = [] + # TODO(sridar): refactor to get strings to a common location. + if agent_mode == 'dvr_snat': + if router_info.snat_iptables_manager: + ipt_mgrs.append({'ipt': router_info.snat_iptables_manager, + 'if_prefix': SNAT_INT_DEV_PREFIX}) + if router_info.dist_fip_count: + # handle the fip case on n/w or compute node. + ipt_mgrs.append({'ipt': router_info.iptables_manager, + 'if_prefix': ROUTER_2_FIP_DEV_PREFIX}) + return ipt_mgrs + + def delete_firewall(self, agent_mode, apply_list, firewall): LOG.debug(_('Deleting firewall %(fw_id)s for tenant %(tid)s)'), {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) fwid = firewall['id'] try: for router_info in apply_list: - ipt_mgr = router_info.iptables_manager - self._remove_chains(fwid, ipt_mgr) - self._remove_default_chains(ipt_mgr) - # apply the changes immediately (no defer in firewall path) - ipt_mgr.defer_apply_off() + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() except (LookupError, RuntimeError): # catch known library exceptions and raise Fwaas generic exception LOG.exception(_("Failed to delete firewall: %s"), fwid) raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) - def update_firewall(self, apply_list, firewall): + def update_firewall(self, agent_mode, apply_list, firewall): LOG.debug(_('Updating firewall %(fw_id)s for tenant %(tid)s)'), {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) try: if firewall['admin_state_up']: - self._setup_firewall(apply_list, firewall) + self._setup_firewall(agent_mode, apply_list, firewall) else: - self.apply_default_policy(apply_list, firewall) + self.apply_default_policy(agent_mode, apply_list, firewall) except (LookupError, RuntimeError): # catch known library exceptions and raise Fwaas generic exception LOG.exception(_("Failed to update firewall: %s"), firewall['id']) raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) - def apply_default_policy(self, apply_list, firewall): + def apply_default_policy(self, agent_mode, apply_list, firewall): LOG.debug(_('Applying firewall %(fw_id)s for tenant %(tid)s)'), {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) fwid = firewall['id'] try: for router_info in apply_list: - ipt_mgr = router_info.iptables_manager + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + # the following only updates local memory; no hole in FW + ipt_mgr = ipt_if_prefix['ipt'] + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) - # the following only updates local memory; no hole in FW - self._remove_chains(fwid, ipt_mgr) - self._remove_default_chains(ipt_mgr) + # create default 'DROP ALL' policy chain + self._add_default_policy_chain_v4v6(ipt_mgr) + self._enable_policy_chain(fwid, ipt_if_prefix) - # create default 'DROP ALL' policy chain - self._add_default_policy_chain_v4v6(ipt_mgr) - self._enable_policy_chain(fwid, ipt_mgr) - - # apply the changes immediately (no defer in firewall path) - ipt_mgr.defer_apply_off() + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() except (LookupError, RuntimeError): # catch known library exceptions and raise Fwaas generic exception LOG.exception(_("Failed to apply default policy on firewall: %s"), fwid) raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) - def _setup_firewall(self, apply_list, firewall): + def _setup_firewall(self, agent_mode, apply_list, firewall): fwid = firewall['id'] for router_info in apply_list: - ipt_mgr = router_info.iptables_manager + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + # the following only updates local memory; no hole in FW + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) - # the following only updates local memory; no hole in FW - self._remove_chains(fwid, ipt_mgr) - self._remove_default_chains(ipt_mgr) + # create default 'DROP ALL' policy chain + self._add_default_policy_chain_v4v6(ipt_mgr) + #create chain based on configured policy + self._setup_chains(firewall, ipt_if_prefix) - # create default 'DROP ALL' policy chain - self._add_default_policy_chain_v4v6(ipt_mgr) - #create chain based on configured policy - self._setup_chains(firewall, ipt_mgr) - - # apply the changes immediately (no defer in firewall path) - ipt_mgr.defer_apply_off() + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() def _get_chain_name(self, fwid, ver, direction): return '%s%s%s' % (CHAIN_NAME_PREFIX[direction], IP_VER_TAG[ver], fwid) - def _setup_chains(self, firewall, ipt_mgr): + def _setup_chains(self, firewall, ipt_if_prefix): """Create Fwaas chain using the rules in the policy """ fw_rules_list = firewall['firewall_rule_list'] fwid = firewall['id'] + ipt_mgr = ipt_if_prefix['ipt'] #default rules for invalid packets and established sessions invalid_rule = self._drop_invalid_packets_rule() @@ -170,7 +207,7 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION) table.add_rule(ichain_name, iptbl_rule) table.add_rule(ochain_name, iptbl_rule) - self._enable_policy_chain(fwid, ipt_mgr) + self._enable_policy_chain(fwid, ipt_if_prefix) def _remove_default_chains(self, nsid): """Remove fwaas default policy chain.""" @@ -204,8 +241,10 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): for rule in rules: table.add_rule(chain_name, rule) - def _enable_policy_chain(self, fwid, ipt_mgr): + def _enable_policy_chain(self, fwid, ipt_if_prefix): bname = iptables_manager.binary_name + ipt_mgr = ipt_if_prefix['ipt'] + if_prefix = ipt_if_prefix['if_prefix'] for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']), (IPV6, ipt_mgr.ipv6['filter'])]: @@ -213,20 +252,20 @@ class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): chain_name = self._get_chain_name(fwid, ver, direction) chain_name = iptables_manager.get_chain_name(chain_name) if chain_name in tbl.chains: - jump_rule = ['%s qr-+ -j %s-%s' % (IPTABLES_DIR[direction], - bname, chain_name)] - self._add_rules_to_chain(ipt_mgr, ver, 'FORWARD', - jump_rule) + jump_rule = ['%s %s+ -j %s-%s' % (IPTABLES_DIR[direction], + if_prefix, bname, chain_name)] + self._add_rules_to_chain(ipt_mgr, + ver, 'FORWARD', jump_rule) #jump to DROP_ALL policy chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN) - jump_rule = ['-o qr-+ -j %s-%s' % (bname, chain_name)] + jump_rule = ['-o %s+ -j %s-%s' % (if_prefix, bname, chain_name)] self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule) self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule) #jump to DROP_ALL policy chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN) - jump_rule = ['-i qr-+ -j %s-%s' % (bname, chain_name)] + jump_rule = ['-i %s+ -j %s-%s' % (if_prefix, bname, chain_name)] self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule) self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule) diff --git a/neutron/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py b/neutron/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py index a69ef54a9a..113377b462 100644 --- a/neutron/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py +++ b/neutron/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py @@ -249,6 +249,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): 'admin_state_up': True}] fake_router = {'id': 1111, 'tenant_id': 2} self.api.plugin_rpc = mock.Mock() + agent_mode = 'legacy' ri = mock.Mock() ri.router = fake_router routers = [ri.router] @@ -280,6 +281,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): ri.router['tenant_id']) mock_get_firewalls_for_tenant.assert_called_once_with(ctx) mock_driver_update_firewall.assert_called_once_with( + agent_mode, routers, fake_firewall_list[0]) @@ -292,6 +294,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): fake_firewall_list = [{'id': 0, 'tenant_id': 1, 'status': constants.PENDING_DELETE}] fake_router = {'id': 1111, 'tenant_id': 2} + agent_mode = 'legacy' self.api.plugin_rpc = mock.Mock() ri = mock.Mock() ri.router = fake_router @@ -324,6 +327,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): ri.router['tenant_id']) mock_get_firewalls_for_tenant.assert_called_once_with(ctx) mock_driver_delete_firewall.assert_called_once_with( + agent_mode, routers, fake_firewall_list[0]) diff --git a/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py b/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py index 472586547c..29f532220b 100644 --- a/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py +++ b/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py @@ -90,10 +90,12 @@ class IptablesFwaasTestCase(base.BaseTestCase): 'firewall_rule_list': rule_list} return fw_inst - def _fake_apply_list(self, router_count=1): + def _fake_apply_list(self, router_count=1, distributed=False, + distributed_mode=None): apply_list = [] while router_count > 0: iptables_inst = mock.Mock() + router_inst = {'distributed': distributed} v4filter_inst = mock.Mock() v6filter_inst = mock.Mock() v4filter_inst.chains = [] @@ -102,15 +104,29 @@ class IptablesFwaasTestCase(base.BaseTestCase): iptables_inst.ipv6 = {'filter': v6filter_inst} router_info_inst = mock.Mock() router_info_inst.iptables_manager = iptables_inst + router_info_inst.snat_iptables_manager = iptables_inst + if distributed_mode == 'dvr': + router_info_inst.dist_fip_count = 1 + router_info_inst.router = router_inst apply_list.append(router_info_inst) router_count -= 1 return apply_list - def _setup_firewall_with_rules(self, func, router_count=1): - apply_list = self._fake_apply_list(router_count=router_count) + def _setup_firewall_with_rules(self, func, router_count=1, + distributed=False, distributed_mode=None): + apply_list = self._fake_apply_list(router_count=router_count, + distributed=distributed, distributed_mode=distributed_mode) rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) firewall = self._fake_firewall(rule_list) - func(apply_list, firewall) + if distributed: + if distributed_mode == 'dvr_snat': + if_prefix = 'sg-+' + if distributed_mode == 'dvr': + if_prefix = 'rfp-+' + else: + if_prefix = 'qr-+' + distributed_mode = 'legacy' + func(distributed_mode, apply_list, firewall) invalid_rule = '-m state --state INVALID -j DROP' est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT' rule1 = '-p tcp --dport 80 -s 10.24.4.2 -j ACCEPT' @@ -138,19 +154,23 @@ class IptablesFwaasTestCase(base.BaseTestCase): mock.call.add_rule(ingress_chain, rule2), mock.call.add_rule(egress_chain, rule2), mock.call.add_rule('FORWARD', - '-o qr-+ -j %s' % ipt_mgr_ichain), + '-o %s -j %s' % (if_prefix, + ipt_mgr_ichain)), mock.call.add_rule('FORWARD', - '-i qr-+ -j %s' % ipt_mgr_echain), + '-i %s -j %s' % (if_prefix, + ipt_mgr_echain)), mock.call.add_rule('FORWARD', - '-o qr-+ -j %s-fwaas-defau' % bname), + '-o %s -j %s-fwaas-defau' % (if_prefix, + bname)), mock.call.add_rule('FORWARD', - '-i qr-+ -j %s-fwaas-defau' % bname)] + '-i %s -j %s-fwaas-defau' % (if_prefix, + bname))] v4filter_inst.assert_has_calls(calls) def test_create_firewall_no_rules(self): apply_list = self._fake_apply_list() firewall = self._fake_firewall_no_rule() - self.firewall.create_firewall(apply_list, firewall) + self.firewall.create_firewall('legacy', apply_list, firewall) invalid_rule = '-m state --state INVALID -j DROP' est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT' bname = fwaas.iptables_manager.binary_name @@ -195,7 +215,7 @@ class IptablesFwaasTestCase(base.BaseTestCase): def test_delete_firewall(self): apply_list = self._fake_apply_list() firewall = self._fake_firewall_no_rule() - self.firewall.delete_firewall(apply_list, firewall) + self.firewall.delete_firewall('legacy', apply_list, firewall) ingress_chain = 'iv4%s' % firewall['id'] egress_chain = 'ov4%s' % firewall['id'] calls = [mock.call.ensure_remove_chain(ingress_chain), @@ -207,10 +227,26 @@ class IptablesFwaasTestCase(base.BaseTestCase): apply_list = self._fake_apply_list() rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) firewall = self._fake_firewall_with_admin_down(rule_list) - self.firewall.create_firewall(apply_list, firewall) + self.firewall.create_firewall('legacy', apply_list, firewall) calls = [mock.call.ensure_remove_chain('iv4fake-fw-uuid'), mock.call.ensure_remove_chain('ov4fake-fw-uuid'), mock.call.ensure_remove_chain('fwaas-default-policy'), mock.call.add_chain('fwaas-default-policy'), mock.call.add_rule('fwaas-default-policy', '-j DROP')] apply_list[0].iptables_manager.ipv4['filter'].assert_has_calls(calls) + + def test_create_firewall_with_rules_dvr_snat(self): + self._setup_firewall_with_rules(self.firewall.create_firewall, + distributed=True, distributed_mode='dvr_snat') + + def test_update_firewall_with_rules_dvr_snat(self): + self._setup_firewall_with_rules(self.firewall.update_firewall, + distributed=True, distributed_mode='dvr_snat') + + def test_create_firewall_with_rules_dvr(self): + self._setup_firewall_with_rules(self.firewall.create_firewall, + distributed=True, distributed_mode='dvr') + + def test_update_firewall_with_rules_dvr(self): + self._setup_firewall_with_rules(self.firewall.update_firewall, + distributed=True, distributed_mode='dvr')