Support l3 stateless firewall based on OVN
This patch implements a driver based on OVN, it creates port_group for every l3 firewall_group and adds relating ports to port_group, it also transforms firewall_rules to stateless acls. Tests will been put in next patch. NOTE: it depends on ML2/OVN. Partially-Implements: blueprint support-l3-firewall-for-ovn-driver Related-Bug: #1971958 Change-Id: If153645b3da198ef1746e98af80ac6f0a0b41bf9
This commit is contained in:
parent
e7b472e6bf
commit
8de0c36cb9
@ -38,12 +38,21 @@ function install_fwaas() {
|
||||
fi
|
||||
}
|
||||
|
||||
function is_ovn_enabled {
|
||||
[[ $Q_AGENT == "ovn" ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
function configure_fwaas_v2() {
|
||||
# Add conf file
|
||||
cp $NEUTRON_FWAAS_DIR/etc/neutron_fwaas.conf.sample $NEUTRON_FWAAS_CONF
|
||||
neutron_server_config_add $NEUTRON_FWAAS_CONF
|
||||
inicomment $NEUTRON_FWAAS_CONF service_providers service_provider
|
||||
iniadd $NEUTRON_FWAAS_CONF service_providers service_provider $NEUTRON_FWAAS_SERVICE_PROVIDERV2
|
||||
if is_ovn_enabled; then
|
||||
iniadd $NEUTRON_FWAAS_CONF service_providers service_provider $NEUTRON_FWAAS_SERVICE_PROVIDERV2_OVN
|
||||
else
|
||||
iniadd $NEUTRON_FWAAS_CONF service_providers service_provider $NEUTRON_FWAAS_SERVICE_PROVIDERV2
|
||||
fi
|
||||
|
||||
neutron_fwaas_configure_driver fwaas_v2
|
||||
if is_service_enabled q-l3; then
|
||||
|
@ -8,5 +8,6 @@ NEUTRON_FWAAS_CONF_FILE=neutron_fwaas.conf
|
||||
NEUTRON_FWAAS_CONF=$NEUTRON_CONF_DIR/$NEUTRON_FWAAS_CONF_FILE
|
||||
|
||||
NEUTRON_FWAAS_SERVICE_PROVIDERV2=${NEUTRON_FWAAS_SERVICE_PROVIDERV2:-FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.agents.agents.FirewallAgentDriver:default}
|
||||
NEUTRON_FWAAS_SERVICE_PROVIDERV2_OVN=${NEUTRON_FWAAS_SERVICE_PROVIDERV2:-FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.ovn.firewall_l3_driver.OVNFwaasDriver:default}
|
||||
|
||||
enable_service q-fwaas-v2
|
||||
|
237
neutron_fwaas/services/firewall/service_drivers/ovn/acl.py
Normal file
237
neutron_fwaas/services/firewall/service_drivers/ovn/acl.py
Normal file
@ -0,0 +1,237 @@
|
||||
# Copyright 2022 EasyStack, 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.common.ovn import utils as ovn_utils
|
||||
from neutron_lib import constants as const
|
||||
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
constants as ovn_const
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
exceptions as ovn_fw_exc
|
||||
|
||||
|
||||
def acl_direction(direction, port_group=None):
|
||||
if direction == const.INGRESS_DIRECTION:
|
||||
portdir = 'inport'
|
||||
else:
|
||||
portdir = 'outport'
|
||||
return '%s == @%s' % (portdir, port_group)
|
||||
|
||||
|
||||
def acl_ethertype(rule):
|
||||
match = ''
|
||||
ip_version = None
|
||||
icmp = None
|
||||
if rule['ip_version'] == const.IP_VERSION_4:
|
||||
match = ' && ip4'
|
||||
ip_version = 'ip4'
|
||||
icmp = 'icmp4'
|
||||
elif rule['ip_version'] == const.IP_VERSION_6:
|
||||
match = ' && ip6'
|
||||
ip_version = 'ip6'
|
||||
icmp = 'icmp6'
|
||||
return match, ip_version, icmp
|
||||
|
||||
|
||||
def acl_ip(rule, ip_version):
|
||||
src_ip = rule.get('source_ip_address')
|
||||
dst_ip = rule.get('destination_ip_address')
|
||||
src = ' && %s.src == %s' % (ip_version, src_ip) if src_ip else ''
|
||||
dst = ' && %s.dst == %s' % (ip_version, dst_ip) if dst_ip else ''
|
||||
return src + dst
|
||||
|
||||
|
||||
def get_min_max_ports_from_range(port_range):
|
||||
if not port_range:
|
||||
return [None, None]
|
||||
min_port, sep, max_port = port_range.partition(":")
|
||||
if not max_port:
|
||||
max_port = min_port
|
||||
return [int(min_port), int(max_port)]
|
||||
|
||||
|
||||
def acl_protocol_ports(protocol, port_range, is_dst=True):
|
||||
match = ''
|
||||
min_port, max_port = get_min_max_ports_from_range(port_range)
|
||||
dir = 'dst' if is_dst else 'src'
|
||||
if protocol in ovn_const.TRANSPORT_PROTOCOLS:
|
||||
if min_port is not None and min_port == max_port:
|
||||
match += ' && %s.%s == %d' % (protocol, dir, min_port)
|
||||
else:
|
||||
if min_port is not None:
|
||||
match += ' && %s.%s >= %d' % (protocol, dir, min_port)
|
||||
if max_port is not None:
|
||||
match += ' && %s.%s <= %d' % (protocol, dir, max_port)
|
||||
return match
|
||||
|
||||
|
||||
def acl_protocol_and_ports(rule, icmp):
|
||||
match = ''
|
||||
protocol = rule.get('protocol')
|
||||
if protocol is None:
|
||||
return match
|
||||
src_port = rule.get('source_port')
|
||||
dst_port = rule.get('destination_port')
|
||||
if protocol in ovn_const.TRANSPORT_PROTOCOLS:
|
||||
match += ' && %s' % protocol
|
||||
match += acl_protocol_ports(protocol, src_port, is_dst=False)
|
||||
match += acl_protocol_ports(protocol, dst_port)
|
||||
elif protocol in ovn_const.ICMP_PROTOCOLS:
|
||||
protocol = icmp
|
||||
match += ' && %s' % protocol
|
||||
return match
|
||||
|
||||
|
||||
def acl_action_and_priority(rule, direction):
|
||||
action = rule['action']
|
||||
pos = rule.get('position', 0)
|
||||
if action == 'deny' and rule.get(ovn_const.DEFAULT_RULE, False):
|
||||
return (ovn_const.ACL_ACTION_DROP,
|
||||
ovn_const.ACL_PRIORITY_DEFAULT)
|
||||
if direction == const.INGRESS_DIRECTION:
|
||||
priority = ovn_const.ACL_PRIORITY_INGRESS
|
||||
else:
|
||||
priority = ovn_const.ACL_PRIORITY_EGRESS
|
||||
if action == 'allow':
|
||||
return (ovn_const.ACL_ACTION_ALLOW_STATELESS,
|
||||
priority - pos)
|
||||
elif action == 'deny':
|
||||
return (ovn_const.ACL_ACTION_DROP,
|
||||
priority - pos)
|
||||
elif action == 'reject':
|
||||
return (ovn_const.ACL_ACTION_REJECT,
|
||||
priority - pos)
|
||||
|
||||
|
||||
def acl_entry_for_port_group(port_group, rule, direction, match):
|
||||
dir_map = {const.INGRESS_DIRECTION: 'from-lport',
|
||||
const.EGRESS_DIRECTION: 'to-lport'}
|
||||
action, priority = acl_action_and_priority(rule, direction)
|
||||
|
||||
acl = {"port_group": port_group,
|
||||
"priority": priority,
|
||||
"action": action,
|
||||
"log": False,
|
||||
"name": [],
|
||||
"severity": [],
|
||||
"direction": dir_map[direction],
|
||||
"match": match,
|
||||
ovn_const.OVN_FWR_EXT_ID_KEY: rule['id']}
|
||||
return acl
|
||||
|
||||
|
||||
def get_rule_acl_for_port_group(port_group, rule, direction):
|
||||
match = acl_direction(direction, port_group=port_group)
|
||||
ip_match, ip_version, icmp = acl_ethertype(rule)
|
||||
match += ip_match
|
||||
match += acl_ip(rule, ip_version)
|
||||
match += acl_protocol_and_ports(rule, icmp)
|
||||
return acl_entry_for_port_group(port_group, rule, direction, match)
|
||||
|
||||
|
||||
def update_ports_for_pg(nb_idl, txn, pg_name, ports_add=None,
|
||||
ports_delete=None):
|
||||
if ports_add is None:
|
||||
ports_add = []
|
||||
if ports_delete is None:
|
||||
ports_delete = []
|
||||
# Add ports to port_group
|
||||
for port_id in ports_add:
|
||||
txn.add(nb_idl.pg_add_ports(
|
||||
pg_name, port_id))
|
||||
for port_id in ports_delete:
|
||||
txn.add(nb_idl.pg_del_ports(
|
||||
pg_name, port_id, if_exists=True))
|
||||
|
||||
|
||||
def get_default_acls_for_pg(nb_idl, pg_name):
|
||||
nb_acls = nb_idl.pg_acl_list(pg_name).execute(check_error=True)
|
||||
default_acl_list = []
|
||||
for nb_acl in nb_acls:
|
||||
# Get acl whose external_ids has firewall_rule_id, then
|
||||
# append it to list if its value equal to default_rule_id
|
||||
ext_ids = getattr(nb_acl, 'external_ids', {})
|
||||
if (ext_ids.get(ovn_const.OVN_FWR_EXT_ID_KEY) ==
|
||||
ovn_const.DEFAULT_RULE_ID):
|
||||
default_acl_list.append(nb_acl.uuid)
|
||||
return default_acl_list
|
||||
|
||||
|
||||
def process_rule_for_pg(nb_idl, txn, pg_name, rule, direction,
|
||||
op=ovn_const.OP_ADD):
|
||||
dir_map = {const.INGRESS_DIRECTION: 'from-lport',
|
||||
const.EGRESS_DIRECTION: 'to-lport'}
|
||||
supported_ops = [ovn_const.OP_ADD, ovn_const.OP_DEL,
|
||||
ovn_const.OP_MOD]
|
||||
if op not in supported_ops:
|
||||
raise ovn_fw_exc.OperatorNotSupported(
|
||||
operator=op, valid_operators=supported_ops)
|
||||
|
||||
acl = get_rule_acl_for_port_group(
|
||||
pg_name, rule, direction)
|
||||
|
||||
# Add acl
|
||||
if op == ovn_const.OP_ADD:
|
||||
txn.add(nb_idl.pg_acl_add(**acl, may_exist=True))
|
||||
# Modify/Delete acl
|
||||
else:
|
||||
nb_acls = nb_idl.pg_acl_list(pg_name).execute(check_error=True)
|
||||
for nb_acl in nb_acls:
|
||||
# Get acl whose external_ids has firewall_rule_id,
|
||||
# then change it if its value equal to rule's
|
||||
ext_ids = getattr(nb_acl, 'external_ids', {})
|
||||
if (ext_ids.get(ovn_const.OVN_FWR_EXT_ID_KEY) ==
|
||||
rule['id'] and dir_map[direction] == nb_acl.direction):
|
||||
if op == ovn_const.OP_MOD:
|
||||
txn.add(nb_idl.db_set(
|
||||
'ACL', nb_acl.uuid,
|
||||
('match', acl['match']),
|
||||
('action', acl['action'])))
|
||||
elif op == ovn_const.OP_DEL:
|
||||
txn.add(nb_idl.pg_acl_del(
|
||||
acl['port_group'],
|
||||
acl['direction'],
|
||||
nb_acl.priority,
|
||||
acl['match']))
|
||||
break
|
||||
|
||||
|
||||
def create_pg_for_fwg(nb_idl, fwg_id):
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
# Add port_group
|
||||
with nb_idl.transaction(check_error=True) as txn:
|
||||
ext_ids = {ovn_const.OVN_FWG_EXT_ID_KEY: fwg_id}
|
||||
txn.add(nb_idl.pg_add(name=pg_name, acls=[],
|
||||
external_ids=ext_ids))
|
||||
|
||||
|
||||
def add_default_acls_for_pg(nb_idl, txn, pg_name):
|
||||
# Traffic is default denied, ipv4 or ipv6 with two directions,
|
||||
# so number of default acls is 4
|
||||
default_rule_v4 = {'action': 'deny', 'ip_version': 4,
|
||||
'id': ovn_const.DEFAULT_RULE_ID,
|
||||
ovn_const.DEFAULT_RULE: True}
|
||||
default_rule_v6 = {'action': 'deny', 'ip_version': 6,
|
||||
'id': ovn_const.DEFAULT_RULE_ID,
|
||||
ovn_const.DEFAULT_RULE: True}
|
||||
for dir in [const.EGRESS_DIRECTION, const.INGRESS_DIRECTION]:
|
||||
process_rule_for_pg(nb_idl, txn, pg_name,
|
||||
default_rule_v4,
|
||||
dir,
|
||||
op=ovn_const.OP_ADD)
|
||||
process_rule_for_pg(nb_idl, txn, pg_name,
|
||||
default_rule_v6,
|
||||
dir,
|
||||
op=ovn_const.OP_ADD)
|
@ -0,0 +1,44 @@
|
||||
# Copyright 2022 EasyStack, 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 as const
|
||||
|
||||
OVN_FWG_EXT_ID_KEY = 'neutron:firewall_group_id'
|
||||
OVN_FWR_EXT_ID_KEY = 'neutron:firewall_rule_id'
|
||||
ACL_ACTION_DROP = 'drop'
|
||||
ACL_ACTION_REJECT = 'reject'
|
||||
ACL_ACTION_ALLOW_STATELESS = 'allow-stateless'
|
||||
ACL_ACTION_ALLOW = 'allow'
|
||||
ACL_PRIORITY_INGRESS = 2000
|
||||
ACL_PRIORITY_EGRESS = 2000
|
||||
ACL_PRIORITY_DEFAULT = 1001
|
||||
OP_ADD = 'add'
|
||||
OP_DEL = 'del'
|
||||
OP_MOD = 'mod'
|
||||
DEFAULT_RULE = 'is_default'
|
||||
DEFAULT_RULE_ID = 'default_rule'
|
||||
|
||||
# Drop acls of ipv4 or ipv6 with two directions, so number of
|
||||
# default acls is 4
|
||||
DEFAULT_ACL_NUM = 4
|
||||
|
||||
# Group of transport protocols supported
|
||||
TRANSPORT_PROTOCOLS = (const.PROTO_NAME_TCP,
|
||||
const.PROTO_NAME_UDP,
|
||||
const.PROTO_NAME_SCTP)
|
||||
|
||||
# Group of versions of the ICMP protocol supported
|
||||
ICMP_PROTOCOLS = (const.PROTO_NAME_ICMP,
|
||||
const.PROTO_NAME_IPV6_ICMP)
|
@ -0,0 +1,33 @@
|
||||
# Copyright 2022 EasyStack, 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._i18n import _
|
||||
from neutron_lib import exceptions as n_exc
|
||||
|
||||
|
||||
class MechanismDriverNotFound(n_exc.NotFound):
|
||||
message = _("None of the supported mechanism drivers found: "
|
||||
"%(mechanism_drivers)s. Check your configuration.")
|
||||
|
||||
|
||||
class ProtocolNotSupported(n_exc.NeutronException):
|
||||
message = _('The protocol "%(protocol)s" is not supported. Valid '
|
||||
'protocols are: %(valid_protocols)s; or protocol '
|
||||
'numbers ranging from 0 to 255.')
|
||||
|
||||
|
||||
class OperatorNotSupported(n_exc.NeutronException):
|
||||
message = _('The operator "%(operator)s" is not supported. Valid '
|
||||
'operators are: %(valid_operators)s.')
|
@ -0,0 +1,358 @@
|
||||
# Copyright 2022 EasyStack, 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.common.ovn import utils as ovn_utils
|
||||
from neutron_lib import constants as const
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron_fwaas.services.firewall.service_drivers import driver_api
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
acl as ovn_acl
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
constants as ovn_const
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
exceptions as ovn_fw_exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OVNFwaasDriver(driver_api.FirewallDriverDB):
|
||||
"""OVN l3 acl driver to implement
|
||||
|
||||
Depends on ml2/ovn, use ovn_client to put acl rules to the lsp which
|
||||
is a peer of the lrp.
|
||||
"""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(OVNFwaasDriver, self).__init__(service_plugin)
|
||||
self._mech = None
|
||||
|
||||
def is_supported_l2_port(self, port):
|
||||
return False
|
||||
|
||||
def is_supported_l3_port(self, port):
|
||||
return True
|
||||
|
||||
def start_rpc_listener(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def _nb_ovn(self):
|
||||
return self._mech_driver.nb_ovn
|
||||
|
||||
@property
|
||||
def _mech_driver(self):
|
||||
if self._mech is None:
|
||||
drivers = ('ovn', 'ovn-sync')
|
||||
for driver in drivers:
|
||||
try:
|
||||
self._mech = \
|
||||
self._core_plugin.mechanism_manager.mech_drivers[
|
||||
driver].obj
|
||||
break
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
raise ovn_fw_exc.MechanismDriverNotFound(
|
||||
mechanism_drivers=drivers)
|
||||
return self._mech
|
||||
|
||||
def _init_firewall_group(self, txn, fwg_id):
|
||||
"""Add port_group for firewall_group
|
||||
|
||||
After create port_group for fwg, add default drop acls to it
|
||||
"""
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
ovn_acl.create_pg_for_fwg(self._nb_ovn, fwg_id)
|
||||
ovn_acl.add_default_acls_for_pg(self._nb_ovn, txn, pg_name)
|
||||
LOG.info("Successfully created port_group for firewall_group: %s",
|
||||
fwg_id)
|
||||
|
||||
def _add_rules_for_firewall_group(self, context, txn, fwg_id,
|
||||
rule_id=None):
|
||||
"""Add all rules belong to firewall_group
|
||||
"""
|
||||
fwg_with_rules = \
|
||||
self.firewall_db.make_firewall_group_dict_with_rules(
|
||||
context, fwg_id)
|
||||
egress_rule_list = fwg_with_rules['egress_rule_list']
|
||||
ingress_rule_list = fwg_with_rules['ingress_rule_list']
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
rule_map = {const.INGRESS_DIRECTION: ingress_rule_list,
|
||||
const.EGRESS_DIRECTION: egress_rule_list}
|
||||
for dir, rule_list in rule_map.items():
|
||||
position = 0
|
||||
for rule in rule_list:
|
||||
rule['position'] = position
|
||||
position += 1
|
||||
if not rule['enabled']:
|
||||
continue
|
||||
if rule_id:
|
||||
# For specify rule id
|
||||
if rule_id == rule['id']:
|
||||
ovn_acl.process_rule_for_pg(self._nb_ovn, txn,
|
||||
pg_name, rule, dir,
|
||||
op=ovn_const.OP_ADD)
|
||||
LOG.info("Successfully enable rule %(rule)s to "
|
||||
"firewall_group %(fwg)s",
|
||||
{"rule": rule_id,
|
||||
"fwg": fwg_id})
|
||||
break
|
||||
else:
|
||||
ovn_acl.process_rule_for_pg(self._nb_ovn, txn, pg_name,
|
||||
rule, dir,
|
||||
op=ovn_const.OP_ADD)
|
||||
LOG.info("Successfully added rules for firewall_group %s",
|
||||
fwg_id)
|
||||
|
||||
def _clear_rules_for_firewall_group(self, context, txn, fwg_id):
|
||||
"""Clear acls belong to firewall_group
|
||||
|
||||
Delete all rule acls but remain the default acls
|
||||
"""
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
default_acls = ovn_acl.get_default_acls_for_pg(self._nb_ovn, pg_name)
|
||||
if len(default_acls) == ovn_const.DEFAULT_ACL_NUM:
|
||||
txn.add(self._nb_ovn.db_set(
|
||||
'Port_Group', pg_name,
|
||||
('acls', default_acls)))
|
||||
else:
|
||||
ovn_acl.add_default_acls_for_pg(self._nb_ovn, txn, pg_name)
|
||||
LOG.info("Successfully clear rules for firewall_group %s",
|
||||
fwg_id)
|
||||
|
||||
def _process_acls_by_policies_or_rule(self, context, policy_ids,
|
||||
rule_info=None,
|
||||
op=ovn_const.OP_ADD):
|
||||
"""Delete/Update/Add the acls by rule or policies
|
||||
"""
|
||||
ing_fwg_list = []
|
||||
eg_fwg_list = []
|
||||
if not policy_ids:
|
||||
return
|
||||
for policy_id in policy_ids:
|
||||
ing_fwg_ids, eg_fwg_ids = self.firewall_db.get_fwgs_with_policy(
|
||||
context, policy_id)
|
||||
ing_fwg_list += ing_fwg_ids
|
||||
eg_fwg_list += eg_fwg_ids
|
||||
|
||||
if not rule_info and op == ovn_const.OP_ADD:
|
||||
# Add acls
|
||||
rule_info = {}
|
||||
for fwg_id in list(set(ing_fwg_list + eg_fwg_list)):
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if self._nb_ovn.get_port_group(pg_name):
|
||||
self._clear_rules_for_firewall_group(context, txn,
|
||||
fwg_id)
|
||||
else:
|
||||
self._init_firewall_group(txn, fwg_id)
|
||||
self._add_rules_for_firewall_group(context, txn, fwg_id)
|
||||
elif rule_info and op == ovn_const.OP_ADD:
|
||||
# Process the rule when enabled
|
||||
for fwg_id in list(set(ing_fwg_list + eg_fwg_list)):
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if self._nb_ovn.get_port_group(pg_name):
|
||||
self._add_rules_for_firewall_group(context, txn,
|
||||
fwg_id,
|
||||
rule_info['id'])
|
||||
elif rule_info:
|
||||
# Delete/Update acls
|
||||
fwg_map = {const.INGRESS_DIRECTION: list(set(ing_fwg_list)),
|
||||
const.EGRESS_DIRECTION: list(set(eg_fwg_list))}
|
||||
for dir, fwg_list in fwg_map.items():
|
||||
for fwg_id in fwg_list:
|
||||
pg_name = ovn_utils.ovn_port_group_name(fwg_id)
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if not self._nb_ovn.get_port_group(pg_name):
|
||||
LOG.warning("Cannot find Port_Group with name: %s",
|
||||
pg_name)
|
||||
continue
|
||||
ovn_acl.process_rule_for_pg(self._nb_ovn, txn,
|
||||
pg_name, rule_info,
|
||||
dir, op=op)
|
||||
LOG.info("Successfully %(op)s acls by rule %(rule)s "
|
||||
"and policies %(p_ids)s",
|
||||
{"op": op,
|
||||
"rule": rule_info.get('id'),
|
||||
"p_ids": policy_ids})
|
||||
|
||||
def create_firewall_group_precommit(self, context, firewall_group):
|
||||
if not firewall_group['ports']:
|
||||
LOG.info("No ports bound to firewall_group: %s, "
|
||||
"set it to inactive", firewall_group['id'])
|
||||
status = const.INACTIVE
|
||||
else:
|
||||
status = const.PENDING_CREATE
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
self._init_firewall_group(txn, firewall_group['id'])
|
||||
firewall_group['status'] = status
|
||||
|
||||
def create_firewall_group_postcommit(self, context, firewall_group):
|
||||
pg_name = ovn_utils.ovn_port_group_name(firewall_group['id'])
|
||||
try:
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if (firewall_group['ingress_firewall_policy_id'] or
|
||||
firewall_group['egress_firewall_policy_id']):
|
||||
# Add rule acls to port_group
|
||||
self._add_rules_for_firewall_group(context, txn,
|
||||
firewall_group['id'])
|
||||
|
||||
if firewall_group['ports']:
|
||||
# Add ports to port_group
|
||||
ovn_acl.update_ports_for_pg(self._nb_ovn,
|
||||
txn, pg_name,
|
||||
firewall_group['ports'])
|
||||
firewall_group['status'] = const.ACTIVE
|
||||
LOG.info("Successfully added ports for firewall_group %s",
|
||||
firewall_group['id'])
|
||||
except Exception:
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if self._nb_ovn.get_port_group(pg_name):
|
||||
txn.add(self._nb_ovn.pg_del(name=pg_name, if_exists=True))
|
||||
LOG.error("Failed to create_firewall_group_postcommit.")
|
||||
raise
|
||||
else:
|
||||
self.firewall_db.update_firewall_group_status(
|
||||
context, firewall_group['id'], firewall_group['status'])
|
||||
|
||||
def update_firewall_group_precommit(self, context, old_firewall_group,
|
||||
new_firewall_group):
|
||||
port_updated = (set(new_firewall_group['ports']) !=
|
||||
set(old_firewall_group['ports']))
|
||||
policies_updated = (
|
||||
new_firewall_group['ingress_firewall_policy_id'] !=
|
||||
old_firewall_group['ingress_firewall_policy_id'] or
|
||||
new_firewall_group['egress_firewall_policy_id'] !=
|
||||
old_firewall_group['egress_firewall_policy_id']
|
||||
)
|
||||
if port_updated or policies_updated:
|
||||
new_firewall_group['status'] = const.PENDING_UPDATE
|
||||
|
||||
def update_firewall_group_postcommit(self, context, old_firewall_group,
|
||||
new_firewall_group):
|
||||
if new_firewall_group['status'] != const.PENDING_UPDATE:
|
||||
return
|
||||
old_ports = set(old_firewall_group['ports'])
|
||||
new_ports = set(new_firewall_group['ports'])
|
||||
old_ing_policy = old_firewall_group['ingress_firewall_policy_id']
|
||||
new_ing_policy = new_firewall_group['ingress_firewall_policy_id']
|
||||
old_eg_policy = old_firewall_group['egress_firewall_policy_id']
|
||||
new_eg_policy = new_firewall_group['egress_firewall_policy_id']
|
||||
pg_name = ovn_utils.ovn_port_group_name(new_firewall_group['id'])
|
||||
|
||||
# We except it would be active
|
||||
# If no ports, set it to inactive
|
||||
new_firewall_group['status'] = const.ACTIVE
|
||||
if not new_ports:
|
||||
LOG.info("No ports bound to firewall_group: %s, "
|
||||
"set it to inactive", new_firewall_group['id'])
|
||||
new_firewall_group['status'] = const.INACTIVE
|
||||
|
||||
# If port_group is not exist, recreate it,
|
||||
# add acls and ports.
|
||||
if not self._nb_ovn.get_port_group(pg_name):
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
self._init_firewall_group(txn, new_firewall_group['id'])
|
||||
if new_ports:
|
||||
ovn_acl.update_ports_for_pg(self._nb_ovn, txn,
|
||||
pg_name, new_ports)
|
||||
if new_ing_policy or new_eg_policy:
|
||||
self._add_rules_for_firewall_group(
|
||||
context, txn, new_firewall_group['id'])
|
||||
else:
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
# Process changes of ports
|
||||
if old_ports != new_ports:
|
||||
ports_add = list(new_ports - old_ports)
|
||||
ports_delete = list(old_ports - new_ports)
|
||||
ovn_acl.update_ports_for_pg(self._nb_ovn, txn, pg_name,
|
||||
ports_add, ports_delete)
|
||||
# Process changes of policies
|
||||
if (old_ing_policy != new_ing_policy or
|
||||
old_eg_policy != new_eg_policy):
|
||||
# Clear rules first
|
||||
self._clear_rules_for_firewall_group(
|
||||
context, txn, new_firewall_group['id'])
|
||||
# Add rules if it has
|
||||
if new_ing_policy or new_eg_policy:
|
||||
self._add_rules_for_firewall_group(
|
||||
context, txn, new_firewall_group['id'])
|
||||
|
||||
self.firewall_db.update_firewall_group_status(
|
||||
context, new_firewall_group['id'],
|
||||
new_firewall_group['status'])
|
||||
|
||||
def delete_firewall_group_precommit(self, context, firewall_group):
|
||||
pg_name = ovn_utils.ovn_port_group_name(firewall_group['id'])
|
||||
with self._nb_ovn.transaction(check_error=True) as txn:
|
||||
if self._nb_ovn.get_port_group(pg_name):
|
||||
txn.add(self._nb_ovn.pg_del(name=pg_name, if_exists=True))
|
||||
|
||||
def update_firewall_policy_postcommit(self, context, old_firewall_policy,
|
||||
new_firewall_policy):
|
||||
old_rules = old_firewall_policy['firewall_rules']
|
||||
new_rules = new_firewall_policy['firewall_rules']
|
||||
if old_rules == new_rules:
|
||||
return
|
||||
self._process_acls_by_policies_or_rule(context,
|
||||
[new_firewall_policy['id']])
|
||||
|
||||
def update_firewall_rule_postcommit(self, context, old_firewall_rule,
|
||||
new_firewall_rule):
|
||||
NEED_UPDATE_FIELDS = ['enabled', 'protocol', 'ip_version',
|
||||
'source_ip_address', 'destination_ip_address',
|
||||
'source_port', 'destination_port', 'action']
|
||||
need_update = False
|
||||
for field in NEED_UPDATE_FIELDS:
|
||||
if old_firewall_rule[field] != new_firewall_rule[field]:
|
||||
need_update = True
|
||||
if not need_update:
|
||||
return
|
||||
firewall_policy_ids = old_firewall_rule.get('firewall_policy_id')
|
||||
|
||||
# If rule is enabled, its acls should be inserted
|
||||
# If rule is disabled, its acls should be removed
|
||||
# If rule is always disabled, nothing to do
|
||||
if not old_firewall_rule['enabled'] and new_firewall_rule['enabled']:
|
||||
self._process_acls_by_policies_or_rule(context,
|
||||
firewall_policy_ids,
|
||||
new_firewall_rule)
|
||||
return
|
||||
elif old_firewall_rule['enabled'] and not new_firewall_rule['enabled']:
|
||||
self._process_acls_by_policies_or_rule(context,
|
||||
firewall_policy_ids,
|
||||
old_firewall_rule,
|
||||
ovn_const.OP_DEL)
|
||||
return
|
||||
elif not new_firewall_rule['enabled']:
|
||||
return
|
||||
|
||||
# Process changes of rule
|
||||
self._process_acls_by_policies_or_rule(
|
||||
context, firewall_policy_ids, new_firewall_rule, ovn_const.OP_MOD)
|
||||
LOG.info("Successfully updated acls for rule: %s",
|
||||
new_firewall_rule['id'])
|
||||
|
||||
def insert_rule_postcommit(self, context, policy_id, rule_info):
|
||||
# Add acls by policy_id
|
||||
self._process_acls_by_policies_or_rule(context, [policy_id])
|
||||
|
||||
def remove_rule_postcommit(self, context, policy_id, rule_info):
|
||||
rule_detail = self.firewall_db.get_firewall_rule(
|
||||
context, rule_info['firewall_rule_id'])
|
||||
self._process_acls_by_policies_or_rule(
|
||||
context, [policy_id], rule_detail, ovn_const.OP_DEL)
|
@ -0,0 +1,452 @@
|
||||
# Copyright 2022 EasyStack, 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 unittest import mock
|
||||
|
||||
from neutron import extensions as neutron_extensions
|
||||
from neutron.tests.unit.extensions import test_l3
|
||||
from neutron.tests.unit import fake_resources as fakes
|
||||
from neutron_lib import constants as nl_constants
|
||||
from neutron_lib import context
|
||||
|
||||
from neutron_fwaas.services.firewall.service_drivers import driver_api
|
||||
from neutron_fwaas.services.firewall.service_drivers.ovn import \
|
||||
firewall_l3_driver as ovn_driver
|
||||
from neutron_fwaas.tests.unit.services.firewall import test_fwaas_plugin_v2
|
||||
|
||||
|
||||
OVN_FWAAS_DRIVER = ('neutron_fwaas.services.firewall.service_drivers.'
|
||||
'ovn.firewall_l3_driver.OVNFwaasDriver')
|
||||
|
||||
|
||||
class TestOVNFwaasDriver(test_fwaas_plugin_v2.FirewallPluginV2TestCase,
|
||||
test_l3.L3NatTestCaseMixin):
|
||||
|
||||
def setUp(self):
|
||||
l3_plugin_str = ('neutron.tests.unit.extensions.test_l3.'
|
||||
'TestL3NatServicePlugin')
|
||||
l3_plugin = {'l3_plugin_name': l3_plugin_str}
|
||||
super(TestOVNFwaasDriver, self).setUp(
|
||||
service_provider=OVN_FWAAS_DRIVER,
|
||||
extra_service_plugins=l3_plugin,
|
||||
extra_extension_paths=neutron_extensions.__path__)
|
||||
self.db = self.plugin.driver.firewall_db
|
||||
self.mech_driver = mock.MagicMock()
|
||||
self.nb_ovn = fakes.FakeOvsdbNbOvnIdl()
|
||||
self.sb_ovn = fakes.FakeOvsdbSbOvnIdl()
|
||||
self.mech_driver._nb_ovn = self.nb_ovn
|
||||
self.mech_driver._sb_ovn = self.sb_ovn
|
||||
|
||||
@property
|
||||
def _self_context(self):
|
||||
return context.Context('', self._tenant_id)
|
||||
|
||||
def test_create_firewall_group_ports_not_specified(self):
|
||||
with self.firewall_policy(as_admin=True) as fwp, \
|
||||
mock.patch.object(driver_api.FirewallDriver,
|
||||
'_core_plugin') as mock_ml2:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
mock_ml2.mechanism_manager = mock.MagicMock()
|
||||
mock_ml2.mechanism_manager.mech_drivers = {
|
||||
'ovn': self.mech_driver}
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
admin_state_up=True,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
def test_create_firewall_group_with_ports(self):
|
||||
with self.router(name='router1', admin_state_up=True,
|
||||
tenant_id=self._tenant_id, as_admin=True) as r, \
|
||||
self.subnet(as_admin=True) as s1, \
|
||||
self.subnet(cidr='20.0.0.0/24', as_admin=True) as s2:
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s1['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id1 = body['port_id']
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s2['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id2 = body['port_id']
|
||||
fwg_ports = [port_id1, port_id2]
|
||||
with self.firewall_policy(do_delete=False,
|
||||
as_admin=True) as fwp, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
ports=fwg_ports, admin_state_up=True,
|
||||
do_delete=False, as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
def test_update_firewall_group_with_new_ports(self):
|
||||
with self.router(name='router1', admin_state_up=True,
|
||||
tenant_id=self._tenant_id, as_admin=True) as r, \
|
||||
self.subnet(as_admin=True) as s1, \
|
||||
self.subnet(cidr='20.0.0.0/24', as_admin=True) as s2, \
|
||||
self.subnet(cidr='30.0.0.0/24', as_admin=True) as s3:
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s1['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id1 = body['port_id']
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s2['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id2 = body['port_id']
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s3['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id3 = body['port_id']
|
||||
fwg_ports = [port_id1, port_id2]
|
||||
with self.firewall_policy(do_delete=False,
|
||||
as_admin=True) as fwp, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
ports=fwg_ports, admin_state_up=True,
|
||||
do_delete=False, as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
data = {'firewall_group': {'ports': [port_id2, port_id3]}}
|
||||
req = self.new_update_request('firewall_groups', data,
|
||||
fwg1['firewall_group']['id'],
|
||||
context=self._self_context,
|
||||
as_admin=True)
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual(sorted([port_id2, port_id3]),
|
||||
sorted(res['firewall_group']['ports']))
|
||||
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
res['firewall_group']['status'])
|
||||
|
||||
def test_update_firewall_group_with_ports_and_policy(self):
|
||||
with self.router(name='router1', admin_state_up=True,
|
||||
tenant_id=self._tenant_id, as_admin=True) as r, \
|
||||
self.subnet(as_admin=True) as s1, \
|
||||
self.subnet(cidr='20.0.0.0/24', as_admin=True) as s2:
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s1['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id1 = body['port_id']
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s2['subnet']['id'],
|
||||
None,
|
||||
as_admin=True)
|
||||
port_id2 = body['port_id']
|
||||
|
||||
fwg_ports = [port_id1, port_id2]
|
||||
with self.firewall_rule(do_delete=False, as_admin=True) as fwr, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr['firewall_rule']['id']],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
ports=fwg_ports,
|
||||
admin_state_up=True,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
fwp_id = fwp["firewall_policy"]["id"]
|
||||
data = {'firewall_group': {'ports': fwg_ports}}
|
||||
req = (self.
|
||||
new_update_request('firewall_groups', data,
|
||||
fwg1['firewall_group']['id'],
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
res['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_group': {
|
||||
'ingress_firewall_policy_id': fwp_id}}
|
||||
req = (self.
|
||||
new_update_request('firewall_groups', data,
|
||||
fwg1['firewall_group']['id'],
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
self.assertEqual(nl_constants.ACTIVE,
|
||||
res['firewall_group']['status'])
|
||||
|
||||
def test_update_firewall_policy_with_new_rules(self):
|
||||
with self.firewall_rule(do_delete=False, as_admin=True) as fwr, \
|
||||
self.firewall_rule(name='firewall_rule2', action='reject',
|
||||
do_delete=False, as_admin=True) as fwr2, \
|
||||
self.firewall_rule(name='firewall_rule3', action='deny',
|
||||
do_delete=False, as_admin=True) as fwr3, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
fwr2_id = fwr2['firewall_rule']['id']
|
||||
fwr3_id = fwr3['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
new_rules = [fwr_id, fwr2_id, fwr3_id]
|
||||
data = {'firewall_policy': {'firewall_rules':
|
||||
new_rules}}
|
||||
req = (self.
|
||||
new_update_request('firewall_policies', data,
|
||||
fwp_id,
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual(new_rules,
|
||||
res['firewall_policy']['firewall_rules'])
|
||||
|
||||
def test_disable_firewall_rule(self):
|
||||
with self.firewall_rule(do_delete=False, as_admin=True) as fwr, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_rule': {'enabled': False}}
|
||||
req = (self.
|
||||
new_update_request('firewall_rules', data,
|
||||
fwr_id,
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual(False,
|
||||
res['firewall_rule']['enabled'])
|
||||
|
||||
def test_enable_firewall_rule(self):
|
||||
with self.firewall_rule(enabled=False, do_delete=False,
|
||||
as_admin=True) as fwr, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_rule': {'enabled': True}}
|
||||
req = (self.
|
||||
new_update_request('firewall_rules', data,
|
||||
fwr_id,
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual(True,
|
||||
res['firewall_rule']['enabled'])
|
||||
|
||||
def test_update_firewall_rule_with_action(self):
|
||||
with self.firewall_rule(source_port=None, destination_port=None,
|
||||
protocol='icmp', do_delete=False,
|
||||
as_admin=True) as fwr, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_rule': {'action': 'deny'}}
|
||||
req = (self.
|
||||
new_update_request('firewall_rules', data,
|
||||
fwr_id,
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual('deny',
|
||||
res['firewall_rule']['action'])
|
||||
|
||||
def test_insert_rule_into_firewall_policy(self):
|
||||
with self.firewall_rule(do_delete=False, as_admin=True) as fwr, \
|
||||
self.firewall_rule(name='firewall_rule2', action='reject',
|
||||
do_delete=False, as_admin=True) as fwr2, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
fwr2_id = fwr2['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id],
|
||||
do_delete=False,
|
||||
as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_rule_id': fwr2_id,
|
||||
'insert_after': fwr_id}
|
||||
req = (self.
|
||||
new_update_request('firewall_policies', data,
|
||||
fwp_id,
|
||||
subresource='insert_rule',
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual([fwr_id, fwr2_id],
|
||||
res['firewall_rules'])
|
||||
|
||||
def test_remove_rules_from_firewall_policy(self):
|
||||
with self.firewall_rule(do_delete=False, as_admin=True) as fwr, \
|
||||
self.firewall_rule(name='firewall_rule2', action='reject',
|
||||
do_delete=False, as_admin=True) as fwr2, \
|
||||
mock.patch.object(ovn_driver.OVNFwaasDriver,
|
||||
'_nb_ovn') as mock_nb_ovn:
|
||||
mock_nb_ovn.return_value = self.nb_ovn
|
||||
fwr_id = fwr['firewall_rule']['id']
|
||||
fwr2_id = fwr2['firewall_rule']['id']
|
||||
with self.firewall_policy(
|
||||
firewall_rules=[fwr_id, fwr2_id],
|
||||
do_delete=False, as_admin=True) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall_group(
|
||||
name='test',
|
||||
default_policy=False,
|
||||
admin_state_up=True,
|
||||
ingress_firewall_policy_id=fwp_id,
|
||||
egress_firewall_policy_id=fwp_id,
|
||||
do_delete=False,
|
||||
as_admin=True) as fwg1:
|
||||
self.assertEqual(nl_constants.INACTIVE,
|
||||
fwg1['firewall_group']['status'])
|
||||
|
||||
data = {'firewall_rule_id': fwr2_id}
|
||||
req = (self.
|
||||
new_update_request('firewall_policies', data,
|
||||
fwp_id,
|
||||
subresource='remove_rule',
|
||||
context=self._self_context,
|
||||
as_admin=True))
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
self.assertEqual([fwr_id],
|
||||
res['firewall_rules'])
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
features:
|
||||
- L3 stateless firewall support for ML2/OVN driver is implemented.
|
||||
issues:
|
||||
- |
|
||||
If the user configures stateful security group rules for VMs ports and
|
||||
stateless L3 firewall rules for gateway ports like this:
|
||||
|
||||
- SG ingress rules: --remote_ip_prefix 0.0.0.0/0
|
||||
- FW ingress rules: --destination_ip_address 0.0.0.0/0 --action allow
|
||||
|
||||
It only opens ingress traffic for another network to access VM, but the
|
||||
reply traffic (egress direction) also passes because it matches the
|
||||
committed conntrack entry.
|
||||
So it only works well with stateless security groups for VMs.
|
Loading…
Reference in New Issue
Block a user