229 lines
9.2 KiB
Python
229 lines
9.2 KiB
Python
# 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 netaddr
|
|
|
|
from neutron_lib.plugins import directory
|
|
from oslo_log import log as logging
|
|
|
|
from vmware_nsx.extensions import projectpluginmap
|
|
from vmware_nsx.services.fwaas.common import v3_utils
|
|
from vmware_nsx.services.fwaas.nsx_v3 import edge_fwaas_driver_base \
|
|
as base_driver
|
|
from vmware_nsxlib.v3 import nsx_constants as consts
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
FWAAS_DRIVER_NAME = 'Fwaas V2 NSX-V3 driver'
|
|
RULE_NAME_PREFIX = 'Fwaas-'
|
|
DEFAULT_RULE_NAME = 'Default LR Layer3 Rule'
|
|
|
|
|
|
class EdgeFwaasV3DriverV2(base_driver.CommonEdgeFwaasV3Driver):
|
|
"""NSX-V3 driver for Firewall As A Service - V2."""
|
|
|
|
def __init__(self):
|
|
super(EdgeFwaasV3DriverV2, self).__init__(FWAAS_DRIVER_NAME)
|
|
|
|
@property
|
|
def core_plugin(self):
|
|
"""Get the NSX-V3 core plugin"""
|
|
if not self._core_plugin:
|
|
self._core_plugin = directory.get_plugin()
|
|
if self._core_plugin.is_tvd_plugin():
|
|
self._core_plugin = self._core_plugin.get_plugin_by_type(
|
|
projectpluginmap.NsxPlugins.NSX_T)
|
|
if not self._core_plugin:
|
|
# The nsx-t plugin was not initialized
|
|
return
|
|
# make sure plugin init was completed
|
|
if not self._core_plugin.init_is_complete:
|
|
self._core_plugin.init_complete(None, None, {})
|
|
return self._core_plugin
|
|
|
|
@property
|
|
def nsxlib(self):
|
|
return self.core_plugin.nsxlib
|
|
|
|
@property
|
|
def nsx_firewall(self):
|
|
return self.nsxlib.firewall_section
|
|
|
|
@property
|
|
def nsx_router(self):
|
|
return self.nsxlib.logical_router
|
|
|
|
def _translate_cidr(self, cidr, fwaas_rule_id):
|
|
# Validate that this is a legal & supported ipv4 / ipv6 cidr
|
|
error_msg = (_("Unsupported FWAAS cidr %(cidr)s for rule %(id)s") % {
|
|
'cidr': cidr, 'id': fwaas_rule_id})
|
|
net = netaddr.IPNetwork(cidr)
|
|
if net.version == 4:
|
|
if cidr.startswith('0.0.0.0'):
|
|
# Treat as ANY and just log warning
|
|
LOG.warning(error_msg)
|
|
return
|
|
if net.prefixlen == 0:
|
|
LOG.error(error_msg)
|
|
raise self.driver_exception(driver=self.driver_name)
|
|
elif net.version == 6:
|
|
if str(net.ip) == "::" or net.prefixlen == 0:
|
|
LOG.error(error_msg)
|
|
raise self.driver_exception(driver=self.driver_name)
|
|
else:
|
|
LOG.error(error_msg)
|
|
raise self.driver_exception(driver=self.driver_name)
|
|
|
|
return self.nsx_firewall.get_ip_cidr_reference(
|
|
cidr,
|
|
consts.IPV6 if net.version == 6 else consts.IPV4)
|
|
|
|
def translate_addresses_to_target(self, cidrs, plugin_type,
|
|
fwaas_rule_id=None):
|
|
translated_cidrs = []
|
|
for ip in cidrs:
|
|
res = self._translate_cidr(ip, fwaas_rule_id)
|
|
if res:
|
|
translated_cidrs.append(res)
|
|
return translated_cidrs
|
|
|
|
def _translate_services(self, fwaas_rule):
|
|
l4_protocol = v3_utils.translate_fw_rule_protocol(
|
|
fwaas_rule['protocol'])
|
|
if l4_protocol in [consts.TCP, consts.UDP]:
|
|
source_ports = []
|
|
destination_ports = []
|
|
if fwaas_rule.get('source_port'):
|
|
source_ports = v3_utils.translate_fw_rule_ports(
|
|
fwaas_rule['source_port'])
|
|
if fwaas_rule.get('destination_port'):
|
|
destination_ports = v3_utils.translate_fw_rule_ports(
|
|
fwaas_rule['destination_port'])
|
|
|
|
return [self.nsx_firewall.get_nsservice(
|
|
consts.L4_PORT_SET_NSSERVICE,
|
|
l4_protocol=l4_protocol,
|
|
source_ports=source_ports,
|
|
destination_ports=destination_ports)]
|
|
if l4_protocol == consts.ICMPV4:
|
|
# Add both icmp v4 & v6 services
|
|
return [
|
|
self.nsx_firewall.get_nsservice(
|
|
consts.ICMP_TYPE_NSSERVICE,
|
|
protocol=consts.ICMPV4),
|
|
self.nsx_firewall.get_nsservice(
|
|
consts.ICMP_TYPE_NSSERVICE,
|
|
protocol=consts.ICMPV6),
|
|
]
|
|
|
|
def _translate_rules(self, fwaas_rules, replace_src=None,
|
|
replace_dest=None, logged=False):
|
|
translated_rules = []
|
|
for rule in fwaas_rules:
|
|
nsx_rule = {}
|
|
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 255)
|
|
if rule.get('name'):
|
|
name = RULE_NAME_PREFIX + rule['name']
|
|
else:
|
|
name = RULE_NAME_PREFIX + rule['id']
|
|
nsx_rule['display_name'] = name[:255]
|
|
if rule.get('description'):
|
|
nsx_rule['notes'] = rule['description']
|
|
nsx_rule['action'] = v3_utils.translate_fw_rule_action(
|
|
rule['action'], rule['id'])
|
|
if not nsx_rule['action']:
|
|
raise self.driver_exception(driver=self.driver_name)
|
|
|
|
if (rule.get('destination_ip_address') and
|
|
not rule['destination_ip_address'].startswith('0.0.0.0')):
|
|
nsx_rule['destinations'] = self.translate_addresses_to_target(
|
|
[rule['destination_ip_address']], rule['id'])
|
|
elif replace_dest:
|
|
# set this value as the destination logical switch
|
|
# (only if no dest IP)
|
|
nsx_rule['destinations'] = [{'target_type': 'LogicalSwitch',
|
|
'target_id': replace_dest}]
|
|
if (rule.get('source_ip_address') and
|
|
not rule['source_ip_address'].startswith('0.0.0.0')):
|
|
nsx_rule['sources'] = self.translate_addresses_to_target(
|
|
[rule['source_ip_address']], rule['id'])
|
|
elif replace_src:
|
|
# set this value as the source logical switch,
|
|
# (only if no source IP)
|
|
nsx_rule['sources'] = [{'target_type': 'LogicalSwitch',
|
|
'target_id': replace_src}]
|
|
if rule.get('protocol'):
|
|
nsx_rule['services'] = self._translate_services(rule)
|
|
if logged:
|
|
nsx_rule['logged'] = logged
|
|
# Set rule direction
|
|
if replace_src:
|
|
nsx_rule['direction'] = 'OUT'
|
|
elif replace_dest:
|
|
nsx_rule['direction'] = 'IN'
|
|
translated_rules.append(nsx_rule)
|
|
|
|
return translated_rules
|
|
|
|
def get_default_backend_rule(self, section_id, allow_all=True):
|
|
# Add default allow all rule
|
|
old_default_rule = self.nsx_firewall.get_default_rule(
|
|
section_id)
|
|
return {
|
|
'display_name': DEFAULT_RULE_NAME,
|
|
'action': (consts.FW_ACTION_ALLOW if allow_all
|
|
else consts.FW_ACTION_DROP),
|
|
'is_default': True,
|
|
'id': old_default_rule['id'] if old_default_rule else 0}
|
|
|
|
def get_port_translated_rules(self, nsx_ls_id, firewall_group,
|
|
plugin_rules):
|
|
"""Return the list of translated rules per port"""
|
|
port_rules = []
|
|
# TODO(asarfaty): get this value from the firewall group extensions
|
|
logged = False
|
|
# Add the firewall group ingress/egress rules only if the fw is up
|
|
if firewall_group['admin_state_up']:
|
|
port_rules.extend(self._translate_rules(
|
|
firewall_group['ingress_rule_list'],
|
|
replace_dest=nsx_ls_id,
|
|
logged=logged))
|
|
port_rules.extend(self._translate_rules(
|
|
firewall_group['egress_rule_list'],
|
|
replace_src=nsx_ls_id,
|
|
logged=logged))
|
|
|
|
# Add the per-port plugin rules
|
|
if plugin_rules and isinstance(plugin_rules, list):
|
|
port_rules.extend(plugin_rules)
|
|
|
|
# Add ingress/egress block rules for this port
|
|
port_rules.extend([
|
|
{'display_name': "Block port ingress",
|
|
'action': consts.FW_ACTION_DROP,
|
|
'destinations': [{'target_type': 'LogicalSwitch',
|
|
'target_id': nsx_ls_id}],
|
|
'direction': 'IN'},
|
|
{'display_name': "Block port egress",
|
|
'action': consts.FW_ACTION_DROP,
|
|
'sources': [{'target_type': 'LogicalSwitch',
|
|
'target_id': nsx_ls_id}],
|
|
'direction': 'OUT'}])
|
|
|
|
return port_rules
|