FWaaS v2 L3 Agent Extension

FWaaS v2 agent extension supporting L3 ports and interfaces with
the FWaaS v2 plugin and the FWaaS v2 driver.

Partial-Implements: blueprint fwaas-api-2.0

Co-Authored-By: Chandan Dutta Chowdhury <chandanc@juniper.net>
Co-Authored-By: Nate Johnston <Nate_Johnston@cable.comcast.com>
Depends-on: Ia923cf1c1abb2dae18370215e16a6f896671392b

Change-Id: Iff506bd11b83d396305e631f3dd95d44cf38fd63
This commit is contained in:
Margaret Frances 2016-07-05 10:34:25 -04:00 committed by Chandan Dutta Chowdhury
parent 151946dcb1
commit fd8d6d27f0
12 changed files with 1753 additions and 18 deletions

View File

@ -26,12 +26,13 @@ from neutron.common import config as common_config
from neutron.common import topics
from neutron.conf.agent.l3 import config as l3_config
from neutron import service as neutron_service
from neutron_fwaas.services.firewall.agents import firewall_agent_api as fwa
FWAAS_AGENT = (
'neutron_fwaas.services.firewall.agents.'
'l3reference.firewall_l3_agent.L3WithFWaaS'
)
FWAAS_AGENTS = {fwa.FWAAS_V1: ('neutron_fwaas.services.firewall.agents.'
'l3reference.firewall_l3_agent.L3WithFWaaS'),
fwa.FWAAS_V2: ('neutron_fwaas.services.firewall.agents.'
'l3reference.firewall_l3_agent_v2.L3WithFWaaS')}
def register_opts(conf):
@ -48,10 +49,11 @@ def register_opts(conf):
config.register_availability_zone_opts_helper(conf)
def main(manager=FWAAS_AGENT):
def main():
register_opts(cfg.CONF)
common_config.init(sys.argv[1:])
config.setup_logging()
manager = FWAAS_AGENTS[cfg.CONF.fwaas.agent_version]
server = neutron_service.Service.create(
binary='neutron-l3-agent',
topic=topics.L3_AGENT,

View File

@ -20,6 +20,10 @@ import oslo_messaging
from neutron_fwaas._i18n import _
FWAAS_V1 = "v1"
FWAAS_V2 = "v2"
FWaaSOpts = [
cfg.StrOpt(
'driver',
@ -29,6 +33,10 @@ FWaaSOpts = [
'enabled',
default=False,
help=_("Enable FWaaS")),
cfg.StrOpt(
'agent_version',
default=FWAAS_V1,
help=_("Firewall agent class")),
]
cfg.CONF.register_opts(FWaaSOpts, 'fwaas')

View File

@ -0,0 +1,461 @@
# Copyright (c) 2016
# 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.agent.l3 import agent
from neutron.agent.linux import ip_lib
from neutron import context
from neutron.plugins.common import constants as n_const
from neutron_fwaas.common import fwaas_constants as f_const
from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from neutron_fwaas._i18n import _, _LE
from neutron_fwaas.extensions import firewall as fw_ext
from neutron_fwaas.services.firewall.agents import firewall_agent_api as api
from neutron_fwaas.services.firewall.agents import firewall_service
LOG = logging.getLogger(__name__)
class FWaaSL3PluginApi(api.FWaaSPluginApiMixin):
"""Agent side of the FWaaS agent-to-plugin RPC API."""
def __init__(self, topic, host):
super(FWaaSL3PluginApi, self).__init__(topic, host)
def get_firewall_groups_for_project(self, context, **kwargs):
"""Fetches a project's firewall groups from the plugin."""
LOG.debug("Fetch firewall groups from plugin")
cctxt = self.client.prepare()
return cctxt.call(context, 'get_firewall_groups_for_project',
host=self.host)
def get_projects_with_firewall_groups(self, context, **kwargs):
"""Fetches from the plugin all projects that have firewall groups
configured.
"""
LOG.debug("Fetch from plugin projects that have firewall groups "
"configured")
cctxt = self.client.prepare()
return cctxt.call(context,
'get_projects_with_firewall_groups', host=self.host)
def firewall_group_deleted(self, context, fwg_id, **kwargs):
"""Notifies the plugin that a firewall group has been deleted."""
LOG.debug("Notify plugin that firewall group has been deleted")
cctxt = self.client.prepare()
return cctxt.call(context, 'firewall_group_deleted', fwg_id=fwg_id,
host=self.host)
def set_firewall_group_status(self, context, fwg_id, status, **kwargs):
"""Sets firewall group's status on the plugin."""
LOG.debug("Set firewall groups from plugin")
cctxt = self.client.prepare()
return cctxt.call(context, 'set_firewall_group_status',
fwg_id=fwg_id, status=status, host=self.host)
class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin):
"""FWaaS agent support to be used by neutron's L3 agent."""
def __init__(self, host, conf):
LOG.debug("Initializing firewall group agent")
self.neutron_service_plugins = None
self.conf = conf
self.fwaas_enabled = cfg.CONF.fwaas.enabled
# None means l3-agent has no information on the server
# configuration due to the lack of RPC support.
if self.neutron_service_plugins is not None:
fwaas_plugin_configured = (n_const.FIREWALL
in self.neutron_service_plugins)
if fwaas_plugin_configured and not self.fwaas_enabled:
msg = _("FWaaS plugin is configured in the server side, but "
"FWaaS is disabled in L3-agent.")
LOG.error(msg)
raise SystemExit(1)
self.fwaas_enabled = self.fwaas_enabled and fwaas_plugin_configured
if self.fwaas_enabled:
# NOTE: Temp location for creating service and loading driver
self.fw_service = firewall_service.FirewallService()
self.fwaas_driver = self.fw_service.load_device_drivers()
self.services_sync_needed = False
self.fwplugin_rpc = FWaaSL3PluginApi(f_const.FIREWALL_PLUGIN,
host)
super(FWaaSL3AgentRpcCallback, self).__init__(host=host)
@property
def _local_namespaces(self):
root_ip = ip_lib.IPWrapper()
local_ns_list = root_ip.get_namespaces()
return local_ns_list
def _has_port_insertion_fields(self, firewall_group):
"""The presence of the 'add-port-ids' key in the firewall group dict
shows we are using the current version of the plugin. If this key
is absent, we are in an upgrade and message is from an older
version of the plugin.
"""
return 'add-port-ids' in firewall_group
def _get_firewall_group_ports(self, context, firewall_group,
to_delete=False, require_new_plugin=False):
"""Returns in-namespace ports, either from firewall group dict if newer
version of plugin or from project routers otherwise.
NOTE: Vernacular move from "tenant" to "project" doesn't yet appear
as a key in router or firewall group objects.
"""
fwg_port_ids = []
if self._has_port_insertion_fields(firewall_group):
if to_delete:
fwg_port_ids = firewall_group['del-port-ids']
else:
fwg_port_ids = firewall_group['add-port-ids']
elif not require_new_plugin:
routers = [self.router_info[rid] for rid in self.router_info]
for router in routers:
if router.router['tenant_id'] == firewall_group['tenant_id']:
fwg_port_ids.extend([p['id'] for p in
router.internal_ports])
# Return in-namespace port objects.
return self._get_in_ns_ports(fwg_port_ids)
def _get_in_ns_ports(self, port_ids):
"""Returns port objects in the local namespace, along with their
router_info.
"""
in_ns_ports = []
if port_ids:
for router_id in self.router_info:
# For routers without an interface - get_routers returns
# the router - but this is not yet populated in router_info
router_info = self.router_info[router_id]
if router_info.ns_name not in self._local_namespaces:
continue
in_ns_router_port_ids = []
for port in router_info.internal_ports:
if port['id'] in port_ids:
in_ns_router_port_ids.append(port['id'])
if in_ns_router_port_ids:
in_ns_ports.append((router_info, in_ns_router_port_ids))
return in_ns_ports
def _invoke_driver_for_sync_from_plugin(self, ctx, port, firewall_group):
"""Calls the FWaaS driver's delete_firewall_group method if firewall
group has status of PENDING_DELETE; calls driver's
update_firewall_group method for all other statuses. Both of these
methods are idempotent.
"""
if firewall_group['status'] == n_const.PENDING_DELETE:
try:
self.fwaas_driver.delete_firewall_group(
self.conf.agent_mode, [port], firewall_group)
self.fwplugin_rpc.firewall_group_deleted(
ctx, firewall_group['id'])
except fw_ext.FirewallInternalDriverError:
msg = _LE("FWaaS driver error on %(status)s "
"for firewall group: %(fwg_id)s")
LOG.exception(msg, {'status': firewall_group['status'],
'fwg_id': firewall_group['id']})
self.fwplugin_rpc.set_firewall_group_status(
ctx, firewall_group['id'], n_const.ERROR)
else: # PENDING_UPDATE, PENDING_CREATE, ...
# Prepare firewall group status to return to plugin; may be
# overwritten if call to driver fails.
if firewall_group['admin_state_up']:
status = n_const.ACTIVE
else:
status = n_const.DOWN
# Call the driver.
try:
self.fwaas_driver.update_firewall_group(
self.conf.agent_mode, [port], firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = _LE("FWaaS driver error on %(status)s for firewall "
"group: "
"%(fwg_id)s")
LOG.exception(msg, {'status': firewall_group['status'],
'fwg_id': firewall_group['id']})
status = n_const.ERROR
# Notify the plugin of firewall group's status.
self.fwplugin_rpc.set_firewall_group_status(
ctx, firewall_group['id'], status)
def _process_router_add(self, new_router):
"""If the new router is in the local namespace, queries the plugin to
get the firewall groups for the project in question and then sees if
the router has any ports for any firewall group that is configured
for that project. If so, installs firewall group rules on the
requested ports on this router.
"""
LOG.debug("Process router add, router_id: %s.",
new_router.router['id'])
router_id = new_router.router['id']
if router_id not in self.router_info or \
self.router_info[router_id].ns_name not in \
self._local_namespaces:
return
# Get the firewall groups for the new router's project.
# NOTE: Vernacular move from "tenant" to "project" doesn't yet appear
# as a key in router or firewall group objects.
ctx = context.Context('', new_router.router['tenant_id'])
fwg_list = self.fwplugin_rpc.get_firewall_groups_for_project(ctx)
# Apply a firewall group, as requested, to ports on the new router.
for port in new_router.router.internal_ports:
for firewall_group in fwg_list:
if (self._has_port_insertion_fields(firewall_group) and
(port['id'] in firewall_group['add-port-ids'] or
port['id'] in firewall_group['del-port-ids'])):
self._invoke_driver_for_sync_from_plugin(ctx, port,
firewall_group)
# A port can have at most one firewall group.
break
def process_router_add(self, new_router):
"""Handles agent restart and router add. Fetches firewall groups from
plugin and updates driver.
"""
if not self.fwaas_enabled:
return
try:
self._process_router_add(new_router)
except Exception:
LOG.exception(_LE("FWaaS RPC info call failed for %s"),
new_router.router['id'])
self.services_sync_needed = True
def process_services_sync(self, ctx):
"""Syncs with plugin and applies the sync data.
"""
if not self.services_sync_needed or not self.fwaas_enabled:
return
try:
# Fetch from the plugin the list of projects with firewall groups.
project_ids = \
self.fwplugin_rpc.get_projects_with_firewall_groups(ctx)
LOG.debug("Projects with firewall groups: %s",
', '.join(project_ids))
for project_id in project_ids:
ctx = context.Context('', project_id)
fwg_list = \
self.fwplugin_rpc.get_firewall_groups_for_project(ctx)
for firewall_group in fwg_list:
if firewall_group['status'] == n_const.PENDING_DELETE:
self.delete_firewall_group(ctx, firewall_group,
self.host)
# No need to apply sync data for ACTIVE firewall group.
elif firewall_group['status'] != n_const.ACTIVE:
self.update_firewall_group(ctx, firewall_group,
self.host)
self.services_sync_needed = False
except Exception:
LOG.exception(_LE("Failed FWaaS process services sync."))
self.services_sync_needed = True
@log_helpers.log_method_call
def create_firewall_group(self, context, firewall_group, host):
"""Handles RPC from plugin to create a firewall group.
"""
# Get the in-namespace ports to which to add the firewall group.
ports_for_fwg = self._get_firewall_group_ports(context, firewall_group)
if not ports_for_fwg:
return
LOG.debug("Create firewall group %(fwg_id)s on ports: %(ports)s"
% {'fwg_id': firewall_group['id'],
'ports': ', '.join([p for ri_ports in ports_for_fwg
for p in ri_ports[1]])})
# Set firewall group status; will be overwritten if call to driver
# fails.
if firewall_group['admin_state_up']:
status = n_const.ACTIVE
else:
status = n_const.DOWN
# Call the driver.
try:
self.fwaas_driver.create_firewall_group(self.conf.agent_mode,
ports_for_fwg,
firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = _LE("FWaaS driver error in create_firewall_group "
"for firewall group: %(fwg_id)s")
LOG.exception(msg, {'fwg_id': firewall_group['id']})
status = n_const.ERROR
# Send firewall group's status to plugin.
try:
self.fwplugin_rpc.set_firewall_group_status(context,
firewall_group['id'], status)
except Exception:
msg = _LE("FWaaS RPC failure in create_firewall_group "
"for firewall group: %(fwg_id)s")
LOG.exception(msg, {'fwg_id': firewall_group['id']})
self.services_sync_needed = True
@log_helpers.log_method_call
def update_firewall_group(self, context, firewall_group, host):
"""Handles RPC from plugin to update a firewall group.
"""
# Initialize firewall group status.
status = ""
# Get the list of in-namespace ports from which to delete the firewall
# group.
ports_for_fwg = self._get_firewall_group_ports(context, firewall_group,
to_delete=True, require_new_plugin=True)
# Remove firewall group from ports if requested.
if ports_for_fwg:
fw_ports = [p for ri_ports in ports_for_fwg for p in ri_ports[1]]
LOG.debug("Update (delete) firewall group %(fwg_id)s on ports: "
"%(ports)s" % {'fwg_id': firewall_group['id'],
'ports': ', '.join(fw_ports)})
# Set firewall group's status; will be overwritten if call to
# driver fails.
if firewall_group['admin_state_up']:
status = n_const.ACTIVE
if firewall_group['last-port']:
status = n_const.INACTIVE
else:
status = n_const.DOWN
# Call the driver.
try:
self.fwaas_driver.delete_firewall_group(self.conf.agent_mode,
ports_for_fwg,
firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = _LE("FWaaS driver error in update_firewall_group "
"(add) for firewall group: %s")
LOG.exception(msg, firewall_group['id'])
status = n_const.ERROR
# Handle the add router and/or rule, policy, firewall group attribute
# updates.
if status not in (n_const.ERROR, n_const.INACTIVE):
ports_for_fwg = self._get_firewall_group_ports(context,
firewall_group)
if ports_for_fwg:
LOG.debug("Update (create) firewall group %(fwg_id)s on "
"ports: %(ports)s" % {'fwg_id': firewall_group['id'],
'ports': ', '.join(fw_ports)})
# Set firewall group status, which will be overwritten if call
# to driver fails.
if firewall_group['admin_state_up']:
status = n_const.ACTIVE
else:
status = n_const.DOWN
# Call the driver.
try:
self.fwaas_driver.update_firewall_group(
self.conf.agent_mode, ports_for_fwg,
firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = _LE("FWaaS driver error in update_firewall_group "
"for firewall group: %s")
LOG.exception(msg, firewall_group['id'])
status = n_const.ERROR
else:
status = n_const.INACTIVE
# Return status to plugin.
try:
self.fwplugin_rpc.set_firewall_group_status(context,
firewall_group['id'], status)
except Exception:
LOG.exception(_LE("FWaaS RPC failure in update_firewall_group "
"for firewall group: %(fwg_id)s"),
{'fwg_id': firewall_group['id']})
self.services_sync_needed = True
@log_helpers.log_method_call
def delete_firewall_group(self, context, firewall_group, host):
"""Handles RPC from plugin to delete a firewall group.
"""
ports_for_fwg = self._get_firewall_group_ports(context, firewall_group,
to_delete=True)
if not ports_for_fwg:
return
fw_ports = [p for ri_ports in ports_for_fwg for p in ri_ports[1]]
LOG.debug("Delete firewall group %(fwg_id)s on ports: %(ports)s"
% {'fwg_id': firewall_group['id'],
'ports': ', '.join(fw_ports)})
# Set the firewall group's status to return to plugin; status may be
# overwritten if call to driver fails.
if firewall_group['admin_state_up']:
status = n_const.ACTIVE
else:
status = n_const.DOWN
try:
self.fwaas_driver.delete_firewall_group(self.conf.agent_mode,
ports_for_fwg,
firewall_group)
# Call the driver.
except fw_ext.FirewallInternalDriverError:
LOG.exception(_LE("FWaaS driver error in delete_firewall_group "
"for firewall group: %(fwg_id)s"),
{'fwg_id': firewall_group['id']})
status = n_const.ERROR
# Notify plugin of deletion or return firewall group's status to
# plugin, as appopriate.
try:
if status in [n_const.ACTIVE, n_const.DOWN]:
self.fwplugin_rpc.firewall_group_deleted(context,
firewall_group['id'])
else:
self.fwplugin_rpc.set_firewall_group_status(context,
firewall_group['id'], status)
except Exception:
LOG.exception(_LE("FWaaS RPC failure in delete_firewall_group "
"for firewall group: %(fwg_id)s"),
{'fwg_id': firewall_group['id']})
self.services_sync_needed = True
class L3WithFWaaS(FWaaSL3AgentRpcCallback, agent.L3NATAgentWithStateReport):
def __init__(self, host, conf=None):
if conf:
self.conf = conf
else:
self.conf = cfg.CONF
super(L3WithFWaaS, self).__init__(host=self.conf.host, conf=self.conf)

View File

@ -21,13 +21,13 @@ eventlet.monkey_patch()
import netaddr
from neutron.agent.common import config
from neutron.agent.l3 import agent
from neutron.agent.l3 import config as l3_config
from neutron.agent.l3 import ha
from neutron.agent.l3 import router_info
from neutron.agent.linux import external_process
from neutron.agent.linux import interface
from neutron.agent.linux import ip_lib
from neutron.common import config as common_config
from neutron.conf.agent.l3 import config as l3_config
from neutron import service as neutron_service
from neutron_lib import constants as l3_constants
from oslo_config import cfg

View File

@ -0,0 +1,96 @@
# Copyright (c) 2016
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class FwaasDriverBase(object):
"""Firewall as a Service Driver base class.
Using FwaasDriver Class, an instance of L3 perimeter Firewall
can be created. The firewall co-exists with the L3 agent.
One instance is created for each tenant. One firewall policy
is associated with each tenant (in the Havana release).
The Firewall can be visualized as having two zones (in Havana
release), trusted and untrusted.
All the 'internal' interfaces of Neutron Router is treated as trusted. The
interface connected to 'external network' is treated as untrusted.
The policy is applied on traffic ingressing/egressing interfaces on
the trusted zone. This implies that policy will be applied for traffic
passing from
- trusted to untrusted zones
- untrusted to trusted zones
- trusted to trusted zones
Policy WILL NOT be applied for traffic from untrusted to untrusted zones.
This is not a problem in Havana release as there is only one interface
connected to external network.
Since the policy is applied on the internal interfaces, the traffic
will be not be NATed to floating IP. For incoming traffic, the
traffic will get NATed to internal IP address before it hits
the firewall rules. So, while writing the rules, care should be
taken if using rules based on floating IP.
The firewall rule addition/deletion/insertion/update are done by the
management console. When the policy is sent to the driver, the complete
policy is sent and the whole policy has to be applied atomically. The
firewall rules will not get updated individually. This is to avoid problems
related to out-of-order notifications or inconsistent behaviour by partial
application of rules. Argument agent_mode indicates the l3 agent in DVR or
DVR_SNAT or LEGACY mode.
"""
@abc.abstractmethod
def create_firewall_group(self, agent_mode, apply_list, firewall):
"""Create the Firewall with default (drop all) policy.
The default policy will be applied on all the interfaces of
trusted zone.
"""
pass
@abc.abstractmethod
def delete_firewall_group(self, agent_mode, apply_list, firewall):
"""Delete firewall.
Removes all policies created by this instance and frees up
all the resources.
"""
pass
@abc.abstractmethod
def update_firewall_group(self, agent_mode, apply_list, firewall):
"""Apply the policy on all trusted interfaces.
Remove previous policy and apply the new policy on all trusted
interfaces.
"""
pass
@abc.abstractmethod
def apply_default_policy(self, agent_mode, apply_list, firewall):
"""Apply the default policy on all trusted interfaces.
Remove current policy and apply the default policy on all trusted
interfaces.
"""
pass

View File

@ -0,0 +1,456 @@
# Copyright (c) 2016
# 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.agent.linux import iptables_manager
from neutron.agent.linux import utils as linux_utils
from oslo_log import log as logging
from neutron_fwaas._i18n import _LE
from neutron_fwaas.extensions import firewall as fw_ext
from neutron_fwaas.services.firewall.drivers import fwaas_base_v2
LOG = logging.getLogger(__name__)
FWAAS_DRIVER_NAME = 'Fwaas iptables driver'
FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy'
FWAAS_TO_IPTABLE_ACTION_MAP = {'allow': 'ACCEPT',
'deny': 'DROP',
'reject': 'REJECT'}
INGRESS_DIRECTION = 'ingress'
EGRESS_DIRECTION = 'egress'
CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i',
EGRESS_DIRECTION: 'o'}
""" Firewall rules are applied on internal-interfaces of Neutron router.
The packets ingressing tenant's network will be on the output
direction on internal-interfaces.
"""
IPTABLES_DIR = {INGRESS_DIRECTION: '-o',
EGRESS_DIRECTION: '-i'}
IPV4 = 'ipv4'
IPV6 = 'ipv6'
IP_VER_TAG = {IPV4: 'v4',
IPV6: 'v6'}
INTERNAL_DEV_PREFIX = 'qr-'
SNAT_INT_DEV_PREFIX = 'sg-'
ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
MAX_INTF_NAME_LEN = 14
class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase):
"""IPTables driver for Firewall As A Service."""
def __init__(self):
LOG.debug("Initializing fwaas iptables driver")
self.pre_firewall = None
def _get_intf_name(self, if_prefix, port_id):
_name = "%s%s" % (if_prefix, port_id)
return _name[:MAX_INTF_NAME_LEN]
def create_firewall_group(self, agent_mode, apply_list, firewall):
LOG.debug('Creating firewall %(fw_id)s for tenant %(tid)s',
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
try:
if firewall['admin_state_up']:
self._setup_firewall(agent_mode, apply_list, firewall)
self._remove_conntrack_new_firewall(agent_mode,
apply_list, firewall)
self.pre_firewall = dict(firewall)
else:
self.apply_default_policy(agent_mode, apply_list, firewall)
except (LookupError, RuntimeError):
# catch known library exceptions and raise Fwaas generic exception
LOG.exception(_LE("Failed to create firewall: %s"), firewall['id'])
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
def _get_ipt_mgrs_with_if_prefix(self, agent_mode, router_info):
"""Gets the iptables manager along with the if prefix to apply rules.
With DVR we can have differing namespaces depending on which agent
(on Network or Compute node). Also, there is an associated i/f for
each namespace. The iptables on the relevant namespace and matching
i/f are provided. On the Network node we could have both the snat
namespace and a fip so this is provided back as a list - so in that
scenario rules can be applied on both.
"""
if not router_info.router.get('distributed'):
return [{'ipt': router_info.iptables_manager,
'if_prefix': INTERNAL_DEV_PREFIX}]
ipt_mgrs = []
# TODO(sridar): refactor to get strings to a common location.
if agent_mode == 'dvr_snat':
if router_info.snat_iptables_manager:
ipt_mgrs.append({'ipt': router_info.snat_iptables_manager,
'if_prefix': SNAT_INT_DEV_PREFIX})
if router_info.dist_fip_count:
# handle the fip case on n/w or compute node.
ipt_mgrs.append({'ipt': router_info.iptables_manager,
'if_prefix': ROUTER_2_FIP_DEV_PREFIX})
return ipt_mgrs
def delete_firewall_group(self, agent_mode, apply_list, firewall):
LOG.debug('Deleting firewall %(fw_id)s for tenant %(tid)s',
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
fwid = firewall['id']
try:
for router_info, router_fw_ports in apply_list:
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
ipt_mgr = ipt_if_prefix['ipt']
self._remove_chains(fwid, ipt_mgr)
self._remove_default_chains(ipt_mgr)
# apply the changes immediately (no defer in firewall path)
ipt_mgr.defer_apply_off()
self.pre_firewall = None
except (LookupError, RuntimeError):
# catch known library exceptions and raise Fwaas generic exception
LOG.exception(_LE("Failed to delete firewall: %s"), fwid)
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
def update_firewall_group(self, agent_mode, apply_list, firewall):
LOG.debug('Updating firewall %(fw_id)s for tenant %(tid)s',
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
try:
if firewall['admin_state_up']:
if self.pre_firewall:
self._remove_conntrack_updated_firewall(agent_mode,
apply_list, self.pre_firewall, firewall)
else:
self._remove_conntrack_new_firewall(agent_mode,
apply_list, firewall)
self._setup_firewall(agent_mode, apply_list, firewall)
else:
self.apply_default_policy(agent_mode, apply_list, firewall)
self.pre_firewall = dict(firewall)
except (LookupError, RuntimeError):
# catch known library exceptions and raise Fwaas generic exception
LOG.exception(_LE("Failed to update firewall: %s"), firewall['id'])
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
def apply_default_policy(self, agent_mode, apply_list, firewall):
LOG.debug('Applying firewall %(fw_id)s for tenant %(tid)s',
{'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
fwid = firewall['id']
try:
for router_info, router_fw_ports in apply_list:
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
# the following only updates local memory; no hole in FW
ipt_mgr = ipt_if_prefix['ipt']
self._remove_chains(fwid, ipt_mgr)
self._remove_default_chains(ipt_mgr)
# create default 'DROP ALL' policy chain
self._add_default_policy_chain_v4v6(ipt_mgr)
self._enable_policy_chain(fwid, ipt_if_prefix,
router_fw_ports)
# apply the changes immediately (no defer in firewall path)
ipt_mgr.defer_apply_off()
except (LookupError, RuntimeError):
# catch known library exceptions and raise Fwaas generic exception
LOG.exception(
_LE("Failed to apply default policy on firewall: %s"), fwid)
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
def _setup_firewall(self, agent_mode, apply_list, firewall):
fwid = firewall['id']
for router_info, router_fw_ports in apply_list:
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
ipt_mgr = ipt_if_prefix['ipt']
# the following only updates local memory; no hole in FW
self._remove_chains(fwid, ipt_mgr)
self._remove_default_chains(ipt_mgr)
# create default 'DROP ALL' policy chain
self._add_default_policy_chain_v4v6(ipt_mgr)
# create chain based on configured policy
self._setup_chains(firewall, ipt_if_prefix, router_fw_ports)
# apply the changes immediately (no defer in firewall path)
ipt_mgr.defer_apply_off()
def _get_chain_name(self, fwid, ver, direction):
return '%s%s%s' % (CHAIN_NAME_PREFIX[direction],
IP_VER_TAG[ver],
fwid)
def _setup_chains(self, firewall, ipt_if_prefix, router_fw_ports):
"""Create Fwaas chain using the rules in the policy
"""
egress_rule_list = firewall['egress_rule_list']
ingress_rule_list = firewall['ingress_rule_list']
fwid = firewall['id']
ipt_mgr = ipt_if_prefix['ipt']
# default rules for invalid packets and established sessions
invalid_rule = self._drop_invalid_packets_rule()
est_rule = self._allow_established_rule()
for ver in [IPV4, IPV6]:
if ver == IPV4:
table = ipt_mgr.ipv4['filter']
else:
table = ipt_mgr.ipv6['filter']
ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
for name in [ichain_name, ochain_name]:
table.add_chain(name)
table.add_rule(name, invalid_rule)
table.add_rule(name, est_rule)
for rule in ingress_rule_list:
if not rule['enabled']:
continue
iptbl_rule = self._convert_fwaas_to_iptables_rule(rule)
if rule['ip_version'] == 4:
ver = IPV4
table = ipt_mgr.ipv4['filter']
else:
ver = IPV6
table = ipt_mgr.ipv6['filter']
ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
table.add_rule(ichain_name, iptbl_rule)
for rule in egress_rule_list:
if not rule['enabled']:
continue
iptbl_rule = self._convert_fwaas_to_iptables_rule(rule)
if rule['ip_version'] == 4:
ver = IPV4
table = ipt_mgr.ipv4['filter']
else:
ver = IPV6
table = ipt_mgr.ipv6['filter']
ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
table.add_rule(ochain_name, iptbl_rule)
self._enable_policy_chain(fwid, ipt_if_prefix, router_fw_ports)
def _find_changed_rules(self, pre_firewall, firewall):
"""Find the rules changed between the current firewall
and the updating rule
"""
changed_rules = []
for fw_rule_list in ['egress_rule_list', 'ingress_rule_list']:
pre_fw_rules = pre_firewall[fw_rule_list]
fw_rules = firewall[fw_rule_list]
for pre_fw_rule in pre_fw_rules:
for fw_rule in fw_rules:
if (pre_fw_rule.get('id') == fw_rule.get('id') and
pre_fw_rule != fw_rule):
changed_rules.append(pre_fw_rule)
changed_rules.append(fw_rule)
return changed_rules
def _find_removed_rules(self, pre_firewall, firewall):
removed_rules = []
for fw_rule_list in ['egress_rule_list', 'ingress_rule_list']:
pre_fw_rules = pre_firewall[fw_rule_list]
fw_rules = firewall[fw_rule_list]
fw_rule_ids = [fw_rule['id'] for fw_rule in fw_rules]
removed_rules.extend([pre_fw_rule for pre_fw_rule in pre_fw_rules
if pre_fw_rule['id'] not in fw_rule_ids])
return removed_rules
def _find_new_rules(self, pre_firewall, firewall):
return self._find_removed_rules(firewall, pre_firewall)
def _get_conntrack_cmd_from_rule(self, ipt_mgr, rule=None):
prefixcmd = ['ip', 'netns', 'exec'] + [ipt_mgr.namespace]
cmd = ['conntrack', '-D']
if rule:
conntrack_filter = self._get_conntrack_filter_from_rule(rule)
exec_cmd = prefixcmd + cmd + conntrack_filter
else:
exec_cmd = prefixcmd + cmd
return exec_cmd
def _remove_conntrack_by_cmd(self, cmd):
if cmd:
try:
linux_utils.execute(cmd, run_as_root=True,
check_exit_code=True,
extra_ok_codes=[1])
except RuntimeError:
LOG.exception(
_LE("Failed execute conntrack command %s"), str(cmd))
def _remove_conntrack_new_firewall(self, agent_mode, apply_list, firewall):
"""Remove conntrack when create new firewall"""
routers_list = list(set([apply_info[0] for apply_info in apply_list]))
for router_info in routers_list:
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
ipt_mgr = ipt_if_prefix['ipt']
cmd = self._get_conntrack_cmd_from_rule(ipt_mgr)
self._remove_conntrack_by_cmd(cmd)
def _remove_conntrack_updated_firewall(self, agent_mode,
apply_list, pre_firewall, firewall):
"""Remove conntrack when updated firewall"""
routers_list = list(set([apply_info[0] for apply_info in apply_list]))
for router_info in routers_list:
ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix(
agent_mode, router_info)
for ipt_if_prefix in ipt_if_prefix_list:
ipt_mgr = ipt_if_prefix['ipt']
ch_rules = self._find_changed_rules(pre_firewall,
firewall)
i_rules = self._find_new_rules(pre_firewall, firewall)
r_rules = self._find_removed_rules(pre_firewall, firewall)
removed_conntrack_rules_list = ch_rules + i_rules + r_rules
for rule in removed_conntrack_rules_list:
cmd = self._get_conntrack_cmd_from_rule(ipt_mgr, rule)
self._remove_conntrack_by_cmd(cmd)
def _get_conntrack_filter_from_rule(self, rule):
"""Get conntrack filter from rule.
The key for get conntrack filter is protocol, destination_port
and source_port. If we want to take more keys, add to the list.
"""
conntrack_filter = []
keys = [['-p', 'protocol'], ['-f', 'ip_version'],
['--dport', 'destination_port'], ['--sport', 'source_port']]
for key in keys:
if rule.get(key[1]):
if key[1] == 'ip_version':
conntrack_filter.append(key[0])
conntrack_filter.append('ipv' + str(rule.get(key[1])))
else:
conntrack_filter.append(key[0])
conntrack_filter.append(rule.get(key[1]))
return conntrack_filter
def _remove_default_chains(self, nsid):
"""Remove fwaas default policy chain."""
self._remove_chain_by_name(IPV4, FWAAS_DEFAULT_CHAIN, nsid)
self._remove_chain_by_name(IPV6, FWAAS_DEFAULT_CHAIN, nsid)
def _remove_chains(self, fwid, ipt_mgr):
"""Remove fwaas policy chain."""
for ver in [IPV4, IPV6]:
for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
chain_name = self._get_chain_name(fwid, ver, direction)
self._remove_chain_by_name(ver, chain_name, ipt_mgr)
def _add_default_policy_chain_v4v6(self, ipt_mgr):
ipt_mgr.ipv4['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
ipt_mgr.ipv4['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
ipt_mgr.ipv6['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
ipt_mgr.ipv6['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
def _remove_chain_by_name(self, ver, chain_name, ipt_mgr):
if ver == IPV4:
ipt_mgr.ipv4['filter'].remove_chain(chain_name)
else:
ipt_mgr.ipv6['filter'].remove_chain(chain_name)
def _add_rules_to_chain(self, ipt_mgr, ver, chain_name, rules):
if ver == IPV4:
table = ipt_mgr.ipv4['filter']
else:
table = ipt_mgr.ipv6['filter']
for rule in rules:
table.add_rule(chain_name, rule)
def _enable_policy_chain(self, fwid, ipt_if_prefix, router_fw_ports):
bname = iptables_manager.binary_name
ipt_mgr = ipt_if_prefix['ipt']
if_prefix = ipt_if_prefix['if_prefix']
for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']),
(IPV6, ipt_mgr.ipv6['filter'])]:
for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
chain_name = self._get_chain_name(fwid, ver, direction)
chain_name = iptables_manager.get_chain_name(chain_name)
if chain_name in tbl.chains:
for router_fw_port in router_fw_ports:
intf_name = self._get_intf_name(if_prefix,
router_fw_port)
jump_rule = ['%s %s -j %s-%s' % (
IPTABLES_DIR[direction], intf_name,
bname, chain_name)]
self._add_rules_to_chain(ipt_mgr, ver,
'FORWARD', jump_rule)
# jump to DROP_ALL policy
chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
for router_fw_port in router_fw_ports:
intf_name = self._get_intf_name(if_prefix,
router_fw_port)
jump_rule = ['-o %s -j %s-%s' % (intf_name, bname, chain_name)]
self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
# jump to DROP_ALL policy
chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
for router_fw_port in router_fw_ports:
intf_name = self._get_intf_name(if_prefix,
router_fw_port)
jump_rule = ['-i %s -j %s-%s' % (intf_name, bname, chain_name)]
self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
def _convert_fwaas_to_iptables_rule(self, rule):
action = FWAAS_TO_IPTABLE_ACTION_MAP[rule.get('action')]
args = [self._protocol_arg(rule.get('protocol')),
self._port_arg('dport',
rule.get('protocol'),
rule.get('destination_port')),
self._port_arg('sport',
rule.get('protocol'),
rule.get('source_port')),
self._ip_prefix_arg('s', rule.get('source_ip_address')),
self._ip_prefix_arg('d', rule.get('destination_ip_address')),
self._action_arg(action)]
iptables_rule = ' '.join(args)
return iptables_rule
def _drop_invalid_packets_rule(self):
return '-m state --state INVALID -j DROP'
def _allow_established_rule(self):
return '-m state --state ESTABLISHED,RELATED -j ACCEPT'
def _action_arg(self, action):
if action:
return '-j %s' % action
return ''
def _protocol_arg(self, protocol):
if protocol:
return '-p %s' % protocol
return ''
def _port_arg(self, direction, protocol, port):
if not (protocol in ['udp', 'tcp'] and port):
return ''
return '--%s %s' % (direction, port)
def _ip_prefix_arg(self, direction, ip_prefix):
if ip_prefix:
return '-%s %s' % (direction, ip_prefix)
return ''

View File

@ -17,10 +17,10 @@ import uuid
import mock
from oslo_config import cfg
from neutron.agent.l3 import config as l3_config
from neutron.agent.l3 import ha
from neutron.agent.l3 import router_info
from neutron.agent.linux import ip_lib
from neutron.conf.agent.l3 import config as l3_config
from neutron.conf import common as base_config
from neutron import context
from neutron.plugins.common import constants
@ -50,7 +50,7 @@ def _setup_test_agent_class(service_plugins):
def __init__(self, conf):
self.event_observers = mock.Mock()
self.conf = conf
super(FWaasTestAgent, self).__init__('myhost', conf)
super(FWaasTestAgent, self).__init__("myhost", conf)
return FWaasTestAgent
@ -64,7 +64,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase):
self.conf.register_opts(l3_config.OPTS)
self.conf.register_opts(ha.OPTS)
self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas')
self.api = FWaasAgent('myhost', self.conf)
self.api = FWaasAgent("myhost", self.conf)
self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriver()
self.adminContext = context.get_admin_context()
self.router_id = str(uuid.uuid4())

View File

@ -0,0 +1,308 @@
# Copyright (c) 2016
# 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 uuid
import mock
from oslo_config import cfg
from neutron.agent.l3 import config as l3_config
from neutron.agent.l3 import router_info
from neutron.agent.linux import ip_lib
from neutron import context
from neutron.plugins.common import constants
from neutron_fwaas.services.firewall.agents import firewall_agent_api
from neutron_fwaas.services.firewall.agents.l3reference \
import firewall_l3_agent_v2
from neutron_fwaas.tests import base
from neutron_fwaas.tests.unit.services.firewall.agents \
import test_firewall_agent_api
class FWaasHelper(object):
def __init__(self, host):
pass
class FWaasAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback, FWaasHelper):
neutron_service_plugins = []
def _setup_test_agent_class(service_plugins):
class FWaasTestAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback,
FWaasHelper):
neutron_service_plugins = service_plugins
def __init__(self, conf):
self.event_observers = mock.Mock()
self.conf = conf
super(FWaasTestAgent, self).__init__('myhost', conf)
return FWaasTestAgent
class TestFwaasL3AgentRpcCallback(base.BaseTestCase):
def setUp(self):
super(TestFwaasL3AgentRpcCallback, self).setUp()
self.conf = cfg.ConfigOpts()
self.conf.register_opts(l3_config.OPTS)
self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas')
self.api = FWaasAgent('myhost', self.conf)
self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriverV2()
self.adminContext = context.get_admin_context()
self.context = mock.sentinel.context
self.router_id = str(uuid.uuid4())
self.agent_conf = mock.Mock()
self.ri_kwargs = {'router': {'id': self.router_id,
'project_id': str(uuid.uuid4())},
'agent_conf': self.agent_conf,
'interface_driver': mock.ANY,
'use_ipv6': mock.ANY
}
def test_fw_config_match(self):
test_agent_class = _setup_test_agent_class([constants.FIREWALL])
cfg.CONF.set_override('enabled', True, 'fwaas')
with mock.patch('oslo_utils.importutils.import_object'):
test_agent_class(cfg.CONF)
def test_fw_config_mismatch_plugin_enabled_agent_disabled(self):
self.skipTest('this is broken')
test_agent_class = _setup_test_agent_class([constants.FIREWALL])
cfg.CONF.set_override('enabled', False, 'fwaas')
self.assertRaises(SystemExit, test_agent_class, cfg.CONF)
def test_fw_plugin_list_unavailable(self):
test_agent_class = _setup_test_agent_class(None)
cfg.CONF.set_override('enabled', False, 'fwaas')
with mock.patch('oslo_utils.importutils.import_object'):
test_agent_class(cfg.CONF)
def test_create_firewall_group(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'add-port-ids': [1, 2]}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api, '_get_firewall_group_ports'
) as mock_get_firewall_group_ports, \
mock.patch.object(self.api, '_get_in_ns_ports'
) as mock_get_in_ns_ports, \
mock.patch.object(self.api.fwaas_driver,
'create_firewall_group'
) as mock_driver_create_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'set_firewall_group_status'
) as mock_set_firewall_group_status:
mock_driver_create_firewall_group.return_value = True
self.api.create_firewall_group(self.context, firewall_group,
host='host')
mock_get_firewall_group_ports.assert_called_once_with(self.context,
firewall_group)
mock_get_in_ns_ports.assert_called
assert mock_get_in_ns_ports
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'ACTIVE')
def test_update_firewall_group_with_ports_added_and_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'add-port-ids': [1, 2],
'del-port-ids': [3, 4],
'router_ids': [],
'last-port': False}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api, '_get_firewall_group_ports'
) as mock_get_firewall_group_ports, \
mock.patch.object(self.api, '_get_in_ns_ports'
) as mock_get_in_ns_ports, \
mock.patch.object(self.api.fwaas_driver,
'update_firewall_group'
) as mock_driver_update_firewall_group, \
mock.patch.object(self.api.fwaas_driver,
'delete_firewall_group'
) as mock_driver_delete_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'set_firewall_group_status'
) as mock_set_firewall_group_status:
mock_driver_delete_firewall_group.return_value = True
mock_driver_update_firewall_group.return_value = True
calls = [mock.call(self.context, firewall_group, to_delete=True,
require_new_plugin=True),
mock.call(self.context, firewall_group)]
self.api.update_firewall_group(self.context, firewall_group,
host='host')
self.assertEqual(mock_get_firewall_group_ports.call_args_list,
calls)
mock_get_in_ns_ports.assert_called
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'ACTIVE')
def test_update_firewall_group_with_ports_added_and_admin_state_down(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': False,
'add-port-ids': [1, 2],
'del-port-ids': [],
'router_ids': [],
'last-port': False}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api, '_get_firewall_group_ports'
) as mock_get_firewall_group_ports, \
mock.patch.object(self.api, '_get_in_ns_ports'
) as mock_get_in_ns_ports, \
mock.patch.object(self.api.fwaas_driver,
'update_firewall_group'
) as mock_driver_update_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'set_firewall_group_status'
) as mock_set_firewall_group_status:
mock_driver_update_firewall_group.return_value = True
self.api.update_firewall_group(self.context, firewall_group,
host='host')
mock_get_firewall_group_ports.assert_called
mock_get_in_ns_ports.assert_called
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'DOWN')
def test_update_firewall_group_with_all_ports_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'add-port-ids': [],
'del-port-ids': [3, 4],
'last-port': True}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api, '_get_firewall_group_ports'
) as mock_get_firewall_group_ports, \
mock.patch.object(self.api, '_get_in_ns_ports'
) as mock_get_in_ns_ports, \
mock.patch.object(self.api.fwaas_driver,
'delete_firewall_group'
) as mock_driver_delete_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'set_firewall_group_status'
) as mock_set_firewall_group_status:
mock_driver_delete_firewall_group.return_value = True
self.api.update_firewall_group(self.context, firewall_group,
host='host')
mock_get_firewall_group_ports.assert_called_once_with(self.context,
firewall_group, require_new_plugin=True, to_delete=True)
mock_get_in_ns_ports.assert_called
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'INACTIVE')
def test_update_firewall_group_with_no_ports_added_or_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'add-port-ids': [],
'del-port-ids': [],
'router_ids': []}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api.fwaas_driver, 'update_firewall_group'
) as mock_driver_update_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'set_firewall_group_status'
) as mock_set_firewall_group_status:
mock_driver_update_firewall_group.return_value = True
self.api.update_firewall_group(self.context, firewall_group,
host='host')
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'INACTIVE')
def test_delete_firewall_group(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'add-port-ids': [],
'del-port-ids': [3, 4],
'last-port': False}
self.api.plugin_rpc = mock.Mock()
with mock.patch.object(self.api, '_get_firewall_group_ports'
) as mock_get_firewall_group_ports, \
mock.patch.object(self.api, '_get_in_ns_ports'
) as mock_get_in_ns_ports, \
mock.patch.object(self.api.fwaas_driver,
'delete_firewall_group'
) as mock_driver_delete_firewall_group, \
mock.patch.object(self.api.fwplugin_rpc,
'firewall_group_deleted'
) as mock_firewall_group_deleted:
mock_driver_delete_firewall_group.return_value = True
self.api.delete_firewall_group(self.context, firewall_group,
host='host')
mock_get_firewall_group_ports.assert_called_once_with(
self.context, firewall_group, to_delete=True)
mock_get_in_ns_ports.assert_called
mock_firewall_group_deleted.assert_called_once_with(self.context,
firewall_group['id'])
def _prepare_router_data(self):
return router_info.RouterInfo(self.router_id,
**self.ri_kwargs)
def test_get_in_ns_ports_for_non_ns_fw(self):
port_ids = [1, 2]
ports = [{'id': pid} for pid in port_ids]
ri = self._prepare_router_data()
ri.internal_ports = ports
self.api.router_info = {ri.router_id: ri}
fw_port_ids = port_ids
with mock.patch.object(ip_lib.IPWrapper,
'get_namespaces') as mock_get_namespaces:
mock_get_namespaces.return_value = []
ports_for_fw_list = self.api._get_in_ns_ports(fw_port_ids)
mock_get_namespaces.assert_called_once_with()
self.assertFalse(ports_for_fw_list)
def test_get_in_ns_ports_for_fw(self):
port_ids = [1, 2]
ports = [{'id': pid} for pid in port_ids]
ri = self._prepare_router_data()
ri.internal_ports = ports
self.api.router_info = {}
self.api.router_info[ri.router_id] = ri
fw_port_ids = port_ids
ports_for_fw_expected = [(ri, port_ids)]
with mock.patch.object(ip_lib.IPWrapper,
'get_namespaces') as mock_get_namespaces:
mock_get_namespaces.return_value = [ri.ns_name]
ports_for_fw_actual = self.api._get_in_ns_ports(fw_port_ids)
self.assertEqual(ports_for_fw_expected, ports_for_fw_actual)

View File

@ -16,14 +16,15 @@
import mock
from neutron_fwaas.services.firewall.agents import firewall_agent_api as api
from neutron_fwaas.services.firewall.drivers import fwaas_base as base_driver
from neutron_fwaas.services.firewall.drivers import fwaas_base
from neutron_fwaas.services.firewall.drivers import fwaas_base_v2
from neutron_fwaas.tests import base
class NoopFwaasDriver(base_driver.FwaasDriverBase):
class NoopFwaasDriver(fwaas_base.FwaasDriverBase):
"""Noop Fwaas Driver.
Firewall driver which does nothing.
v1 firewall driver which does nothing.
This driver is for disabling Fwaas functionality.
"""
@ -40,6 +41,26 @@ class NoopFwaasDriver(base_driver.FwaasDriverBase):
pass
class NoopFwaasDriverV2(fwaas_base_v2.FwaasDriverBase):
"""Noop Fwaas Driver.
v2 firewall driver which does nothing.
This driver is for disabling Fwaas functionality.
"""
def create_firewall_group(self, agent_mode, apply_list, firewall):
pass
def delete_firewall_group(self, agent_mode, apply_list, firewall):
pass
def update_firewall_group(self, agent_mode, apply_list, firewall):
pass
def apply_default_policy(self, agent_mode, apply_list, firewall):
pass
class TestFWaaSAgentApi(base.BaseTestCase):
def setUp(self):
super(TestFWaaSAgentApi, self).setUp()

