Change-Id: I795afc55b72306507ec6717838150fc2fed7a15dchanges/11/104811/1
parent
b5607354c0
commit
872cab4426
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,85 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright (c) 2013 OpenStack Foundation
|
||||
# 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||
# @author: Sridar Kandaswamy, skandasw@cisco.com, Cisco Systems, Inc.
|
||||
# @author: Dan Florea, dflorea@cisco.com, Cisco Systems, Inc.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import rpc_compat
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
FWaaSOpts = [
|
||||
cfg.StrOpt(
|
||||
'driver',
|
||||
default='',
|
||||
help=_("Name of the FWaaS Driver")),
|
||||
cfg.BoolOpt(
|
||||
'enabled',
|
||||
default=False,
|
||||
help=_("Enable FWaaS")),
|
||||
]
|
||||
cfg.CONF.register_opts(FWaaSOpts, 'fwaas')
|
||||
|
||||
|
||||
class FWaaSPluginApiMixin(rpc_compat.RpcProxy):
|
||||
"""Agent side of the FWaaS agent to FWaaS Plugin RPC API."""
|
||||
|
||||
RPC_API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, topic, host):
|
||||
super(FWaaSPluginApiMixin,
|
||||
self).__init__(topic=topic,
|
||||
default_version=self.RPC_API_VERSION)
|
||||
self.host = host
|
||||
|
||||
def set_firewall_status(self, context, firewall_id, status):
|
||||
"""Make a RPC to set the status of a firewall."""
|
||||
return self.call(context,
|
||||
self.make_msg('set_firewall_status', host=self.host,
|
||||
firewall_id=firewall_id, status=status),
|
||||
topic=self.topic)
|
||||
|
||||
def firewall_deleted(self, context, firewall_id):
|
||||
"""Make a RPC to indicate that the firewall resources are deleted."""
|
||||
return self.call(context,
|
||||
self.make_msg('firewall_deleted', host=self.host,
|
||||
firewall_id=firewall_id),
|
||||
topic=self.topic)
|
||||
|
||||
|
||||
class FWaaSAgentRpcCallbackMixin(object):
|
||||
"""Mixin for FWaaS agent Implementations."""
|
||||
|
||||
def __init__(self, host):
|
||||
|
||||
super(FWaaSAgentRpcCallbackMixin, self).__init__(host)
|
||||
|
||||
def create_firewall(self, context, firewall, host):
|
||||
"""Handle RPC cast from plugin to create a firewall."""
|
||||
pass
|
||||
|
||||
def update_firewall(self, context, firewall, host):
|
||||
"""Handle RPC cast from plugin to update a firewall."""
|
||||
pass
|
||||
|
||||
def delete_firewall(self, context, firewall, host):
|
||||
"""Handle RPC cast from plugin to delete a firewall."""
|
||||
pass
|
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,295 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
# 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||
# @author: Sridar Kandaswamy, skandasw@cisco.com, Cisco Systems, Inc.
|
||||
# @author: Dan Florea, dflorea@cisco.com, Cisco Systems, Inc.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import topics
|
||||
from neutron import context
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.openstack.common import importutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.firewall.agents import firewall_agent_api as api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FWaaSL3PluginApi(api.FWaaSPluginApiMixin):
|
||||
"""Agent side of the FWaaS agent to FWaaS Plugin RPC API."""
|
||||
|
||||
def __init__(self, topic, host):
|
||||
super(FWaaSL3PluginApi, self).__init__(topic, host)
|
||||
|
||||
def get_firewalls_for_tenant(self, context, **kwargs):
|
||||
"""Get the Firewalls with rules from the Plugin to send to driver."""
|
||||
LOG.debug(_("Retrieve Firewall with rules from Plugin"))
|
||||
|
||||
return self.call(context,
|
||||
self.make_msg('get_firewalls_for_tenant',
|
||||
host=self.host),
|
||||
topic=self.topic)
|
||||
|
||||
def get_tenants_with_firewalls(self, context, **kwargs):
|
||||
"""Get all Tenants that have Firewalls configured from plugin."""
|
||||
LOG.debug(_("Retrieve Tenants with Firewalls configured from Plugin"))
|
||||
|
||||
return self.call(context,
|
||||
self.make_msg('get_tenants_with_firewalls',
|
||||
host=self.host),
|
||||
topic=self.topic)
|
||||
|
||||
|
||||
class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin):
|
||||
"""FWaaS Agent support to be used by Neutron L3 agent."""
|
||||
|
||||
def __init__(self, conf):
|
||||
LOG.debug(_("Initializing firewall agent"))
|
||||
self.conf = conf
|
||||
fwaas_driver_class_path = cfg.CONF.fwaas.driver
|
||||
self.fwaas_enabled = cfg.CONF.fwaas.enabled
|
||||
if self.fwaas_enabled:
|
||||
try:
|
||||
self.fwaas_driver = importutils.import_object(
|
||||
fwaas_driver_class_path)
|
||||
LOG.debug(_("FWaaS Driver Loaded: '%s'"),
|
||||
fwaas_driver_class_path)
|
||||
except ImportError:
|
||||
msg = _('Error importing FWaaS device driver: %s')
|
||||
raise ImportError(msg % fwaas_driver_class_path)
|
||||
self.services_sync = False
|
||||
self.root_helper = config.get_root_helper(conf)
|
||||
# setup RPC to msg fwaas plugin
|
||||
self.fwplugin_rpc = FWaaSL3PluginApi(topics.FIREWALL_PLUGIN,
|
||||
conf.host)
|
||||
super(FWaaSL3AgentRpcCallback, self).__init__(host=conf.host)
|
||||
|
||||
def _get_router_info_list_for_tenant(self, routers, tenant_id):
|
||||
"""Returns the list of router info objects on which to apply the fw."""
|
||||
root_ip = ip_lib.IPWrapper(self.root_helper)
|
||||
# Get the routers for the tenant
|
||||
router_ids = [
|
||||
router['id']
|
||||
for router in routers
|
||||
if router['tenant_id'] == tenant_id]
|
||||
local_ns_list = root_ip.get_namespaces(
|
||||
self.root_helper) if self.conf.use_namespaces else []
|
||||
|
||||
router_info_list = []
|
||||
# Pick up namespaces for Tenant Routers
|
||||
for rid in router_ids:
|
||||
# for routers without an interface - get_routers returns
|
||||
# the router - but this is not yet populated in router_info
|
||||
if rid not in self.router_info:
|
||||
continue
|
||||
if self.router_info[rid].use_namespaces:
|
||||
router_ns = self.router_info[rid].ns_name
|
||||
if router_ns in local_ns_list:
|
||||
router_info_list.append(self.router_info[rid])
|
||||
else:
|
||||
router_info_list.append(self.router_info[rid])
|
||||
return router_info_list
|
||||
|
||||
def _invoke_driver_for_plugin_api(self, context, fw, func_name):
|
||||
"""Invoke driver method for plugin API and provide status back."""
|
||||
LOG.debug(_("%(func_name)s from agent for fw: %(fwid)s"),
|
||||
{'func_name': func_name, 'fwid': fw['id']})
|
||||
try:
|
||||
routers = self.plugin_rpc.get_routers(context)
|
||||
router_info_list = self._get_router_info_list_for_tenant(
|
||||
routers,
|
||||
fw['tenant_id'])
|
||||
if not router_info_list:
|
||||
LOG.debug(_('No Routers on tenant: %s'), fw['tenant_id'])
|
||||
# fw was created before any routers were added, and if a
|
||||
# delete is sent then we need to ack so that plugin can
|
||||
# cleanup.
|
||||
if func_name == 'delete_firewall':
|
||||
self.fwplugin_rpc.firewall_deleted(context, fw['id'])
|
||||
return
|
||||
LOG.debug(_("Apply fw on Router List: '%s'"),
|
||||
[ri.router['id'] for ri in router_info_list])
|
||||
# call into the driver
|
||||
try:
|
||||
self.fwaas_driver.__getattribute__(func_name)(
|
||||
router_info_list,
|
||||
fw)
|
||||
if fw['admin_state_up']:
|
||||
status = constants.ACTIVE
|
||||
else:
|
||||
status = constants.DOWN
|
||||
except fw_ext.FirewallInternalDriverError:
|
||||
LOG.error(_("Firewall Driver Error for %(func_name)s "
|
||||
"for fw: %(fwid)s"),
|
||||
{'func_name': func_name, 'fwid': fw['id']})
|
||||
status = constants.ERROR
|
||||
# delete needs different handling
|
||||
if func_name == 'delete_firewall':
|
||||
if status in [constants.ACTIVE, constants.DOWN]:
|
||||
self.fwplugin_rpc.firewall_deleted(context, fw['id'])
|
||||
else:
|
||||
self.fwplugin_rpc.set_firewall_status(
|
||||
context,
|
||||
fw['id'],
|
||||
status)
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
_("FWaaS RPC failure in %(func_name)s for fw: %(fwid)s"),
|
||||
{'func_name': func_name, 'fwid': fw['id']})
|
||||
self.services_sync = True
|
||||
return
|
||||
|
||||
def _invoke_driver_for_sync_from_plugin(self, ctx, router_info_list, fw):
|
||||
"""Invoke the delete driver method for status of PENDING_DELETE and
|
||||
update method for all other status to (re)apply on driver which is
|
||||
Idempotent.
|
||||
"""
|
||||
if fw['status'] == constants.PENDING_DELETE:
|
||||
try:
|
||||
self.fwaas_driver.delete_firewall(router_info_list, fw)
|
||||
self.fwplugin_rpc.firewall_deleted(
|
||||
ctx,
|
||||
fw['id'])
|
||||
except fw_ext.FirewallInternalDriverError:
|
||||
LOG.error(_("Firewall Driver Error on fw state %(fwmsg)s "
|
||||
"for fw: %(fwid)s"),
|
||||
{'fwmsg': fw['status'], 'fwid': fw['id']})
|
||||
self.fwplugin_rpc.set_firewall_status(
|
||||
ctx,
|
||||
fw['id'],
|
||||
constants.ERROR)
|
||||
else:
|
||||
# PENDING_UPDATE, PENDING_CREATE, ...
|
||||
try:
|
||||
self.fwaas_driver.update_firewall(router_info_list, fw)
|
||||
if fw['admin_state_up']:
|
||||
status = constants.ACTIVE
|
||||
else:
|
||||
status = constants.DOWN
|
||||
except fw_ext.FirewallInternalDriverError:
|
||||
LOG.error(_("Firewall Driver Error on fw state %(fwmsg)s "
|
||||
"for fw: %(fwid)s"),
|
||||
{'fwmsg': fw['status'], 'fwid': fw['id']})
|
||||
status = constants.ERROR
|
||||
|
||||
self.fwplugin_rpc.set_firewall_status(
|
||||
ctx,
|
||||
fw['id'],
|
||||
status)
|
||||
|
||||
def _process_router_add(self, ri):
|
||||
"""On router add, get fw with rules from plugin and update driver."""
|
||||
LOG.debug(_("Process router add, router_id: '%s'"), ri.router['id'])
|
||||
routers = []
|
||||
routers.append(ri.router)
|
||||
router_info_list = self._get_router_info_list_for_tenant(
|
||||
routers,
|
||||
ri.router['tenant_id'])
|
||||
if router_info_list:
|
||||
# Get the firewall with rules
|
||||
# for the tenant the router is on.
|
||||
ctx = context.Context('', ri.router['tenant_id'])
|
||||
fw_list = self.fwplugin_rpc.get_firewalls_for_tenant(ctx)
|
||||
LOG.debug(_("Process router add, fw_list: '%s'"),
|
||||
[fw['id'] for fw in fw_list])
|
||||
for fw in fw_list:
|
||||
self._invoke_driver_for_sync_from_plugin(
|
||||
ctx,
|
||||
router_info_list,
|
||||
fw)
|
||||
|
||||
def process_router_add(self, ri):
|
||||
"""On router add, get fw with rules from plugin and update driver."""
|
||||
# avoid msg to plugin when fwaas is not configured
|
||||
if not self.fwaas_enabled:
|
||||
return
|
||||
try:
|
||||
self._process_router_add(ri)
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
_("FWaaS RPC info call failed for '%s'."),
|
||||
ri.router['id'])
|
||||
self.services_sync = True
|
||||
|
||||
def process_services_sync(self, ctx):
|
||||
"""On RPC issues sync with plugin and apply the sync data."""
|
||||
# avoid msg to plugin when fwaas is not configured
|
||||
if not self.fwaas_enabled:
|
||||
return
|
||||
try:
|
||||
# get all routers
|
||||
routers = self.plugin_rpc.get_routers(ctx)
|
||||
# get the list of tenants with firewalls configured
|
||||
# from the plugin
|
||||
tenant_ids = self.fwplugin_rpc.get_tenants_with_firewalls(ctx)
|
||||
LOG.debug(_("Tenants with Firewalls: '%s'"), tenant_ids)
|
||||
for tenant_id in tenant_ids:
|
||||
ctx = context.Context('', tenant_id)
|
||||
fw_list = self.fwplugin_rpc.get_firewalls_for_tenant(ctx)
|
||||
if fw_list:
|
||||
# if fw present on tenant
|
||||
router_info_list = self._get_router_info_list_for_tenant(
|
||||
routers,
|
||||
tenant_id)
|
||||
if router_info_list:
|
||||
LOG.debug(_("Router List: '%s'"),
|
||||
[ri.router['id'] for ri in router_info_list])
|
||||
LOG.debug(_("fw_list: '%s'"),
|
||||
[fw['id'] for fw in fw_list])
|
||||
# apply sync data on fw for this tenant
|
||||
for fw in fw_list:
|
||||
# fw, routers present on this host for tenant
|
||||
# install
|
||||
LOG.debug(_("Apply fw on Router List: '%s'"),
|
||||
[ri.router['id']
|
||||
for ri in router_info_list])
|
||||
# no need to apply sync data for ACTIVE fw
|
||||
if fw['status'] != constants.ACTIVE:
|
||||
self._invoke_driver_for_sync_from_plugin(
|
||||
ctx,
|
||||
router_info_list,
|
||||
fw)
|
||||
self.services_sync = False
|
||||
except Exception:
|
||||
LOG.exception(_("Failed fwaas process services sync"))
|
||||
self.services_sync = True
|
||||
|
||||
def create_firewall(self, context, firewall, host):
|
||||
"""Handle Rpc from plugin to create a firewall."""
|
||||
return self._invoke_driver_for_plugin_api(
|
||||
context,
|
||||
firewall,
|
||||
'create_firewall')
|
||||
|
||||
def update_firewall(self, context, firewall, host):
|
||||
"""Handle Rpc from plugin to update a firewall."""
|
||||
return self._invoke_driver_for_plugin_api(
|
||||
context,
|
||||
firewall,
|
||||
'update_firewall')
|
||||
|
||||
def delete_firewall(self, context, firewall, host):
|
||||
"""Handle Rpc from plugin to delete a firewall."""
|
||||
return self._invoke_driver_for_plugin_api(
|
||||
context,
|
||||
firewall,
|
||||
'delete_firewall')
|
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,147 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Gary Duan, gduan@varmour.com, vArmour Networks
|
||||
|
||||
import base64
|
||||
|
||||
import httplib2
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.openstack.common import jsonutils as json
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('director', default='localhost',
|
||||
help=_("vArmour director ip")),
|
||||
cfg.StrOpt('director_port', default='443',
|
||||
help=_("vArmour director port")),
|
||||
cfg.StrOpt('username', default='varmour',
|
||||
help=_("vArmour director username")),
|
||||
cfg.StrOpt('password', default='varmour', secret=True,
|
||||
help=_("vArmour director password")), ]
|
||||
|
||||
cfg.CONF.register_opts(OPTS, "vArmour")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
REST_URL_PREFIX = '/api/v1.0'
|
||||
|
||||
|
||||
class vArmourAPIException(Exception):
|
||||
message = _("An unknown exception.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
try:
|
||||
self.err = self.message % kwargs
|
||||
|
||||
except Exception:
|
||||
self.err = self.message
|
||||
|
||||
def __str__(self):
|
||||
return self.err
|
||||
|
||||
|
||||
class AuthenticationFailure(vArmourAPIException):
|
||||
message = _("Invalid login credential.")
|
||||
|
||||
|
||||
class vArmourRestAPI(object):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug(_('vArmourRestAPI: started'))
|
||||
self.user = cfg.CONF.vArmour.username
|
||||
self.passwd = cfg.CONF.vArmour.password
|
||||
self.server = cfg.CONF.vArmour.director
|
||||
self.port = cfg.CONF.vArmour.director_port
|
||||
self.timeout = 3
|
||||
self.key = ''
|
||||
|
||||
def auth(self):
|
||||
headers = {}
|
||||
enc = base64.b64encode(self.user + ':' + self.passwd)
|
||||
headers['Authorization'] = 'Basic ' + enc
|
||||
resp = self.rest_api('POST', va_utils.REST_URL_AUTH, None, headers)
|
||||
if resp and resp['status'] == 200:
|
||||
self.key = resp['body']['auth']
|
||||
return True
|
||||
else:
|
||||
raise AuthenticationFailure()
|
||||
|
||||
def commit(self):
|
||||
self.rest_api('POST', va_utils.REST_URL_COMMIT)
|
||||
|
||||
def rest_api(self, method, url, body=None, headers=None):
|
||||
url = REST_URL_PREFIX + url
|
||||
if body:
|
||||
body_data = json.dumps(body)
|
||||
else:
|
||||
body_data = ''
|
||||
if not headers:
|
||||
headers = {}
|
||||
enc = base64.b64encode('%s:%s' % (self.user, self.key))
|
||||
headers['Authorization'] = 'Basic ' + enc
|
||||
|
||||
LOG.debug(_("vArmourRestAPI: %(server)s %(port)s"),
|
||||
{'server': self.server, 'port': self.port})
|
||||
|
||||
try:
|
||||
action = "https://" + self.server + ":" + self.port + url
|
||||
|
||||
LOG.debug(_("vArmourRestAPI Sending: "
|
||||
"%(method)s %(action)s %(headers)s %(body_data)s"),
|
||||
{'method': method, 'action': action,
|
||||
'headers': headers, 'body_data': body_data})
|
||||
|
||||
h = httplib2.Http(timeout=3,
|
||||
disable_ssl_certificate_validation=True)
|
||||
resp, resp_str = h.request(action, method,
|
||||
body=body_data,
|
||||
headers=headers)
|
||||
|
||||
LOG.debug(_("vArmourRestAPI Response: %(status)s %(resp_str)s"),
|
||||
{'status': resp.status, 'resp_str': resp_str})
|
||||
|
||||
if resp.status == 200:
|
||||
return {'status': resp.status,
|
||||
'reason': resp.reason,
|
||||
'body': json.loads(resp_str)}
|
||||
except Exception:
|
||||
LOG.error(_('vArmourRestAPI: Could not establish HTTP connection'))
|
||||
|
||||
def del_cfg_objs(self, url, prefix):
|
||||
resp = self.rest_api('GET', url)
|
||||
if resp and resp['status'] == 200:
|
||||
olist = resp['body']['response']
|
||||
if not olist:
|
||||
return
|
||||
|
||||
for o in olist:
|
||||
if o.startswith(prefix):
|
||||
self.rest_api('DELETE', url + '/"name:%s"' % o)
|
||||
self.commit()
|
||||
|
||||
def count_cfg_objs(self, url, prefix):
|
||||
count = 0
|
||||
resp = self.rest_api('GET', url)
|
||||
if resp and resp['status'] == 200:
|
||||
for o in resp['body']['response']:
|
||||
if o.startswith(prefix):
|
||||
count += 1
|
||||
|
||||
return count
|
@ -1,351 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Gary Duan, vArmour Networks Inc.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent import l3_agent
|
||||
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.common import constants as l3_constants
|
||||
from neutron.common import topics
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import service
|
||||
from neutron import service as neutron_service
|
||||
from neutron.services.firewall.agents.l3reference import firewall_l3_agent
|
||||
from neutron.services.firewall.agents.varmour import varmour_api
|
||||
from neutron.services.firewall.agents.varmour import varmour_utils as va_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class vArmourL3NATAgent(l3_agent.L3NATAgent,
|
||||
firewall_l3_agent.FWaaSL3AgentRpcCallback):
|
||||
def __init__(self, host, conf=None):
|
||||
LOG.debug(_('vArmourL3NATAgent: __init__'))
|
||||
self.rest = varmour_api.vArmourRestAPI()
|
||||
super(vArmourL3NATAgent, self).__init__(host, conf)
|
||||
|
||||
def _destroy_router_namespaces(self, only_router_id=None):
|
||||
return
|
||||
|
||||
def _destroy_router_namespace(self, namespace):
|
||||
return
|
||||
|
||||
def _create_router_namespace(self, ri):
|
||||
return
|
||||
|
||||
def _router_added(self, router_id, router):
|
||||
LOG.debug(_("_router_added: %s"), router_id)
|
||||
ri = l3_agent.RouterInfo(router_id, self.root_helper,
|
||||
self.conf.use_namespaces, router)
|
||||
self.router_info[router_id] = ri
|
||||
super(vArmourL3NATAgent, self).process_router_add(ri)
|
||||
|
||||
def _router_removed(self, router_id):
|
||||
LOG.debug(_("_router_removed: %s"), router_id)
|
||||
|
||||
ri = self.router_info[router_id]
|
||||
if ri:
|
||||
ri.router['gw_port'] = None
|
||||
ri.router[l3_constants.INTERFACE_KEY] = []
|
||||
ri.router[l3_constants.FLOATINGIP_KEY] = []
|
||||
self.process_router(ri)
|
||||
|
||||
name = va_utils.get_snat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
|
||||
|
||||
name = va_utils.get_dnat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, name)
|
||||
|
||||
name = va_utils.get_trusted_zone_name(ri)
|
||||
self._va_unset_zone_interfaces(name, True)
|
||||
|
||||
name = va_utils.get_untrusted_zone_name(ri)
|
||||
self._va_unset_zone_interfaces(name, True)
|
||||
|
||||
del self.router_info[router_id]
|
||||
|
||||
def _spawn_metadata_proxy(self, router_id, ns_name):
|
||||
return
|
||||
|
||||
def _destroy_metadata_proxy(self, router_id, ns_name):
|
||||
return
|
||||
|
||||
def _set_subnet_info(self, port):
|
||||
ips = port['fixed_ips']
|
||||
if not ips:
|
||||
raise Exception(_("Router port %s has no IP address") % port['id'])
|
||||
return
|
||||
if len(ips) > 1:
|
||||
LOG.warn(_("Ignoring multiple IPs on router port %s"), port['id'])
|
||||
prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen
|
||||
port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
|
||||
|
||||
def _va_unset_zone_interfaces(self, zone_name, remove_zone=False):
|
||||
# return True if zone exists; otherwise, return False
|
||||
LOG.debug(_("_va_unset_zone_interfaces: %s"), zone_name)
|
||||
resp = self.rest.rest_api('GET', va_utils.REST_URL_CONF_ZONE)
|
||||
if resp and resp['status'] == 200:
|
||||
zlist = resp['body']['response']
|
||||
for zn in zlist:
|
||||
if zn == zone_name:
|
||||
commit = False
|
||||
|
||||
if 'interface' in zlist[zn]:
|
||||
for intf in zlist[zn]['interface']:
|
||||
self.rest.rest_api('DELETE',
|
||||
va_utils.REST_URL_CONF +
|
||||
va_utils.REST_ZONE_NAME % zn +
|
||||
va_utils.REST_INTF_NAME % intf)
|
||||
commit = True
|
||||
if remove_zone:
|
||||
self.rest.rest_api('DELETE',
|
||||
va_utils.REST_URL_CONF +
|
||||
va_utils.REST_ZONE_NAME % zn)
|
||||
commit = True
|
||||
|
||||
if commit:
|
||||
self.rest.commit()
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _va_pif_2_lif(self, pif):
|
||||
return pif + '.0'
|
||||
|
||||
def _va_set_interface_ip(self, pif, cidr):
|
||||
LOG.debug(_("_va_set_interface_ip: %(pif)s %(cidr)s"),
|
||||
{'pif': pif, 'cidr': cidr})
|
||||
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
obj = va_utils.REST_INTF_NAME % pif + va_utils.REST_LOGIC_NAME % lif
|
||||
body = {
|
||||
'name': lif,
|
||||
'family': 'ipv4',
|
||||
'address': cidr
|
||||
}
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF + obj, body)
|
||||
|
||||
def _va_get_port_name(self, port_list, name):
|
||||
if name:
|
||||
for p in port_list:
|
||||
if p['VM name'] == name:
|
||||
return p['name']
|
||||
|
||||
def _va_config_trusted_zone(self, ri, plist):
|
||||
zone = va_utils.get_trusted_zone_name(ri)
|
||||
LOG.debug(_("_va_config_trusted_zone: %s"), zone)
|
||||
|
||||
body = {
|
||||
'name': zone,
|
||||
'type': 'L3',
|
||||
'interface': []
|
||||
}
|
||||
|
||||
if not self._va_unset_zone_interfaces(zone):
|
||||
# if zone doesn't exist, create it
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
# add new internal ports to trusted zone
|
||||
for p in ri.internal_ports:
|
||||
if p['admin_state_up']:
|
||||
dev = self.get_internal_device_name(p['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
if lif not in body['interface']:
|
||||
body['interface'].append(lif)
|
||||
|
||||
self._va_set_interface_ip(pif, p['ip_cidr'])
|
||||
|
||||
if body['interface']:
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_untrusted_zone(self, ri, plist):
|
||||
zone = va_utils.get_untrusted_zone_name(ri)
|
||||
LOG.debug(_("_va_config_untrusted_zone: %s"), zone)
|
||||
|
||||
body = {
|
||||
'name': zone,
|
||||
'type': 'L3',
|
||||
'interface': []
|
||||
}
|
||||
|
||||
if not self._va_unset_zone_interfaces(zone):
|
||||
# if zone doesn't exist, create it
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
# add new gateway ports to untrusted zone
|
||||
if ri.ex_gw_port:
|
||||
LOG.debug(_("_va_config_untrusted_zone: gw=%r"), ri.ex_gw_port)
|
||||
dev = self.get_external_device_name(ri.ex_gw_port['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
lif = self._va_pif_2_lif(pif)
|
||||
|
||||
self._va_set_interface_ip(pif, ri.ex_gw_port['ip_cidr'])
|
||||
|
||||
body['interface'].append(lif)
|
||||
self.rest.rest_api('PUT', va_utils.REST_URL_CONF_ZONE, body)
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_router_snat_rules(self, ri, plist):
|
||||
LOG.debug(_('_va_config_router_snat_rules: %s'), ri.router['id'])
|
||||
|
||||
prefix = va_utils.get_snat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
|
||||
|
||||
if not ri.enable_snat:
|
||||
return
|
||||
|
||||
for idx, p in enumerate(ri.internal_ports):
|
||||
if p['admin_state_up']:
|
||||
dev = self.get_internal_device_name(p['id'])
|
||||
pif = self._va_get_port_name(plist, dev)
|
||||
if pif:
|
||||
net = netaddr.IPNetwork(p['ip_cidr'])
|
||||
body = {
|
||||
'name': '%s_%d' % (prefix, idx),
|
||||
'ingress-context-type': 'interface',
|
||||
'ingress-index': self._va_pif_2_lif(pif),
|
||||
'source-address': [
|
||||
[str(netaddr.IPAddress(net.first + 2)),
|
||||
str(netaddr.IPAddress(net.last - 1))]
|
||||
],
|
||||
'flag': 'interface translate-source'
|
||||
}
|
||||
self.rest.rest_api('POST',
|
||||
va_utils.REST_URL_CONF_NAT_RULE,
|
||||
body)
|
||||
|
||||
if ri.internal_ports:
|
||||
self.rest.commit()
|
||||
|
||||
def _va_config_floating_ips(self, ri):
|
||||
LOG.debug(_('_va_config_floating_ips: %s'), ri.router['id'])
|
||||
|
||||
prefix = va_utils.get_dnat_rule_name(ri)
|
||||
self.rest.del_cfg_objs(va_utils.REST_URL_CONF_NAT_RULE, prefix)
|
||||
|
||||
# add new dnat rules
|
||||
for idx, fip in enumerate(ri.floating_ips):
|
||||
body = {
|
||||
'name': '%s_%d' % (prefix, idx),
|
||||
'ingress-context-type': 'zone',
|
||||
'ingress-index': va_utils.get_untrusted_zone_name(ri),
|
||||
'destination-address': [[fip['floating_ip_address'],
|
||||
fip['floating_ip_address']]],
|
||||
'static': [fip['fixed_ip_address'], fip['fixed_ip_address']],
|
||||
'flag': 'translate-destination'
|
||||
}
|
||||
self.rest.rest_api('POST', va_utils.REST_URL_CONF_NAT_RULE, body)
|
||||
|
||||
if ri.floating_ips:
|
||||
self.rest.commit()
|
||||
|
||||
def process_router(self, ri):
|
||||
LOG.debug(_("process_router: %s"), ri.router['id'])
|
||||
super(vArmourL3NATAgent, self).process_router(ri)
|
||||
|
||||
self.rest.auth()
|
||||
|
||||
# read internal port name and configuration port name map
|
||||
resp = self.rest.rest_api('GET', va_utils.REST_URL_INTF_MAP)
|
||||
if resp and resp['status'] == 200:
|
||||
try:
|
||||
plist = resp['body']['response']
|
||||
except ValueError:
|
||||
LOG.warn(_("Unable to parse interface mapping."))
|
||||
return
|
||||
else:
|
||||
LOG.warn(_("Unable to read interface mapping."))
|
||||
return
|
||||
|
||||
if ri.ex_gw_port:
|
||||
self._set_subnet_info(ri.ex_gw_port)
|
||||
self._va_config_trusted_zone(ri, plist)
|
||||
self._va_config_untrusted_zone(ri, plist)
|
||||
self._va_config_router_snat_rules(ri, plist)
|
||||
self._va_config_floating_ips(ri)
|
||||
|
||||
def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
|
||||
interface_name, action):
|
||||
return
|
||||
|
||||
def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address):
|
||||
return
|
||||
|
||||
def external_gateway_added(self, ri, ex_gw_port,
|
||||
interface_name, internal_cidrs):
|
||||
LOG.debug(_("external_gateway_added: %s"), ri.router['id'])
|
||||
|
||||
if not ip_lib.device_exists(interface_name,
|
||||
root_helper=self.root_helper,
|
||||
namespace=ri.ns_name):
|
||||
self.driver.plug(ex_gw_port['network_id'],
|
||||
ex_gw_port['id'], interface_name,
|
||||
ex_gw_port['mac_address'],
|
||||
bridge=self.conf.external_network_bridge,
|
||||
namespace=ri.ns_name,
|
||||
prefix=l3_agent.EXTERNAL_DEV_PREFIX)
|
||||
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
|
||||
namespace=ri.ns_name)
|
||||
|
||||
def _update_routing_table(self, ri, operation, route):
|
||||
return
|
||||
|
||||
|
||||
class vArmourL3NATAgentWithStateReport(vArmourL3NATAgent,
|
||||
l3_agent.L3NATAgentWithStateReport):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
conf = cfg.CONF
|
||||
conf.register_opts(vArmourL3NATAgent.OPTS)
|
||||
config.register_interface_driver_opts_helper(conf)
|
||||
config.register_use_namespaces_opts_helper(conf)
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
config.register_root_helper(conf)
|
||||
conf.register_opts(interface.OPTS)
|
||||
conf.register_opts(external_process.OPTS)
|
||||
common_config.init(sys.argv[1:])
|
||||
config.setup_logging(conf)
|
||||
server = neutron_service.Service.create(
|
||||
binary='neutron-l3-agent',
|
||||
topic=topics.L3_AGENT,
|
||||
report_interval=cfg.CONF.AGENT.report_interval,
|
||||
manager='neutron.services.firewall.agents.varmour.varmour_router.'
|
||||
'vArmourL3NATAgentWithStateReport')
|
||||
service.launch(server).wait()
|
@ -1,74 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 vArmour Networks Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Gary Duan, gduan@varmour.com, vArmour Networks
|
||||
|
||||
ROUTER_OBJ_PREFIX = 'r-'
|
||||
OBJ_PREFIX_LEN = 8
|
||||
TRUST_ZONE = '_z_trust'
|
||||
UNTRUST_ZONE = '_z_untrust'
|
||||
SNAT_RULE = '_snat'
|
||||
DNAT_RULE = '_dnat'
|
||||
ROUTER_POLICY = '_p'
|
||||
|
||||
REST_URL_CONF = '/config'
|
||||
REST_URL_AUTH = '/auth'
|
||||
REST_URL_COMMIT = '/commit'
|
||||
REST_URL_INTF_MAP = '/operation/interface/mapping'
|
||||
|
||||
REST_URL_CONF_NAT_RULE = REST_URL_CONF + '/nat/rule'
|
||||
REST_URL_CONF_ZONE = REST_URL_CONF + '/zone'
|
||||
REST_URL_CONF_POLICY = REST_URL_CONF + '/policy'
|
||||
REST_URL_CONF_ADDR = REST_URL_CONF + '/address'
|
||||
REST_URL_CONF_SERVICE = REST_URL_CONF + '/service'
|
||||
|
||||
REST_ZONE_NAME = '/zone/"name:%s"'
|
||||
REST_INTF_NAME = '/interface/"name:%s"'
|
||||
REST_LOGIC_NAME = '/logical/"name:%s"'
|
||||
REST_SERVICE_NAME = '/service/"name:%s"/rule'
|
||||
|
||||
|
||||
def get_router_object_prefix(ri):
|
||||
return ROUTER_OBJ_PREFIX + ri.router['id'][:OBJ_PREFIX_LEN]
|
||||
|
||||
|
||||
def get_firewall_object_prefix(ri, fw):
|
||||
return get_router_object_prefix(ri) + '-' + fw['id'][:OBJ_PREFIX_LEN]
|
||||
|
||||
|
||||
def get_trusted_zone_name(ri):
|
||||
return get_router_object_prefix(ri) + TRUST_ZONE
|
||||
|
||||
|
||||
def get_untrusted_zone_name(ri):
|
||||
return get_router_object_prefix(ri) + UNTRUST_ZONE
|
||||
|
||||
|
||||
def get_snat_rule_name(ri):
|
||||
return get_router_object_prefix(ri) + SNAT_RULE
|
||||
|
||||
|
||||
def get_dnat_rule_name(ri):
|
||||
return get_router_object_prefix(ri) + DNAT_RULE
|
||||
|
||||
|
||||
def get_router_policy_name(ri):
|
||||
return get_router_object_prefix(ri) + ROUTER_POLICY
|
||||
|
||||
|
||||
def get_firewall_policy_name(ri, fw, rule):
|
||||
return get_firewall_object_prefix(ri, fw) + rule['id'][:OBJ_PREFIX_LEN]
|
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,100 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Dell Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_firewall(self, 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(self, apply_list, firewall):
|
||||
"""Delete firewall.
|
||||
|
||||
Removes all policies created by this instance and frees up
|
||||
all the resources.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_firewall(self, 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, apply_list, firewall):
|
||||
"""Apply the default policy on all trusted interfaces.
|
||||
|
||||
Remove current policy and apply the default policy on all trusted
|
||||
interfaces.
|
||||
"""
|
||||
pass
|
@ -1,16 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -1,275 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Dell Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc.
|
||||
|
||||
from neutron.agent.linux import iptables_manager
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services.firewall.drivers import fwaas_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FWAAS_DRIVER_NAME = 'Fwaas iptables driver'
|
||||
FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy'
|
||||
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'}
|
||||
|
||||
|
||||
class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
|
||||
"""IPTables driver for Firewall As A Service."""
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug(_("Initializing fwaas iptables driver"))
|
||||
|
||||
def create_firewall(self, 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(apply_list, firewall)
|
||||
else:
|
||||
self.apply_default_policy(apply_list, firewall)
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_("Failed to create firewall: %s"), firewall['id'])
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def delete_firewall(self, 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 in apply_list:
|
||||
ipt_mgr = router_info.iptables_manager
|
||||
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()
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_("Failed to delete firewall: %s"), fwid)
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def update_firewall(self, 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']:
|
||||
self._setup_firewall(apply_list, firewall)
|
||||
else:
|
||||
self.apply_default_policy(apply_list, firewall)
|
||||
except (LookupError, RuntimeError):
|
||||
# catch known library exceptions and raise Fwaas generic exception
|
||||
LOG.exception(_("Failed to update firewall: %s"), firewall['id'])
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def apply_default_policy(self, 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 in apply_list:
|
||||
ipt_mgr = router_info.iptables_manager
|
||||
|
||||
# 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)
|
||||
self._enable_policy_chain(fwid, ipt_mgr)
|
||||
|
||||
# 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(_("Failed to apply default policy on firewall: %s"),
|
||||
fwid)
|
||||
raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
|
||||
|
||||
def _setup_firewall(self, apply_list, firewall):
|
||||
fwid = firewall['id']
|
||||
for router_info in apply_list:
|
||||
ipt_mgr = router_info.iptables_manager
|
||||
|
||||
# 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_mgr)
|
||||
|
||||
# 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_mgr):
|
||||
"""Create Fwaas chain using the rules in the policy
|
||||
"""
|
||||
fw_rules_list = firewall['firewall_rule_list']
|
||||
fwid = firewall['id']
|
||||
|
||||
#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 fw_rules_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)
|
||||
ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
|
||||
table.add_rule(ichain_name, iptbl_rule)
|
||||
table.add_rule(ochain_name, iptbl_rule)
|
||||
self._enable_policy_chain(fwid, ipt_mgr)
|
||||
|
||||
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'].ensure_remove_chain(chain_name)
|
||||
else:
|
||||
ipt_mgr.ipv6['filter'].ensure_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_mgr):
|
||||
bname = iptables_manager.binary_name
|
||||
|
||||
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:
|
||||
jump_rule = ['%s qr-+ -j %s-%s' % (IPTABLES_DIR[direction],
|
||||
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)
|
||||
jump_rule = ['-o qr-+ -j %s-%s' % (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)
|
||||
jump_rule = ['-i qr-+ -j %s-%s' % (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 = rule.get('action') == 'allow' and 'ACCEPT' or 'DROP'
|
||||
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 |