vmware-nsx/vmware_nsx/plugins/nsx_v/vshield/edge_firewall_driver.py
Anna Khmelnitsky e635d0a0f8 Fix firewall rule to allow ping on DHCP edge
Because of edge bug, configuring the rule with raw proto=icmp and
type=8 is creating incorrect firewall rule (edge is misinterpreting
service "icmp:8:any"). This bug should be fixed in 6.3.1.
Meanwhile, we'll configure rule based on application. Since
application ids can change, ids are queried by name from backend
application list.

When edge fix is available, need to switch back to raw icmp format
since its faster.

Change-Id: I7ae50f6fc9754bd2de4c2744494a5a7335c6f364
2016-12-19 09:33:44 -08:00

440 lines
18 KiB
Python

# Copyright 2013 VMware, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from oslo_utils import excutils
from vmware_nsx._i18n import _, _LE
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.db import nsxv_db
from vmware_nsx.plugins.nsx_v.vshield.common import (
exceptions as vcns_exc)
LOG = logging.getLogger(__name__)
VSE_FWAAS_ALLOW = "accept"
VSE_FWAAS_DENY = "deny"
FWAAS_ALLOW = "allow"
FWAAS_DENY = "deny"
class EdgeFirewallDriver(object):
"""Implementation of driver APIs for
Edge Firewall feature configuration
"""
def __init__(self):
super(EdgeFirewallDriver, self).__init__()
self._icmp_echo_application_ids = None
def _convert_firewall_action(self, action):
if action == FWAAS_ALLOW:
return VSE_FWAAS_ALLOW
elif action == FWAAS_DENY:
return VSE_FWAAS_DENY
else:
msg = _("Invalid action value %s in a firewall rule") % action
raise vcns_exc.VcnsBadRequest(resource='firewall_rule', msg=msg)
def _restore_firewall_action(self, action):
if action == VSE_FWAAS_ALLOW:
return FWAAS_ALLOW
elif action == VSE_FWAAS_DENY:
return FWAAS_DENY
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):
if not min_port:
return None
if min_port == max_port:
return str(min_port)
else:
return '%d:%d' % (min_port, max_port)
def _get_ports_list_from_string(self, port_str):
"""Receives a string representation of the service ports,
and return a list of integers
Supported formats:
Empty string - no ports
"number" - a single port
"num1:num2" - a range
"num1,num2,num3" - a list
"""
if not port_str:
return []
if ':' in port_str:
min_port, sep, max_port = port_str.partition(":")
return list(range(int(min_port.strip()),
int(max_port.strip()) + 1))
if ',' in port_str:
# remove duplications (using set) and empty/non numeric entries
ports_set = set()
for orig_port in port_str.split(','):
port = orig_port.strip()
if port and port.isdigit():
ports_set.add(int(port))
return sorted(list(ports_set))
else:
return [int(port_str.strip())]
def _convert_firewall_rule(self, context, rule, index=None):
vcns_rule = {
"action": self._convert_firewall_action(rule['action']),
"enabled": rule.get('enabled', True)}
if rule.get('name'):
vcns_rule['name'] = rule['name']
if rule.get('description'):
vcns_rule['description'] = rule['description']
if rule.get('source_ip_address'):
vcns_rule['source'] = {
"ipAddress": rule['source_ip_address']
}
if rule.get('source_vnic_groups'):
vcns_rule['source'] = {
"vnicGroupId": rule['source_vnic_groups']
}
if rule.get('destination_ip_address'):
vcns_rule['destination'] = {
"ipAddress": rule['destination_ip_address']
}
if rule.get('destination_vnic_groups'):
vcns_rule['destination'] = {
"vnicGroupId": rule['destination_vnic_groups']
}
if rule.get('application'):
vcns_rule['application'] = rule['application']
service = {}
if rule.get('source_port'):
service['sourcePort'] = self._get_ports_list_from_string(
rule['source_port'])
if rule.get('destination_port'):
service['port'] = self._get_ports_list_from_string(
rule['destination_port'])
if rule.get('protocol'):
service['protocol'] = rule['protocol']
if rule['protocol'] == 'icmp':
if rule.get('icmp_type'):
service['icmpType'] = rule['icmp_type']
else:
service['icmpType'] = 'any'
if rule.get('ruleId'):
vcns_rule['ruleId'] = rule.get('ruleId')
if service:
vcns_rule['application'] = {
'service': [service]
}
if index:
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']}}
if rule.get('name'):
fw_rule['firewall_rule']['name'] = rule['name']
if rule.get('description'):
fw_rule['firewall_rule']['description'] = rule['description']
return fw_rule
def _convert_firewall(self, context, 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})
return {
'featureType': "firewall_4.0",
'globalConfig': {'tcpTimeoutEstablished': 7200},
'firewallRules': {
'firewallRules': vcns_rules}}
def _restore_firewall(self, context, edge_id, response):
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:
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)
return res
def _get_firewall(self, edge_id):
try:
return self.vcns.get_firewall(edge_id)[1]
except vcns_exc.VcnsApiException as e:
LOG.exception(_LE("Failed to get firewall with edge "
"id: %s"), edge_id)
raise e
def _get_firewall_rule_next(self, context, edge_id, rule_vseid):
# Return the firewall rule below 'rule_vseid'
fw_cfg = self._get_firewall(edge_id)
for i in range(len(fw_cfg['firewallRules']['firewallRules'])):
rule_cur = fw_cfg['firewallRules']['firewallRules'][i]
if str(rule_cur['ruleId']) == rule_vseid:
if (i + 1) == len(fw_cfg['firewallRules']['firewallRules']):
return None
else:
return fw_cfg['firewallRules']['firewallRules'][i + 1]
def get_firewall_rule(self, context, id, edge_id):
rule_map = nsxv_db.get_nsxv_edge_firewallrule_binding(
context.session, id, edge_id)
if rule_map is None:
msg = _("No rule id:%s found in the edge_firewall_binding") % id
LOG.error(msg)
raise vcns_exc.VcnsNotFound(
resource='vcns_firewall_rule_bindings', msg=msg)
vcns_rule_id = rule_map.rule_vseid
try:
response = self.vcns.get_firewall_rule(
edge_id, vcns_rule_id)[1]
except vcns_exc.VcnsApiException as e:
LOG.exception(_LE("Failed to get firewall rule: %(rule_id)s "
"with edge_id: %(edge_id)s"), {
'rule_id': id,
'edge_id': edge_id})
raise e
return self._restore_firewall_rule(context, edge_id, response)
def get_firewall(self, context, edge_id):
response = self._get_firewall(edge_id)
return self._restore_firewall(context, edge_id, response)
def delete_firewall(self, context, edge_id):
try:
self.vcns.delete_firewall(edge_id)
except vcns_exc.VcnsApiException as e:
LOG.exception(_LE("Failed to delete firewall "
"with edge_id:%s"), edge_id)
raise e
nsxv_db.cleanup_nsxv_edge_firewallrule_binding(
context.session, edge_id)
def update_firewall_rule(self, context, id, edge_id, firewall_rule):
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)
try:
self.vcns.update_firewall_rule(edge_id, vcns_rule_id, fwr_req)
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to update firewall rule: "
"%(rule_id)s "
"with edge_id: %(edge_id)s"),
{'rule_id': id,
'edge_id': edge_id})
def delete_firewall_rule(self, context, id, edge_id):
rule_map = nsxv_db.get_nsxv_edge_firewallrule_binding(
context.session, id, edge_id)
vcns_rule_id = rule_map.rule_vseid
try:
self.vcns.delete_firewall_rule(edge_id, vcns_rule_id)
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to delete firewall rule: "
"%(rule_id)s "
"with edge_id: %(edge_id)s"),
{'rule_id': id,
'edge_id': edge_id})
nsxv_db.delete_nsxv_edge_firewallrule_binding(
context.session, id)
def _add_rule_above(self, context, ref_rule_id, edge_id, firewall_rule):
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)
try:
header = self.vcns.add_firewall_rule_above(
edge_id, ref_vcns_rule_id, fwr_req)[0]
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to add firewall rule above: "
"%(rule_id)s with edge_id: %(edge_id)s"),
{'rule_id': ref_vcns_rule_id,
'edge_id': edge_id})
objuri = header['location']
fwr_vseid = objuri[objuri.rfind("/") + 1:]
map_info = {
'rule_id': firewall_rule['id'],
'rule_vseid': fwr_vseid,
'edge_id': edge_id}
nsxv_db.add_nsxv_edge_firewallrule_binding(
context.session, map_info)
def _add_rule_below(self, context, ref_rule_id, edge_id, firewall_rule):
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_vse_next = self._get_firewall_rule_next(
context, edge_id, ref_vcns_rule_id)
fwr_req = self._convert_firewall_rule(context, firewall_rule)
if fwr_vse_next:
ref_vcns_rule_id = fwr_vse_next['ruleId']
try:
header = self.vcns.add_firewall_rule_above(
edge_id, int(ref_vcns_rule_id), fwr_req)[0]
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to add firewall rule above: "
"%(rule_id)s with edge_id: %(edge_id)s"),
{'rule_id': ref_vcns_rule_id,
'edge_id': edge_id})
else:
# append the rule at the bottom
try:
header = self.vcns.add_firewall_rule(
edge_id, fwr_req)[0]
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to append a firewall rule"
"with edge_id: %s"), edge_id)
objuri = header['location']
fwr_vseid = objuri[objuri.rfind("/") + 1:]
map_info = {
'rule_id': firewall_rule['id'],
'rule_vseid': fwr_vseid,
'edge_id': edge_id
}
nsxv_db.add_nsxv_edge_firewallrule_binding(
context.session, map_info)
def insert_rule(self, context, rule_info, edge_id, fwr):
if rule_info.get('insert_before'):
self._add_rule_above(
context, rule_info['insert_before'], edge_id, fwr)
elif rule_info.get('insert_after'):
self._add_rule_below(
context, rule_info['insert_after'], edge_id, fwr)
else:
msg = _("Can't execute insert rule operation "
"without reference rule_id")
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,
allow_external=allow_external)
try:
self.vcns.update_firewall(edge_id, config)
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to update firewall "
"with edge_id: %s"), edge_id)
vcns_fw_config = self._get_firewall(edge_id)
nsxv_db.cleanup_nsxv_edge_firewallrule_binding(
context.session, edge_id)
self._create_rule_id_mapping(
context, edge_id, firewall, vcns_fw_config)
def _create_rule_id_mapping(
self, context, edge_id, firewall, vcns_fw):
for rule in vcns_fw['firewallRules']['firewallRules']:
if rule.get('ruleTag'):
index = rule['ruleTag'] - 1
# TODO(linb):a simple filter of the retrieved rules which may
# be created by other operations unintentionally
if index < len(firewall['firewall_rule_list']):
rule_vseid = rule['ruleId']
rule_id = firewall['firewall_rule_list'][index].get('id')
if rule_id:
map_info = {
'rule_id': rule_id,
'rule_vseid': rule_vseid,
'edge_id': edge_id
}
nsxv_db.add_nsxv_edge_firewallrule_binding(
context.session, map_info)
def get_icmp_echo_application_ids(self):
# check cached list first
# (if backend version changes, neutron should be restarted)
if self._icmp_echo_application_ids:
return self._icmp_echo_application_ids
self._icmp_echo_application_ids = self.get_application_ids(
['ICMP Echo', 'IPv6-ICMP Echo'])
if not self._icmp_echo_application_ids:
raise nsx_exc.NsxResourceNotFound(
res_name='ICMP Echo', res_id='')
return self._icmp_echo_application_ids
def get_application_ids(self, application_names):
results = self.vcns.list_applications()
application_ids = []
for result in results:
for name in application_names:
if result['name'] == name:
application_ids.append(result['objectId'])
return application_ids