View File

@ -16,12 +16,12 @@
import mock
from neutron.agent.common import config as agent_config
from neutron.agent.l3 import config as l3_config
from neutron.agent.l3 import ha
from neutron.agent.l3 import router_info
from neutron.agent.linux import interface
from neutron.conf.agent.l3 import config as l3_config
from neutron.conf import common as base_config
from neutron_fwaas.services.firewall.agents.varmour import varmour_router
from neutron_fwaas.tests import base
from neutron_lib import constants as l3_constants
@ -39,7 +39,6 @@ class TestVarmourRouter(base.BaseTestCase):
self.skipTest('this is broken')
super(TestVarmourRouter, self).setUp()
self.conf = agent_config.setup_conf()
self.conf.register_opts(base_config.core_opts)
self.conf.register_opts(l3_config.OPTS)
self.conf.register_opts(ha.OPTS)
agent_config.register_process_monitor_opts(self.conf)

View File

@ -0,0 +1,386 @@
# Copyright (c) 2016
# 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 copy
import mock
from oslo_config import cfg
from neutron.agent.common import config as a_cfg
from neutron.tests import base
from neutron.tests.unit.api.v2 import test_base as test_api_v2
import neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas_v2 as fwaas
_uuid = test_api_v2._uuid
FAKE_SRC_PREFIX = '10.0.0.0/24'
FAKE_DST_PREFIX = '20.0.0.0/24'
FAKE_PROTOCOL = 'tcp'
FAKE_SRC_PORT = 5000
FAKE_DST_PORT = 22
FAKE_FW_ID = 'fake-fw-uuid'
FAKE_PORT_IDS = ('1_fake-port-uuid', '2_fake-port-uuid')
FW_LEGACY = 'legacy'
MAX_INTF_NAME_LEN = 14
class IptablesFwaasTestCase(base.BaseTestCase):
def setUp(self):
super(IptablesFwaasTestCase, self).setUp()
cfg.CONF.register_opts(a_cfg.ROOT_HELPER_OPTS, 'AGENT')
self.utils_exec_p = mock.patch(
'neutron.agent.linux.utils.execute')
self.utils_exec = self.utils_exec_p.start()
self.iptables_cls_p = mock.patch(
'neutron.agent.linux.iptables_manager.IptablesManager')
self.iptables_cls_p.start()
self.firewall = fwaas.IptablesFwaasDriver()
def _fake_rules_v4(self, fwid, apply_list):
rule_list = []
rule1 = {'enabled': True,
'action': 'allow',
'ip_version': 4,
'protocol': 'tcp',
'destination_port': '80',
'source_ip_address': '10.24.4.2',
'id': 'fake-fw-rule1'}
rule2 = {'enabled': True,
'action': 'deny',
'ip_version': 4,
'protocol': 'tcp',
'destination_port': '22',
'id': 'fake-fw-rule2'}
rule3 = {'enabled': True,
'action': 'reject',
'ip_version': 4,
'protocol': 'tcp',
'destination_port': '23',
'id': 'fake-fw-rule3'}
ingress_chain = ('iv4%s' % fwid)[:11]
egress_chain = ('ov4%s' % fwid)[:11]
for router_info_inst, port_ids in apply_list:
v4filter_inst = router_info_inst.iptables_manager.ipv4['filter']
v4filter_inst.chains.append(ingress_chain)
v4filter_inst.chains.append(egress_chain)
rule_list.append(rule1)
rule_list.append(rule2)
rule_list.append(rule3)
return rule_list
def _fake_firewall_no_rule(self):
rule_list = []
fw_inst = {'id': FAKE_FW_ID,
'admin_state_up': True,
'tenant_id': 'tenant-uuid',
'egress_rule_list': rule_list,
'ingress_rule_list': rule_list}
return fw_inst
def _fake_firewall(self, rule_list):
_rule_list = copy.deepcopy(rule_list)
for rule in _rule_list:
rule['position'] = str(_rule_list.index(rule))
fw_inst = {'id': FAKE_FW_ID,
'admin_state_up': True,
'tenant_id': 'tenant-uuid',
'egress_rule_list': _rule_list,
'ingress_rule_list': _rule_list}
return fw_inst
def _fake_firewall_with_admin_down(self, rule_list):
fw_inst = {'id': FAKE_FW_ID,
'admin_state_up': False,
'tenant_id': 'tenant-uuid',
'egress_rule_list': rule_list,
'ingress_rule_list': rule_list}
return fw_inst
def _fake_apply_list(self, router_count=1, distributed=False,
distributed_mode=None):
apply_list = []
while router_count > 0:
iptables_inst = mock.Mock()
if distributed:
router_inst = {'distributed': distributed}
else:
router_inst = {}
v4filter_inst = mock.Mock()
v6filter_inst = mock.Mock()
v4filter_inst.chains = []
v6filter_inst.chains = []
iptables_inst.ipv4 = {'filter': v4filter_inst}
iptables_inst.ipv6 = {'filter': v6filter_inst}
router_info_inst = mock.Mock()
router_info_inst.iptables_manager = iptables_inst
router_info_inst.snat_iptables_manager = iptables_inst
if distributed_mode == 'dvr':
router_info_inst.dist_fip_count = 1
router_info_inst.router = router_inst
apply_list.append((router_info_inst, FAKE_PORT_IDS))
router_count -= 1
return apply_list
def _get_intf_name(self, if_prefix, port_id):
_name = "%s%s" % (if_prefix, port_id)
return _name[:MAX_INTF_NAME_LEN]
def _setup_firewall_with_rules(self, func, router_count=1,
distributed=False, distributed_mode=None):
apply_list = self._fake_apply_list(router_count=router_count,
distributed=distributed, distributed_mode=distributed_mode)
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
if distributed:
if distributed_mode == 'dvr_snat':
if_prefix = 'sg-'
if distributed_mode == 'dvr':
if_prefix = 'rfp-'
else:
if_prefix = 'qr-'
distributed_mode = 'legacy'
func(distributed_mode, apply_list, firewall)
invalid_rule = '-m state --state INVALID -j DROP'
est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
rule1 = '-p tcp --dport 80 -s 10.24.4.2 -j ACCEPT'
rule2 = '-p tcp --dport 22 -j DROP'
rule3 = '-p tcp --dport 23 -j REJECT'
ingress_chain = 'iv4%s' % firewall['id']
egress_chain = 'ov4%s' % firewall['id']
bname = fwaas.iptables_manager.binary_name
ipt_mgr_ichain = '%s-%s' % (bname, ingress_chain[:11])
ipt_mgr_echain = '%s-%s' % (bname, egress_chain[:11])
for router_info_inst, port_ids in apply_list:
v4filter_inst = router_info_inst.iptables_manager.ipv4['filter']
calls = [mock.call.remove_chain('iv4fake-fw-uuid'),
mock.call.remove_chain('ov4fake-fw-uuid'),
mock.call.remove_chain('fwaas-default-policy'),
mock.call.add_chain('fwaas-default-policy'),
mock.call.add_rule('fwaas-default-policy', '-j DROP'),
mock.call.add_chain(ingress_chain),
mock.call.add_rule(ingress_chain, invalid_rule),
mock.call.add_rule(ingress_chain, est_rule),
mock.call.add_chain(egress_chain),
mock.call.add_rule(egress_chain, invalid_rule),
mock.call.add_rule(egress_chain, est_rule),
mock.call.add_rule(ingress_chain, rule1),
mock.call.add_rule(ingress_chain, rule2),
mock.call.add_rule(ingress_chain, rule3),
mock.call.add_rule(egress_chain, rule1),
mock.call.add_rule(egress_chain, rule2),
mock.call.add_rule(egress_chain, rule3)]
intf_name = self._get_intf_name(if_prefix, FAKE_PORT_IDS[-1])
calls.append(mock.call.add_rule('FORWARD',
'-o %s -j %s' % (intf_name, ipt_mgr_ichain)))
calls.append(mock.call.add_rule('FORWARD',
'-i %s -j %s' % (intf_name, ipt_mgr_echain)))
for direction in ['o', 'i']:
for port_id in FAKE_PORT_IDS:
intf_name = self._get_intf_name(if_prefix, port_id)
calls.append(mock.call.add_rule('FORWARD',
'-%s %s -j %s-fwaas-defau' % (direction,
intf_name, bname)))
v4filter_inst.assert_has_calls(calls)
def test_create_firewall_group_no_rules(self):
apply_list = self._fake_apply_list()
first_ri = apply_list[0][0]
firewall = self._fake_firewall_no_rule()
self.firewall.create_firewall_group('legacy', apply_list, firewall)
invalid_rule = '-m state --state INVALID -j DROP'
est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
bname = fwaas.iptables_manager.binary_name
for ip_version in (4, 6):
ingress_chain = ('iv%s%s' % (ip_version, firewall['id']))
egress_chain = ('ov%s%s' % (ip_version, firewall['id']))
calls = [mock.call.remove_chain(
'iv%sfake-fw-uuid' % ip_version),
mock.call.remove_chain(
'ov%sfake-fw-uuid' % ip_version),
mock.call.remove_chain('fwaas-default-policy'),
mock.call.add_chain('fwaas-default-policy'),
mock.call.add_rule('fwaas-default-policy', '-j DROP'),
mock.call.add_chain(ingress_chain),
mock.call.add_rule(ingress_chain, invalid_rule),
mock.call.add_rule(ingress_chain, est_rule),
mock.call.add_chain(egress_chain),
mock.call.add_rule(egress_chain, invalid_rule),
mock.call.add_rule(egress_chain, est_rule)]
for port_id in FAKE_PORT_IDS:
for direction in ['o', 'i']:
mock.call.add_rule('FORWARD',
'-%s qr-%s -j %s-fwaas-defau' % (port_id,
direction,
bname))
if ip_version == 4:
v4filter_inst = first_ri.iptables_manager.ipv4['filter']
v4filter_inst.assert_has_calls(calls)
else:
v6filter_inst = first_ri.iptables_manager.ipv6['filter']
v6filter_inst.assert_has_calls(calls)
def test_create_firewall_group_with_rules(self):
self._setup_firewall_with_rules(self.firewall.create_firewall_group)
def test_create_firewall_group_with_rules_without_distributed_attr(self):
self._setup_firewall_with_rules(self.firewall.create_firewall_group,
distributed=None)
def test_create_firewall_group_with_rules_two_routers(self):
self._setup_firewall_with_rules(self.firewall.create_firewall_group,
router_count=2)
def test_update_firewall_group_with_rules(self):
self._setup_firewall_with_rules(self.firewall.update_firewall_group)
def test_update_firewall_group_with_rules_without_distributed_attr(self):
self._setup_firewall_with_rules(self.firewall.update_firewall_group,
distributed=None)
def _test_delete_firewall_group(self, distributed=False):
apply_list = self._fake_apply_list(distributed=distributed)
first_ri = apply_list[0][0]
firewall = self._fake_firewall_no_rule()
self.firewall.delete_firewall_group('legacy', apply_list, firewall)
ingress_chain = 'iv4%s' % firewall['id']
egress_chain = 'ov4%s' % firewall['id']
calls = [mock.call.remove_chain(ingress_chain),
mock.call.remove_chain(egress_chain),
mock.call.remove_chain('fwaas-default-policy')]
first_ri.iptables_manager.ipv4['filter'].assert_has_calls(calls)
def test_delete_firewall_group(self):
self._test_delete_firewall_group()
def test_delete_firewall_group_without_distributed_attr(self):
self._test_delete_firewall_group(distributed=None)
def test_create_firewall_group_with_admin_down(self):
apply_list = self._fake_apply_list()
first_ri = apply_list[0][0]
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall_with_admin_down(rule_list)
self.firewall.create_firewall_group('legacy', apply_list, firewall)
calls = [mock.call.remove_chain('iv4fake-fw-uuid'),
mock.call.remove_chain('ov4fake-fw-uuid'),
mock.call.remove_chain('fwaas-default-policy'),
mock.call.add_chain('fwaas-default-policy'),
mock.call.add_rule('fwaas-default-policy', '-j DROP')]
first_ri.iptables_manager.ipv4['filter'].assert_has_calls(calls)
def test_create_firewall_group_with_rules_dvr_snat(self):
self._setup_firewall_with_rules(self.firewall.create_firewall_group,
distributed=True, distributed_mode='dvr_snat')
def test_update_firewall_group_with_rules_dvr_snat(self):
self._setup_firewall_with_rules(self.firewall.update_firewall_group,
distributed=True, distributed_mode='dvr_snat')
def test_create_firewall_group_with_rules_dvr(self):
self._setup_firewall_with_rules(self.firewall.create_firewall_group,
distributed=True, distributed_mode='dvr')
def test_update_firewall_group_with_rules_dvr(self):
self._setup_firewall_with_rules(self.firewall.update_firewall_group,
distributed=True, distributed_mode='dvr')
def test_remove_conntrack_new_firewall(self):
apply_list = self._fake_apply_list()
firewall = self._fake_firewall_no_rule()
self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall)
for router_info_inst, port_ids in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D']
calls = [
mock.call(cmd, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
def test_remove_conntrack_inserted_rule(self):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall)
self.firewall.pre_firewall = dict(firewall)
insert_rule = {'enabled': True,
'action': 'deny',
'ip_version': 4,
'protocol': 'icmp',
'id': 'fake-fw-rule'}
rule_list.insert(2, insert_rule)
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall)
for router_info_inst, port_ids in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'icmp', '-f', 'ipv4']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
def test_remove_conntrack_removed_rule(self):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall)
self.firewall.pre_firewall = dict(firewall)
remove_rule = rule_list[1]
rule_list.remove(remove_rule)
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall)
for router_info_inst, port_ids in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack',
'-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '22']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)
def test_remove_conntrack_changed_rule(self):
apply_list = self._fake_apply_list()
rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list)
firewall = self._fake_firewall(rule_list)
self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall)
income_rule = {'enabled': True,
'action': 'deny',
'ip_version': 4,
'protocol': 'icmp',
'id': 'fake-fw-rule2'}
rule_list[1] = income_rule
firewall = self._fake_firewall(rule_list)
self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall)
for router_info_inst, port_ids in apply_list:
namespace = router_info_inst.iptables_manager.namespace
cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D',
'-p', 'tcp', '-f', 'ipv4', '--dport', '22']
cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D',
'-p', 'icmp', '-f', 'ipv4']
calls = [
mock.call(cmd1, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1]),
mock.call(cmd2, run_as_root=True, check_exit_code=True,
extra_ok_codes=[1])]
self.utils_exec.assert_has_calls(calls)

View File

@ -17,11 +17,10 @@
import mock
from neutron.agent.common import config as agent_config
from neutron.agent.l3 import config as l3_config
from neutron.agent.l3 import ha
from neutron.agent.l3 import router_info
from neutron.agent.linux import interface
from neutron.conf.agent.l3 import config as l3_config
from neutron.conf import common as base_config
from neutron.tests import base
from neutron_lib import constants as l3_constants
from oslo_utils import uuidutils
@ -39,7 +38,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
def setUp(self):
super(TestBasicRouterOperations, self).setUp()
self.conf = agent_config.setup_conf()
self.conf.register_opts(base_config.core_opts)
self.conf.register_opts(l3_config.OPTS)
self.conf.register_opts(ha.OPTS)
agent_config.register_process_monitor_opts(self.conf)