Co-existing between fwg and sg

The current driver is implemeted at [1], which will work
in standalone mode. However, the most important function of
fwaas v2 is "defense in depth". So this patch will enable
fwg and sg to co-exist. That means a packet must be allowed
by both of them.

[1]https://review.openstack.org/#/c/447251/

Co-Authored-By: Chandan Dutta Chowdhury <chandanc@juniper.net>

Change-Id: I3dc11c96637df765afa6abcc6ac9b24f942e53f7
This commit is contained in:
Nguyen Phuong An 2017-10-26 10:45:25 +07:00
parent f4e3d9cc69
commit 4d64670274
4 changed files with 163 additions and 29 deletions

View File

@ -244,7 +244,7 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
def _accept_flow(self, **flow):
for f in rules.create_accept_flows(flow):
for f in rules.create_accept_flows(flow, self.sg_enabled):
self._add_flow(**f)
def _drop_flow(self, **flow):
@ -289,6 +289,9 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
# differs in constants
def _drop_all_unmatched_flows(self):
for table in fwaas_ovs_consts.OVS_FIREWALL_TABLES:
if (table == fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE and
self.sg_enabled):
continue
self.int_br.br.add_flow(table=table, priority=0, actions='drop')
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
@ -492,6 +495,19 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
self._initialize_egress(port)
self._initialize_ingress(port)
def _fwaas_process_colocated_ingress(self, port):
for mac_addr in port.all_allowed_macs:
self._add_flow(
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=105,
dl_dst=mac_addr,
reg_net=port.vlan_tag,
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
port.ofport,
fwaas_ovs_consts.REG_PORT,
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
)
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
# which differs in constants (table numbers)
def _initialize_egress_ipv6_icmp(self, port):
@ -615,6 +631,9 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
)
# DHCP discovery
accept_or_ingress = fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE
if self.sg_enabled:
accept_or_ingress = ovs_consts.ACCEPT_OR_INGRESS_TABLE
for dl_type, src_port, dst_port in (
(constants.ETHERTYPE_IP, 68, 67),
(constants.ETHERTYPE_IPV6, 546, 547)):
@ -627,8 +646,7 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
nw_proto=lib_const.PROTO_NUM_UDP,
tp_src=src_port,
tp_dst=dst_port,
actions='resubmit(,{:d})'.format(
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE)
actions='resubmit(,{:d})'.format(accept_or_ingress)
)
# Ban dhcp service running on an instance
for dl_type, src_port, dst_port in (
@ -670,33 +688,37 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
# Fill in accept_or_ingress table by checking that traffic is ingress
# and if not, accept it
for mac_addr in port.all_allowed_macs:
if self.sg_enabled:
self._fwaas_process_colocated_ingress(port)
else:
for mac_addr in port.all_allowed_macs:
self._add_flow(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
priority=100,
dl_dst=mac_addr,
reg_net=port.vlan_tag,
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
port.ofport,
fwaas_ovs_consts.REG_PORT,
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
)
for ethertype in [constants.ETHERTYPE_IP,
constants.ETHERTYPE_IPV6]:
self._add_flow(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
priority=90,
dl_type=ethertype,
reg_port=port.ofport,
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
actions='ct(commit,zone=NXM_NX_REG{:d}[0..15]),normal'.
format(fwaas_ovs_consts.REG_NET)
)
self._add_flow(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
priority=100,
dl_dst=mac_addr,
reg_net=port.vlan_tag,
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
port.ofport,
fwaas_ovs_consts.REG_PORT,
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
)
for ethertype in [constants.ETHERTYPE_IP, constants.ETHERTYPE_IPV6]:
self._add_flow(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
priority=90,
dl_type=ethertype,
priority=80,
reg_port=port.ofport,
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
actions='ct(commit,zone=NXM_NX_REG{:d}[0..15]),normal'.format(
fwaas_ovs_consts.REG_NET)
actions='normal'
)
self._add_flow(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
priority=80,
reg_port=port.ofport,
actions='normal'
)
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
# which differs in constants (table numbers)
@ -935,13 +957,17 @@ class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
# which differs in constants (table numbers)
def delete_all_port_flows(self, port):
"""Delete all flows for given port"""
accept_or_ingress = fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE
if self.sg_enabled:
accept_or_ingress = ovs_consts.ACCEPT_OR_INGRESS_TABLE
for mac_addr in port.all_allowed_macs:
self._strict_delete_flow(priority=95,
table=ovs_consts.TRANSIENT_TABLE,
dl_dst=mac_addr,
dl_vlan=port.vlan_tag)
self._delete_flows(
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
table=accept_or_ingress,
dl_dst=mac_addr, reg_net=port.vlan_tag)
self._strict_delete_flow(priority=105,
table=ovs_consts.TRANSIENT_TABLE,

View File

@ -19,6 +19,8 @@ from oslo_log import log as logging
from neutron.agent import firewall
from neutron.common import utils
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
import constants as fwaas_ovs_consts
@ -167,11 +169,24 @@ def create_icmp_flows(flow_template, rule):
return [flow]
def create_accept_flows(flow):
def resubmit_to_sg(flow):
if flow['table'] == fwaas_ovs_consts.FW_RULES_EGRESS_TABLE:
flow['actions'] = 'resubmit(,{:d})'.format(
ovs_consts.RULES_EGRESS_TABLE)
if flow['table'] == fwaas_ovs_consts.FW_RULES_INGRESS_TABLE:
flow['actions'] = 'resubmit(,{:d})'.format(
ovs_consts.RULES_INGRESS_TABLE)
def create_accept_flows(flow, sg_enabled=False):
flow['ct_state'] = CT_STATES[0]
if sg_enabled:
resubmit_to_sg(flow)
result = [flow.copy()]
flow['ct_state'] = CT_STATES[1]
if flow['table'] == fwaas_ovs_consts.FW_RULES_INGRESS_TABLE:
if sg_enabled:
resubmit_to_sg(flow)
elif flow['table'] == fwaas_ovs_consts.FW_RULES_INGRESS_TABLE:
flow['actions'] = (
'ct(commit,zone=NXM_NX_REG{:d}[0..15]),{:s}'.format(
fwaas_ovs_consts.REG_NET, flow['actions']))

View File

@ -447,6 +447,45 @@ class TestOVSFirewallDriver(base.BaseTestCase):
for call in exp_ingress_classifier, exp_egress_classifier, filter_rule:
self.assertIn(call, calls)
def test_prepare_port_filter_in_coexistence_mode(self):
port_dict = {'device': 'port-id',
'firewall_group': 1,
'fixed_ips': [{'subnet_id': "some_subnet_id_here",
'ip_address': "10.0.0.1"}],
'lvlan': TESTING_VLAN_TAG}
self._prepare_firewall_group()
self.firewall.sg_enabled = True
self.firewall.prepare_port_filter(port_dict)
exp_egress_classifier = mock.call(
actions='set_field:{:d}->reg5,set_field:{:d}->reg6,'
'resubmit(,{:d})'.format(
self.port_ofport, TESTING_VLAN_TAG,
fwaas_ovs_consts.FW_BASE_EGRESS_TABLE),
in_port=self.port_ofport,
priority=105,
table=ovs_consts.TRANSIENT_TABLE)
exp_ingress_classifier = mock.call(
actions='set_field:{:d}->reg5,set_field:{:d}->reg6,'
'strip_vlan,resubmit(,{:d})'.format(
self.port_ofport, TESTING_VLAN_TAG,
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
dl_dst=self.port_mac,
dl_vlan='0x%x' % TESTING_VLAN_TAG,
priority=95,
table=ovs_consts.TRANSIENT_TABLE)
filter_rule = mock.call(
actions='resubmit(,{:d})'.format(ovs_consts.RULES_INGRESS_TABLE),
dl_type="0x{:04x}".format(n_const.ETHERTYPE_IP),
nw_proto=constants.PROTO_NUM_TCP,
priority=70,
reg5=self.port_ofport,
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
tcp_dst='0x007b')
calls = self.mock_bridge.br.add_flow.call_args_list
for call in exp_ingress_classifier, exp_egress_classifier, filter_rule:
self.assertIn(call, calls)
def test_prepare_port_filter_port_security_disabled(self):
port_dict = {'device': 'port-id',
'firewall_group': 1,
@ -504,6 +543,44 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.mock_bridge.br.add_flow.assert_has_calls(filter_rules,
any_order=True)
def test_update_port_filter_in_coexistence_mode(self):
port_dict = {'device': 'port-id',
'firewall_group': 1,
'lvlan': TESTING_VLAN_TAG}
self._prepare_firewall_group()
self.firewall.sg_enabled = True
self.firewall.prepare_port_filter(port_dict)
port_dict['firewall_group'] = 2
self.mock_bridge.reset_mock()
self.firewall.update_port_filter(port_dict)
self.assertTrue(self.mock_bridge.br.delete_flows.called)
filter_rules = [
mock.call(
actions='resubmit(,{:d})'.format(
ovs_consts.RULES_EGRESS_TABLE),
dl_type="0x{:04x}".format(n_const.ETHERTYPE_IP),
nw_proto=constants.PROTO_NUM_UDP,
priority=71,
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
reg5=self.port_ofport,
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE),
# XXX FIXME NOTE(ivasilevskaya) this test originally tested that
# flows for SG with remote_group=this group were generated with
# proper conjunction action. If the original idea that conj_manager
# isn't needed for firewall groups proves to be wrong this needs to
# be revizited and properly fixed/covered with tests
mock.call(
actions='resubmit(,{:d})'.format(
ovs_consts.RULES_EGRESS_TABLE),
ct_state=fwaas_ovs_consts.OF_STATE_ESTABLISHED_NOT_REPLY,
dl_type=mock.ANY,
nw_proto=6,
priority=70, reg5=self.port_ofport,
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE)]
self.mock_bridge.br.add_flow.assert_has_calls(filter_rules,
any_order=True)
def test_update_port_filter_create_new_port_if_not_present(self):
port_dict = {'device': 'port-id',
'firewall_group': 1}

View File

@ -0,0 +1,16 @@
---
prelude: >
Coexistence between security group and firewall group.
features:
- L2 firewall group driver based OVS can work in coexistence mode.
That means, if a port is associated with both firewall group and
security group, then a packet must be allowed by both features.
other:
- If a port is associated with both firewall group & security group and
there is a security group logging, which is enabled to collect ``DROP``
events for this port, then most of invalid packets will be dropped at
firewall group for performance reason except first dropped packet, which
is allowed by firewall group but not accepted by security group. So not
every dropped packet will be logged (like in case of security group
works in standalone mode).