diff --git a/doc/source/devstack.rst b/doc/source/devstack.rst index ca51bfa6cd..a365d1f804 100644 --- a/doc/source/devstack.rst +++ b/doc/source/devstack.rst @@ -89,6 +89,24 @@ lines to the policy.json file:: "delete_flow_classifier": "rule:admin_only", "get_flow_classifier": "rule:admin_only" +FWAAS (V1) Driver: +~~~~~~~~~~~~~ + +Add neutron-fwaas repo as an external repository and configure following flags in ``local.conf``:: + + [[local|localrc]] + enable_plugin neutron-fwaas https://git.openstack.org/openstack/neutron-fwaas + ENABLED_SERVICES+=,q-fwaas + + [[post-config|$NEUTRON_CONF]] + [DEFAULT] + service_plugins = neutron_fwaas.services.firewall.fwaas_plugin.FirewallPlugin + + [fwaas] + enabled = True + driver = vmware_nsx.services.fwaas.nsx_v.edge_fwaas_driver.EdgeFwaasDriver + + NSXv3 ----- diff --git a/releasenotes/notes/nsxv-fwaas-driver-4c457dee3fc3bae2.yaml b/releasenotes/notes/nsxv-fwaas-driver-4c457dee3fc3bae2.yaml new file mode 100644 index 0000000000..c179dea429 --- /dev/null +++ b/releasenotes/notes/nsxv-fwaas-driver-4c457dee3fc3bae2.yaml @@ -0,0 +1,6 @@ +--- +prelude: > + The NSX-V plugin can suppport FWaaS-V1 for setting router edges firewall rules. +features: + - | + The NSX-V plugin can suppport FWaaS-V1 for setting router edges firewall rules. diff --git a/tools/tox_install.sh b/tools/tox_install.sh index f9072db66c..71ca229fab 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -8,6 +8,7 @@ ${DIR}/tox_install_project.sh networking-l2gw networking_l2gw $* ${DIR}/tox_install_project.sh networking-sfc networking_sfc $* ${DIR}/tox_install_project.sh neutron-lbaas neutron_lbaas $* ${DIR}/tox_install_project.sh vmware-nsxlib vmware_nsxlib $* +${DIR}/tox_install_project.sh neutron-fwaas neutron_fwaas $* CONSTRAINTS_FILE=$1 shift diff --git a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py index db5a8e86d0..9bac17873b 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py @@ -317,7 +317,7 @@ class RouterSharedDriver(router_driver.RouterBaseDriver): fake_fw_rules += ( nsx_v_md_proxy.get_router_fw_rules()) - # TODO(berlin): Add fw rules if fw service is supported + # TODO(asarfaty): Add fwaas rules when fwaas supports shared routers fake_fw = {'firewall_rule_list': fake_fw_rules} edge_utils.update_firewall(self.nsx_v, context, target_router_id, fake_fw, allow_external=allow_external) diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index e9efd0a947..829162606f 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -127,6 +127,7 @@ from vmware_nsx.plugins.nsx_v.vshield import edge_utils from vmware_nsx.plugins.nsx_v.vshield import securitygroup_utils from vmware_nsx.plugins.nsx_v.vshield import vcns_driver from vmware_nsx.services.flowclassifier.nsx_v import utils as fc_utils +from vmware_nsx.services.fwaas.nsx_v import fwaas_callbacks LOG = logging.getLogger(__name__) PORTGROUP_PREFIX = 'dvportgroup' @@ -272,6 +273,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # will happen only once self.start_rpc_listeners_called = False + # Init the FWaaS support + self._init_fwaas() + # Service insertion driver register self._si_handler = fc_utils.NsxvServiceInsertionHandler(self) registry.subscribe(self.add_vms_to_service_insertion, @@ -377,6 +381,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self.start_rpc_listeners_called = True return self.conn.consume_in_threads() + def _init_fwaas(self): + # Bind FWaaS callbacks to the driver + self.fwaas_callbacks = fwaas_callbacks.NsxvFwaasCallbacks() + def _ext_extend_network_dict(self, result, netdb): ctx = n_context.get_admin_context() with db_api.context_manager.writer.using(ctx): @@ -3459,7 +3467,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, def _update_subnets_and_dnat_firewall(self, context, router, router_id=None, allow_external=True): - fake_fw_rules = [] + fw_rules = [] if not router_id: router_id = router['id'] subnet_cidrs = self._find_router_subnets_cidrs(context, router['id']) @@ -3473,12 +3481,12 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, 'enabled': True, 'source_ip_address': subnet_cidrs, 'destination_ip_address': subnet_cidrs} - fake_fw_rules.append(fake_subnet_fw_rule) + fw_rules.append(fake_subnet_fw_rule) _, dnat_rules = self._get_nat_rules(context, router) # If metadata service is enabled, block access to inter-edge network if self.metadata_proxy_handler: - fake_fw_rules += nsx_v_md_proxy.get_router_fw_rules() + fw_rules += nsx_v_md_proxy.get_router_fw_rules() dnat_cidrs = [rule['dst'] for rule in dnat_rules] if dnat_cidrs: @@ -3487,10 +3495,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, 'action': 'allow', 'enabled': True, 'destination_ip_address': dnat_cidrs} - fake_fw_rules.append(fake_dnat_fw_rule) + fw_rules.append(fake_dnat_fw_rule) nosnat_fw_rules = self._get_nosnat_subnets_fw_rules( context, router) - fake_fw_rules.extend(nosnat_fw_rules) + fw_rules.extend(nosnat_fw_rules) # Get the load balancer rules in case they are refreshed edge_id = self._get_edge_id_by_rtr_id(context, router_id) @@ -3506,10 +3514,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, 'name': vsm_rule['name'], 'ruleId': vsm_rule['ruleId'] } - fake_fw_rules.append(lb_fw_rule) + fw_rules.append(lb_fw_rule) - # TODO(berlin): Add fw rules if fw service is supported - fake_fw = {'firewall_rule_list': fake_fw_rules} + # Add fw rules if FWaaS is enabled + fw_rules.extend(self.fwaas_callbacks.get_fwaas_rules_for_router( + context, router, router_id)) + + fake_fw = {'firewall_rule_list': fw_rules} try: edge_utils.update_firewall(self.nsx_v, context, router_id, fake_fw, allow_external=allow_external) diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_firewall_driver.py b/vmware_nsx/plugins/nsx_v/vshield/edge_firewall_driver.py index cfc397e395..71129b1185 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_firewall_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_firewall_driver.py @@ -25,9 +25,11 @@ LOG = logging.getLogger(__name__) VSE_FWAAS_ALLOW = "accept" VSE_FWAAS_DENY = "deny" +VSE_FWAAS_REJECT = "reject" FWAAS_ALLOW = "allow" FWAAS_DENY = "deny" +FWAAS_REJECT = "reject" class EdgeFirewallDriver(object): @@ -43,6 +45,8 @@ class EdgeFirewallDriver(object): return VSE_FWAAS_ALLOW elif action == FWAAS_DENY: return VSE_FWAAS_DENY + elif action == FWAAS_REJECT: + return VSE_FWAAS_REJECT else: msg = _("Invalid action value %s in a firewall rule") % action raise vcns_exc.VcnsBadRequest(resource='firewall_rule', msg=msg) @@ -52,12 +56,14 @@ class EdgeFirewallDriver(object): return FWAAS_ALLOW elif action == VSE_FWAAS_DENY: return FWAAS_DENY + elif action == VSE_FWAAS_REJECT: + return FWAAS_REJECT else: msg = (_("Invalid action value %s in " "a vshield firewall rule") % action) raise vcns_exc.VcnsBadRequest(resource='firewall_rule', msg=msg) - def _get_port_range_from_min_max_ports(self, min_port, max_port): + def _get_port_range(self, min_port, max_port): if not min_port: return None if min_port == max_port: @@ -74,7 +80,7 @@ class EdgeFirewallDriver(object): "num1:num2" - a range "num1,num2,num3" - a list """ - if not port_str: + if not port_str or port_str == 'any': return [] if ':' in port_str: min_port, sep, max_port = port_str.partition(":") @@ -91,7 +97,7 @@ class EdgeFirewallDriver(object): else: return [int(port_str.strip())] - def _convert_firewall_rule(self, context, rule, index=None): + def _convert_firewall_rule(self, rule, index=None): vcns_rule = { "action": self._convert_firewall_action(rule['action']), "enabled": rule.get('enabled', True)} @@ -141,47 +147,59 @@ class EdgeFirewallDriver(object): vcns_rule['ruleTag'] = index return vcns_rule - def _restore_firewall_rule(self, context, edge_id, response): - rule = response - rule_binding = nsxv_db.get_nsxv_edge_firewallrule_binding_by_vseid( - context.session, edge_id, rule['ruleId']) - service = rule['application']['service'][0] - src_port_range = self._get_port_range_from_min_max_ports( - service['sourcePort'][0], service['sourcePort'][-1]) - dst_port_range = self._get_port_range_from_min_max_ports( - service['port'][0], service['port'][-1]) - fw_rule = { - 'firewall_rule': { - 'id': rule_binding['rule_id'], - 'source_ip_address': rule['source']['ipAddress'], - 'destination_ip_address': rule['destination']['ipAddress'], - 'protocol': service['protocol'], - 'destination_port': dst_port_range, - 'source_port': src_port_range, - 'action': self._restore_firewall_action(rule['action']), - 'enabled': rule['enabled']}} + def _restore_firewall_rule(self, context, edge_id, rule): + fw_rule = {} + try: + rule_binding = ( + nsxv_db.get_nsxv_edge_firewallrule_binding_by_vseid( + context.session, edge_id, rule['ruleId'])) + except nsx_exc.NsxPluginException: + rule_binding = None + fw_rule['id'] = (rule_binding['rule_id'] + if rule_binding else rule['ruleId']) + fw_rule['ruleId'] = rule['ruleId'] + if rule.get('source'): + src = rule['source'] + fw_rule['source_ip_address'] = src['ipAddress'] + fw_rule['source_vnic_groups'] = src['vnicGroupId'] + + if rule.get('destination'): + dest = rule['destination'] + fw_rule['destination_ip_address'] = dest['ipAddress'] + fw_rule['destination_vnic_groups'] = dest['vnicGroupId'] + + if 'application' in rule and 'service' in rule['application']: + service = rule['application']['service'][0] + fw_rule['protocol'] = service['protocol'] + fw_rule['source_port'] = self._get_port_range( + service['sourcePort'][0], service['sourcePort'][-1]) + fw_rule['destination_port'] = self._get_port_range( + service['port'][0], service['port'][-1]) + + fw_rule['action'] = self._restore_firewall_action(rule['action']) + fw_rule['enabled'] = rule['enabled'] if rule.get('name'): - fw_rule['firewall_rule']['name'] = rule['name'] + fw_rule['name'] = rule['name'] if rule.get('description'): - fw_rule['firewall_rule']['description'] = rule['description'] + fw_rule['description'] = rule['description'] return fw_rule - def _convert_firewall(self, context, firewall, allow_external=False): + def _convert_firewall(self, firewall, allow_external=False): #bulk configuration on firewall and rescheduling the rule binding ruleTag = 1 vcns_rules = [] - for rule in firewall['firewall_rule_list']: - tag = rule.get('ruleTag', ruleTag) - vcns_rule = self._convert_firewall_rule(context, rule, tag) - vcns_rules.append(vcns_rule) - if not rule.get('ruleTag'): - ruleTag += 1 if allow_external: vcns_rules.append( {'action': "accept", 'enabled': True, 'destination': {'vnicGroupId': ["external"]}, 'ruleTag': ruleTag}) + for rule in firewall['firewall_rule_list']: + tag = rule.get('ruleTag', ruleTag) + vcns_rule = self._convert_firewall_rule(rule, tag) + vcns_rules.append(vcns_rule) + if not rule.get('ruleTag'): + ruleTag += 1 return { 'featureType': "firewall_4.0", 'globalConfig': {'tcpTimeoutEstablished': 7200}, @@ -192,32 +210,10 @@ class EdgeFirewallDriver(object): res = {} res['firewall_rule_list'] = [] for rule in response['firewallRules']['firewallRules']: - rule_binding = ( - nsxv_db.get_nsxv_edge_firewallrule_binding_by_vseid( - context.session, edge_id, rule['ruleId'])) - if rule_binding is None: + if rule.get('ruleType') == 'default_policy': continue - service = rule['application']['service'][0] - src_port_range = self._get_port_range_from_min_max_ports( - service['sourcePort'][0], service['sourcePort'][-1]) - dst_port_range = self._get_port_range_from_min_max_ports( - service['port'][0], service['port'][-1]) - item = { - 'firewall_rule': { - 'id': rule_binding['rule_id'], - 'source_ip_address': rule['source']['ipAddress'], - 'destination_ip_address': rule[ - 'destination']['ipAddress'], - 'protocol': service['protocol'], - 'destination_port': dst_port_range, - 'source_port': src_port_range, - 'action': self._restore_firewall_action(rule['action']), - 'enabled': rule['enabled']}} - if rule.get('name'): - item['firewall_rule']['name'] = rule['name'] - if rule.get('description'): - item['firewall_rule']['description'] = rule['description'] - res['firewall_rule_list'].append(item) + firewall_rule = self._restore_firewall_rule(context, edge_id, rule) + res['firewall_rule_list'].append({'firewall_rule': firewall_rule}) return res def _get_firewall(self, edge_id): @@ -277,7 +273,7 @@ class EdgeFirewallDriver(object): rule_map = nsxv_db.get_nsxv_edge_firewallrule_binding( context.session, id, edge_id) vcns_rule_id = rule_map.rule_vseid - fwr_req = self._convert_firewall_rule(context, firewall_rule) + fwr_req = self._convert_firewall_rule(firewall_rule) try: self.vcns.update_firewall_rule(edge_id, vcns_rule_id, fwr_req) except vcns_exc.VcnsApiException: @@ -308,7 +304,7 @@ class EdgeFirewallDriver(object): rule_map = nsxv_db.get_nsxv_edge_firewallrule_binding( context.session, ref_rule_id, edge_id) ref_vcns_rule_id = rule_map.rule_vseid - fwr_req = self._convert_firewall_rule(context, firewall_rule) + fwr_req = self._convert_firewall_rule(firewall_rule) try: header = self.vcns.add_firewall_rule_above( edge_id, ref_vcns_rule_id, fwr_req)[0] @@ -334,7 +330,7 @@ class EdgeFirewallDriver(object): ref_vcns_rule_id = rule_map.rule_vseid fwr_vse_next = self._get_firewall_rule_next( context, edge_id, ref_vcns_rule_id) - fwr_req = self._convert_firewall_rule(context, firewall_rule) + fwr_req = self._convert_firewall_rule(firewall_rule) if fwr_vse_next: ref_vcns_rule_id = fwr_vse_next['ruleId'] try: @@ -379,7 +375,7 @@ class EdgeFirewallDriver(object): raise vcns_exc.VcnsBadRequest(resource='firewall_rule', msg=msg) def update_firewall(self, edge_id, firewall, context, allow_external=True): - config = self._convert_firewall(None, firewall, + config = self._convert_firewall(firewall, allow_external=allow_external) try: diff --git a/vmware_nsx/services/fwaas/__init__.py b/vmware_nsx/services/fwaas/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/fwaas/nsx_v/__init__.py b/vmware_nsx/services/fwaas/nsx_v/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver.py b/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver.py new file mode 100644 index 0000000000..11502fc103 --- /dev/null +++ b/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver.py @@ -0,0 +1,202 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import context as n_context +from neutron_lib.plugins import directory +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from neutron_fwaas.extensions import firewall as fw_ext +from neutron_fwaas.services.firewall.drivers import fwaas_base + +from vmware_nsx.common import locking +from vmware_nsx.plugins.nsx_v.vshield.common import ( + exceptions as vcns_exc) +from vmware_nsx.plugins.nsx_v.vshield import edge_utils +from vmware_nsx.plugins.nsx_v.vshield import vcns_driver + +LOG = logging.getLogger(__name__) +FWAAS_DRIVER_NAME = 'Fwaas NSX-V driver' +RULE_NAME_PREFIX = 'Fwaas-' + + +class EdgeFwaasDriver(fwaas_base.FwaasDriverBase): + """NSX-V driver for Firewall As A Service - V1.""" + + @property + def edge_manager(self): + return directory.get_plugin().edge_manager + + def __init__(self): + LOG.debug("Loading FWaaS NsxVDriver.") + super(EdgeFwaasDriver, self).__init__() + self._nsxv = vcns_driver.VcnsDriver(None) + + def _get_routers_edges(self, context, apply_list): + # Get edges for all the routers in the apply list. + # note that shared routers are currently not supported + edge_manager = self.edge_manager + edges = [] + for router_info in apply_list: + lookup_id = None + router_id = router_info.router_id + if router_info.router.get('distributed'): + # we need the plr edge id + lookup_id = edge_manager.get_plr_by_tlr_id( + context, router_id) + if router_info.router.get('router_type') == 'shared': + LOG.info("Cannot apply firewall to shared router %s", + router_id) + else: + # exclusive router + lookup_id = router_id + if lookup_id: + # look for the edge id in the DB + edge_id = edge_utils.get_router_edge_id(context, lookup_id) + if edge_id: + edges.append(edge_id) + return edges + + def _translate_rules(self, fwaas_rules): + translated_rules = [] + for rule in fwaas_rules: + if not rule['enabled']: + # skip disabled rules + continue + # Make sure the rule has a name, and it starts with the prefix + # (backend max name length is 30) + if rule.get('name'): + rule['name'] = RULE_NAME_PREFIX + rule['name'] + else: + rule['name'] = RULE_NAME_PREFIX + rule['id'] + rule['name'] = rule['name'][:30] + # source & destination should be lists + if rule.get('destination_ip_address'): + rule['destination_ip_address'] = [ + rule['destination_ip_address']] + if rule.get('source_ip_address'): + rule['source_ip_address'] = [rule['source_ip_address']] + translated_rules.append(rule) + + return translated_rules + + def _get_other_backend_rules(self, context, edge_id): + """Get a list of current backend rules from other applications + + Those rules should stay on the backend firewall, when updating the + FWaaS rules. + """ + try: + backend_fw = self._nsxv.get_firewall(context, edge_id) + backend_rules = backend_fw['firewall_rule_list'] + except vcns_exc.VcnsApiException: + # Need to create a new one + backend_rules = [] + + # remove old FWaaS rules from the rules list + relevant_rules = [] + for rule_item in backend_rules: + rule = rule_item['firewall_rule'] + if not rule.get('name', '').startswith(RULE_NAME_PREFIX): + relevant_rules.append(rule) + + return relevant_rules + + def _set_rules_on_edge(self, context, edge_id, fw_id, translated_rules): + """delete old FWaaS rules from the Edge, and add new ones + + Note that the edge might have other FW rules like NAT or LBaas + that should remain there. + """ + # Get the existing backend rules which do not belong to FWaaS + backend_rules = self._get_other_backend_rules(context, edge_id) + + # add new FWaaS rules at the end by their original order + backend_rules.extend(translated_rules) + + # update the backend + # allow_external is False because it was already added + try: + with locking.LockManager.get_lock(str(edge_id)): + self._nsxv.update_firewall( + edge_id, + {'firewall_rule_list': backend_rules}, + context, + allow_external=False) + except Exception as e: + # catch known library exceptions and raise Fwaas generic exception + LOG.error("Failed to update backend firewall %(fw)s: " + "%(e)s", {'e': e, 'fw': fw_id}) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def _create_or_update_firewall(self, agent_mode, apply_list, firewall): + # admin state down means default block rule firewall + if not firewall['admin_state_up']: + self.apply_default_policy(agent_mode, apply_list, firewall) + return + + context = n_context.get_admin_context() + + # Find out the relevant edges + router_edges = self._get_routers_edges(context, apply_list) + if not router_edges: + LOG.warning("Cannot apply the firewall to any of the routers %s", + apply_list) + return + + rules = self._translate_rules(firewall['firewall_rule_list']) + # update each edge + for edge_id in router_edges: + self._set_rules_on_edge( + context, edge_id, firewall['id'], rules) + + @log_helpers.log_method_call + def create_firewall(self, agent_mode, apply_list, firewall): + """Create the Firewall with a given policy. """ + self._create_or_update_firewall(agent_mode, apply_list, firewall) + + @log_helpers.log_method_call + def update_firewall(self, agent_mode, apply_list, firewall): + """Remove previous policy and apply the new policy.""" + self._create_or_update_firewall(agent_mode, apply_list, firewall) + + def _delete_firewall_or_set_default_policy(self, apply_list, firewall): + context = n_context.get_admin_context() + router_edges = self._get_routers_edges(context, apply_list) + if router_edges: + for edge_id in router_edges: + self._set_rules_on_edge(context, edge_id, firewall['id'], []) + + @log_helpers.log_method_call + def delete_firewall(self, agent_mode, apply_list, firewall): + """Delete firewall. + + Removes rules created by this instance from the backend firewall. + """ + self._delete_firewall_or_set_default_policy(apply_list, firewall) + + @log_helpers.log_method_call + def apply_default_policy(self, agent_mode, apply_list, firewall): + """Apply the default policy (deny all). + + The backend firewall always has this policy as default, so we only + need to delete the current rules. + """ + self._delete_firewall_or_set_default_policy(apply_list, firewall) + + def get_firewall_translated_rules(self, firewall): + if firewall['admin_state_up']: + return self._translate_rules(firewall['firewall_rule_list']) + return [] diff --git a/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks.py b/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks.py new file mode 100644 index 0000000000..408c591990 --- /dev/null +++ b/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks.py @@ -0,0 +1,115 @@ +# Copyright 2017 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_config import cfg + +from neutron.agent.l3 import router_info +from neutron.common import config as neutron_config # noqa +from neutron_fwaas.db.firewall import firewall_db # noqa +from neutron_fwaas.db.firewall import firewall_router_insertion_db \ + as fw_r_ins_db +from neutron_fwaas.services.firewall.agents.l3reference \ + import firewall_l3_agent +from neutron_lib import context as n_context +from neutron_lib.plugins import directory + + +class NsxvFwaasCallbacks(firewall_l3_agent.L3WithFWaaS): + """NSX-V RPC callbacks for Firewall As A Service - V1.""" + def __init__(self): + # The super code needs a configuration object with the neutron host + # and an agent_mode, hich our driver doesn't use. + neutron_conf = cfg.CONF + neutron_conf.agent_mode = 'nsx' + super(NsxvFwaasCallbacks, self).__init__(conf=neutron_conf) + + @property + def core_plugin(self): + return directory.get_plugin() + + # Override functions using the agent_api that is not used by our plugin + def _get_router_ids_for_fw(self, context, fw, to_delete=False): + """Return the router_ids either from fw dict or tenant routers.""" + if self._has_router_insertion_fields(fw): + # it is a new version of plugin + return (fw['del-router-ids'] if to_delete + else fw['add-router-ids']) + else: + return [router['id'] for router in + self._get_routers_in_project(context, fw['tenant_id'])] + + def _get_routers_in_project(self, context, project_id): + return self.core_plugin.get_routers( + context, + filters={'tenant_id': [project_id]}) + + def _router_dict_to_obj(self, r): + # The callbacks expect a router-info object + return router_info.RouterInfo( + None, r['id'], router=r, + agent_conf=None, + interface_driver=None, + use_ipv6=False) + + def _get_router_info_list_for_tenant(self, router_ids, tenant_id): + """Returns the list of router info objects on which to apply the fw.""" + context = n_context.get_admin_context() + tenant_routers = self._get_routers_in_project(context, tenant_id) + return [self._router_dict_to_obj(ri) for ri in tenant_routers + if ri['id'] in router_ids] + + def get_fwaas_rules_for_router(self, context, router, router_id): + """Return the list of (translated) fwaas rules for this router.""" + if not self.fwaas_enabled: + return [] + + ctx_elevated = context.elevated() + + # get all the relevant router info + # ("router" does not have all the fields) + router_data = self.core_plugin.get_router(ctx_elevated, router['id']) + if not router_data: + return [] + if router_data.get('distributed'): + # in case of distributed router router['id'] is the id of the + # neutron router + # and router_id is the plr/tlr (the one that is being updated) + if router_id == router['id']: + # Do not add firewall rules on the tlr router. + return [] + if router_data.get('router_type') == 'shared': + # Currently there is no FWaaS support for shared routers + return [] + + # Exclusive router or PLR + fw_id = self._get_router_firewall_id(ctx_elevated, router['id']) + if fw_id: + return self._get_fw_applicable_rules(ctx_elevated, fw_id) + return [] + + # TODO(asarfaty): add this api to fwaas firewall-router-insertion-db + def _get_router_firewall_id(self, context, router_id): + entry = context.session.query( + fw_r_ins_db.FirewallRouterAssociation).filter_by( + router_id=router_id).first() + if entry: + return entry.fw_id + + def _get_fw_applicable_rules(self, context, fw_id): + fw_list = self.fwplugin_rpc.get_firewalls_for_tenant(context) + for fw in fw_list: + if fw['id'] == fw_id: + return self.fwaas_driver.get_firewall_translated_rules(fw) + return [] diff --git a/vmware_nsx/tests/unit/db/test_migrations.py b/vmware_nsx/tests/unit/db/test_migrations.py index 639a2a43cf..9d012a59f6 100644 --- a/vmware_nsx/tests/unit/db/test_migrations.py +++ b/vmware_nsx/tests/unit/db/test_migrations.py @@ -86,10 +86,15 @@ TAAS_TABLES = { 'tap_id_associations', } +FWAAS_TABLES = { + 'firewall_router_associations', + 'cisco_firewall_associations', +} + # EXTERNAL_TABLES should contain all names of tables that are not related to # current repo. EXTERNAL_TABLES = (set(external.TABLES) | LBAAS_TABLES | - L2GW_TABLES | SFC_TABLES | TAAS_TABLES) + L2GW_TABLES | SFC_TABLES | TAAS_TABLES | FWAAS_TABLES) class _TestModelsMigrationsFoo(test_migrations._TestModelsMigrations): diff --git a/vmware_nsx/tests/unit/nsx_v/test_fwaas_driver.py b/vmware_nsx/tests/unit/nsx_v/test_fwaas_driver.py new file mode 100644 index 0000000000..f9ce643029 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_v/test_fwaas_driver.py @@ -0,0 +1,142 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import mock + +from vmware_nsx.services.fwaas.nsx_v import edge_fwaas_driver +from vmware_nsx.tests.unit.nsx_v import test_plugin as test_v_plugin + +FAKE_FW_ID = 'fake_fw_uuid' + + +class NsxvFwaasTestCase(test_v_plugin.NsxVPluginV2TestCase): + def setUp(self): + super(NsxvFwaasTestCase, self).setUp() + self.firewall = edge_fwaas_driver.EdgeFwaasDriver() + self.firewall._get_routers_edges = mock.Mock() + self.firewall._get_routers_edges.return_value = ['edge-1'] + + def _fake_rules_v4(self): + rule1 = {'enabled': True, + 'action': 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '80', + 'source_ip_address': '10.24.4.2', + 'id': 'fake-fw-rule1'} + rule2 = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '22', + 'id': 'fake-fw-rule2'} + rule3 = {'enabled': True, + 'action': 'reject', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '23', + 'id': 'fake-fw-rule3'} + return [rule1, rule2, rule3] + + def _fake_firewall_no_rule(self): + rule_list = [] + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': rule_list} + return fw_inst + + def _fake_firewall(self, rule_list): + _rule_list = copy.deepcopy(rule_list) + for rule in _rule_list: + rule['position'] = str(_rule_list.index(rule)) + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': _rule_list} + return fw_inst + + def _fake_firewall_with_admin_down(self, rule_list): + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': False, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': rule_list} + return fw_inst + + def _fake_apply_list(self, router_count=1): + apply_list = [] + while router_count > 0: + router_inst = {} + router_info_inst = mock.Mock() + 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) + rule_list = self._fake_rules_v4() + firewall = self._fake_firewall(rule_list) + edges = ['edge-1'] * router_count + with mock.patch.object(self.firewall._nsxv, + "update_firewall") as update_fw,\ + mock.patch.object(self.firewall, + "_get_routers_edges", return_value=edges): + func('nsx', apply_list, firewall) + self.assertEqual(router_count, update_fw.call_count) + bakend_rules = update_fw.call_args[0][1]['firewall_rule_list'] + self.assertEqual(len(rule_list), len(bakend_rules)) + + def test_create_firewall_no_rules(self): + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_no_rule() + with mock.patch.object(self.firewall._nsxv, + "update_firewall") as update_fw: + self.firewall.create_firewall('nsx', apply_list, firewall) + self.assertEqual(1, update_fw.call_count) + bakend_rules = update_fw.call_args[0][1]['firewall_rule_list'] + self.assertEqual(0, len(bakend_rules)) + + def test_create_firewall_with_rules(self): + self._setup_firewall_with_rules(self.firewall.create_firewall) + + def test_create_firewall_with_rules_two_routers(self): + self._setup_firewall_with_rules(self.firewall.create_firewall, + router_count=2) + + def test_update_firewall_with_rules(self): + self._setup_firewall_with_rules(self.firewall.update_firewall) + + def test_delete_firewall(self): + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_no_rule() + with mock.patch.object(self.firewall._nsxv, + "update_firewall") as update_fw: + self.firewall.delete_firewall('nsx', apply_list, firewall) + self.assertEqual(1, update_fw.call_count) + bakend_rules = update_fw.call_args[0][1]['firewall_rule_list'] + self.assertEqual(0, len(bakend_rules)) + + def test_create_firewall_with_admin_down(self): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4() + firewall = self._fake_firewall_with_admin_down(rule_list) + with mock.patch.object(self.firewall._nsxv, + "update_firewall") as update_fw: + self.firewall.create_firewall('nsx', apply_list, firewall) + self.assertEqual(1, update_fw.call_count) + bakend_rules = update_fw.call_args[0][1]['firewall_rule_list'] + self.assertEqual(0, len(bakend_rules)) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 90238c0283..3a23936290 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -3097,8 +3097,7 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase, # Also test the md_srvip conversion: drv = edge_firewall_driver.EdgeFirewallDriver() - rule = drv._convert_firewall_rule( - context.get_admin_context(), md_srvip) + rule = drv._convert_firewall_rule(md_srvip) exp_service = {'service': [{'port': [80, 443, 8775], 'protocol': 'tcp'}]} exp_rule = {'action': 'accept', @@ -3156,8 +3155,7 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase, # Also test the rule conversion # Ports should be sorted & unique, and ignore non numeric values drv = edge_firewall_driver.EdgeFirewallDriver() - rule = drv._convert_firewall_rule( - context.get_admin_context(), md_srvip) + rule = drv._convert_firewall_rule(md_srvip) exp_service = {'service': [{'port': [55, 66, 80, 443, 8775], 'protocol': 'tcp'}]} exp_rule = {'action': 'accept',