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:
parent
151946dcb1
commit
fd8d6d27f0
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 ''
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue