8c61877187
Commit 87a79256c494c36f2d9597313f430b24c0110161 added neutron_lib for shared exceptions. This patch moves us to make use of the aforementioned library. Change-Id: I9fe014c5da85faca87bf88a80c4ee19f7f123123
218 lines
8.7 KiB
Python
218 lines
8.7 KiB
Python
# Copyright 2014 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 constants
|
|
from neutron_lib import exceptions
|
|
from oslo_log import log
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import excutils
|
|
|
|
from vmware_nsx._i18n import _LW
|
|
from vmware_nsx.common import utils
|
|
from vmware_nsx.nsxlib import mh as nsxlib
|
|
|
|
HTTP_GET = "GET"
|
|
HTTP_POST = "POST"
|
|
HTTP_DELETE = "DELETE"
|
|
HTTP_PUT = "PUT"
|
|
|
|
SECPROF_RESOURCE = "security-profile"
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def mk_body(**kwargs):
|
|
"""Convenience function creates and dumps dictionary to string.
|
|
|
|
:param kwargs: the key/value pirs to be dumped into a json string.
|
|
:returns: a json string.
|
|
"""
|
|
return jsonutils.dumps(kwargs, ensure_ascii=False)
|
|
|
|
|
|
def query_security_profiles(cluster, fields=None, filters=None):
|
|
return nsxlib.get_all_query_pages(
|
|
nsxlib._build_uri_path(SECPROF_RESOURCE,
|
|
fields=fields,
|
|
filters=filters),
|
|
cluster)
|
|
|
|
|
|
def create_security_profile(cluster, tenant_id, neutron_id, security_profile):
|
|
"""Create a security profile on the NSX backend.
|
|
|
|
:param cluster: a NSX cluster object reference
|
|
:param tenant_id: identifier of the Neutron tenant
|
|
:param neutron_id: neutron security group identifier
|
|
:param security_profile: dictionary with data for
|
|
configuring the NSX security profile.
|
|
"""
|
|
path = "/ws.v1/security-profile"
|
|
# Allow all dhcp responses and all ingress traffic
|
|
hidden_rules = {'logical_port_egress_rules':
|
|
[{'ethertype': 'IPv4',
|
|
'protocol': constants.PROTO_NUM_UDP,
|
|
'port_range_min': constants.DHCP_RESPONSE_PORT,
|
|
'port_range_max': constants.DHCP_RESPONSE_PORT,
|
|
'ip_prefix': '0.0.0.0/0'}],
|
|
'logical_port_ingress_rules':
|
|
[{'ethertype': 'IPv4'},
|
|
{'ethertype': 'IPv6'}]}
|
|
display_name = utils.check_and_truncate(security_profile.get('name'))
|
|
# NOTE(salv-orlando): neutron-id tags are prepended with 'q' for
|
|
# historical reasons
|
|
body = mk_body(
|
|
tags=utils.get_tags(os_tid=tenant_id, q_sec_group_id=neutron_id),
|
|
display_name=display_name,
|
|
logical_port_ingress_rules=(
|
|
hidden_rules['logical_port_ingress_rules']),
|
|
logical_port_egress_rules=hidden_rules['logical_port_egress_rules']
|
|
)
|
|
rsp = nsxlib.do_request(HTTP_POST, path, body, cluster=cluster)
|
|
if security_profile.get('name') == 'default':
|
|
# If security group is default allow ip traffic between
|
|
# members of the same security profile is allowed and ingress traffic
|
|
# from the switch
|
|
rules = {'logical_port_egress_rules': [{'ethertype': 'IPv4',
|
|
'profile_uuid': rsp['uuid']},
|
|
{'ethertype': 'IPv6',
|
|
'profile_uuid': rsp['uuid']}],
|
|
'logical_port_ingress_rules': [{'ethertype': 'IPv4'},
|
|
{'ethertype': 'IPv6'}]}
|
|
|
|
update_security_group_rules(cluster, rsp['uuid'], rules)
|
|
LOG.debug("Created Security Profile: %s", rsp)
|
|
return rsp
|
|
|
|
|
|
def update_security_group_rules(cluster, spid, rules):
|
|
path = "/ws.v1/security-profile/%s" % spid
|
|
|
|
# Allow all dhcp responses in
|
|
rules['logical_port_egress_rules'].append(
|
|
{'ethertype': 'IPv4', 'protocol': constants.PROTO_NUM_UDP,
|
|
'port_range_min': constants.DHCP_RESPONSE_PORT,
|
|
'port_range_max': constants.DHCP_RESPONSE_PORT,
|
|
'ip_prefix': '0.0.0.0/0'})
|
|
# If there are no ingress rules add bunk rule to drop all ingress traffic
|
|
if not rules['logical_port_ingress_rules']:
|
|
rules['logical_port_ingress_rules'].append(
|
|
{'ethertype': 'IPv4', 'ip_prefix': '127.0.0.1/32'})
|
|
try:
|
|
body = mk_body(
|
|
logical_port_ingress_rules=summarize_security_group_rules(rules[
|
|
'logical_port_ingress_rules']),
|
|
logical_port_egress_rules=summarize_security_group_rules(rules[
|
|
'logical_port_egress_rules']))
|
|
rsp = nsxlib.do_request(HTTP_PUT, path, body, cluster=cluster)
|
|
except exceptions.NotFound as e:
|
|
LOG.error(nsxlib.format_exception("Unknown", e, locals()))
|
|
#FIXME(salvatore-orlando): This should not raise NeutronException
|
|
raise exceptions.NeutronException()
|
|
LOG.debug("Updated Security Profile: %s", rsp)
|
|
return rsp
|
|
|
|
|
|
def update_security_profile(cluster, spid, name):
|
|
return nsxlib.do_request(
|
|
HTTP_PUT,
|
|
nsxlib._build_uri_path(SECPROF_RESOURCE, resource_id=spid),
|
|
jsonutils.dumps({"display_name": utils.check_and_truncate(name)}),
|
|
cluster=cluster)
|
|
|
|
|
|
def delete_security_profile(cluster, spid):
|
|
path = "/ws.v1/security-profile/%s" % spid
|
|
|
|
try:
|
|
nsxlib.do_request(HTTP_DELETE, path, cluster=cluster)
|
|
except exceptions.NotFound:
|
|
with excutils.save_and_reraise_exception():
|
|
# This is not necessarily an error condition
|
|
LOG.warning(_LW("Unable to find security profile %s on NSX "
|
|
"backend"), spid)
|
|
|
|
|
|
def summarize_security_group_rules(logical_port_rules):
|
|
"""
|
|
Summarizes security group rules and remove duplicates. Given a set of
|
|
arbitrary security group rules, determining the optimum (minimum) rule set
|
|
is a complex (NP-hard) problem. This method does not attempt to obtain the
|
|
optimum rules. Instead, it summarizes a set of common rule patterns.
|
|
"""
|
|
|
|
# Remove port_range_min & port_range_max if it covers the entire port
|
|
# range. Also, remove quad-zero default IPv4 and default IPv6 routes
|
|
for rule in logical_port_rules:
|
|
if ('port_range_min' in rule and 'port_range_max' in rule and
|
|
rule['port_range_min'] <= 1 and
|
|
rule['port_range_max'] == 65535):
|
|
del rule['port_range_min']
|
|
del rule['port_range_max']
|
|
|
|
if ('ip_prefix' in rule and
|
|
rule['ip_prefix'] in ['0.0.0.0/0', '::/0']):
|
|
del rule['ip_prefix']
|
|
|
|
# Remove duplicate rules. Loop through each rule rule_i and exclude a
|
|
# rule if it is part of another rule.
|
|
logical_port_rules_summarized = []
|
|
for i in range(len(logical_port_rules)):
|
|
for j in range(len(logical_port_rules)):
|
|
if i != j:
|
|
if is_sg_rules_identical(logical_port_rules[i],
|
|
logical_port_rules[j]):
|
|
pass
|
|
elif is_sg_rule_subset(logical_port_rules[i],
|
|
logical_port_rules[j]):
|
|
break
|
|
else:
|
|
logical_port_rules_summarized.append(logical_port_rules[i])
|
|
|
|
return logical_port_rules_summarized
|
|
|
|
|
|
def is_sg_rules_identical(sgr1, sgr2):
|
|
"""
|
|
determines if security group rule sgr1 and sgr2 are identical
|
|
"""
|
|
return (sgr1['ethertype'] == sgr2['ethertype'] and
|
|
sgr1.get('protocol') == sgr2.get('protocol') and
|
|
sgr1.get('port_range_min') == sgr2.get('port_range_min') and
|
|
sgr1.get('port_range_max') == sgr2.get('port_range_max') and
|
|
sgr1.get('ip_prefix') == sgr2.get('ip_prefix') and
|
|
sgr1.get('profile_uuid') == sgr2.get('profile_uuid'))
|
|
|
|
|
|
def is_sg_rule_subset(sgr1, sgr2):
|
|
"""
|
|
determine if security group rule sgr1 is a strict subset of sgr2
|
|
"""
|
|
all_protocols = set(range(256))
|
|
sgr1_protocols = {sgr1['protocol']} if 'protocol' in sgr1 else \
|
|
all_protocols
|
|
sgr2_protocols = {sgr2['protocol']} if 'protocol' in sgr2 else \
|
|
all_protocols
|
|
|
|
return (sgr1['ethertype'] == sgr2['ethertype'] and
|
|
sgr1_protocols.issubset(sgr2_protocols) and
|
|
sgr1.get('port_range_min', 0) >= sgr2.get('port_range_min', 0) and
|
|
sgr1.get('port_range_max', 65535) <= sgr2.get('port_range_max',
|
|
65535) and
|
|
(sgr2.get('ip_prefix') is None or
|
|
sgr1.get('ip_prefix') == sgr2.get('prefix')) and
|
|
(sgr2.get('profile_uuid') is None or
|
|
sgr1.get('profile_uuid') == sgr2.get('profile_uuid')))
|