From d6f28ffc9b36d272d23e0d089d2bbfc6193aa30a Mon Sep 17 00:00:00 2001 From: garyduan Date: Sat, 24 Aug 2013 17:34:00 -0700 Subject: [PATCH] vArmour gateway agent and FWaaS driver This patch enables vArmour's routing and firewall services to be deployed in openstack environment. - as gateway for internal networks - support SNAT and DNAT (floating IP) - FWaaS services Implements: blueprint varmour-fwaas-driver Change-Id: I6ddfa3137ed7e2a3fcf16a764d1340a8eae9359a --- .../firewall/agents/varmour/__init__.py | 16 + .../firewall/agents/varmour/varmour_api.py | 147 ++++++++ .../firewall/agents/varmour/varmour_router.py | 347 ++++++++++++++++++ .../firewall/agents/varmour/varmour_utils.py | 74 ++++ .../firewall/drivers/varmour/__init__.py | 16 + .../firewall/drivers/varmour/varmour_fwaas.py | 207 +++++++++++ .../firewall/agents/varmour/__init__.py | 16 + .../agents/varmour/test_varmour_router.py | 322 ++++++++++++++++ .../firewall/drivers/varmour/__init__.py | 16 + .../drivers/varmour/test_varmour_fwaas.py | 290 +++++++++++++++ 10 files changed, 1451 insertions(+) create mode 100755 neutron/services/firewall/agents/varmour/__init__.py create mode 100755 neutron/services/firewall/agents/varmour/varmour_api.py create mode 100755 neutron/services/firewall/agents/varmour/varmour_router.py create mode 100755 neutron/services/firewall/agents/varmour/varmour_utils.py create mode 100755 neutron/services/firewall/drivers/varmour/__init__.py create mode 100755 neutron/services/firewall/drivers/varmour/varmour_fwaas.py create mode 100755 neutron/tests/unit/services/firewall/agents/varmour/__init__.py create mode 100644 neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py create mode 100755 neutron/tests/unit/services/firewall/drivers/varmour/__init__.py create mode 100644 neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py diff --git a/neutron/services/firewall/agents/varmour/__init__.py b/neutron/services/firewall/agents/varmour/__init__.py new file mode 100755 index 0000000000..5e8da711fb --- /dev/null +++ b/neutron/services/firewall/agents/varmour/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/neutron/services/firewall/agents/varmour/varmour_api.py b/neutron/services/firewall/agents/varmour/varmour_api.py new file mode 100755 index 0000000000..86cb46fac3 --- /dev/null +++ b/neutron/services/firewall/agents/varmour/varmour_api.py @@ -0,0 +1,147 @@ +# 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 diff --git a/neutron/services/firewall/agents/varmour/varmour_router.py b/neutron/services/firewall/agents/varmour/varmour_router.py new file mode 100755 index 0000000000..0916533e64 --- /dev/null +++ b/neutron/services/firewall/agents/varmour/varmour_router.py @@ -0,0 +1,347 @@ +# 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 eventlet +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 constants as l3_constants +from neutron.common import legacy +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_info): + return + + def _destroy_metadata_proxy(self, router_info): + 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(): + eventlet.monkey_patch() + conf = cfg.CONF + conf.register_opts(vArmourL3NATAgent.OPTS) + config.register_agent_state_opts_helper(conf) + config.register_root_helper(conf) + conf.register_opts(interface.OPTS) + conf.register_opts(external_process.OPTS) + conf(project='neutron') + config.setup_logging(conf) + legacy.modernize_quantum_config(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() diff --git a/neutron/services/firewall/agents/varmour/varmour_utils.py b/neutron/services/firewall/agents/varmour/varmour_utils.py new file mode 100755 index 0000000000..7290cb6e61 --- /dev/null +++ b/neutron/services/firewall/agents/varmour/varmour_utils.py @@ -0,0 +1,74 @@ +# 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] diff --git a/neutron/services/firewall/drivers/varmour/__init__.py b/neutron/services/firewall/drivers/varmour/__init__.py new file mode 100755 index 0000000000..5e8da711fb --- /dev/null +++ b/neutron/services/firewall/drivers/varmour/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/neutron/services/firewall/drivers/varmour/varmour_fwaas.py b/neutron/services/firewall/drivers/varmour/varmour_fwaas.py new file mode 100755 index 0000000000..bcdf2f909c --- /dev/null +++ b/neutron/services/firewall/drivers/varmour/varmour_fwaas.py @@ -0,0 +1,207 @@ +# 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 + +from neutron.openstack.common import log as logging +from neutron.services.firewall.agents.varmour import varmour_api +from neutron.services.firewall.agents.varmour import varmour_utils as va_utils +from neutron.services.firewall.drivers import fwaas_base + +LOG = logging.getLogger(__name__) + + +class vArmourFwaasDriver(fwaas_base.FwaasDriverBase): + def __init__(self): + LOG.debug(_("Initializing fwaas vArmour driver")) + + self.rest = varmour_api.vArmourRestAPI() + + def create_firewall(self, apply_list, firewall): + LOG.debug(_('create_firewall (%s)'), firewall['id']) + + return self.update_firewall(apply_list, firewall) + + def update_firewall(self, apply_list, firewall): + LOG.debug(_("update_firewall (%s)"), firewall['id']) + + if firewall['admin_state_up']: + return self._update_firewall(apply_list, firewall) + else: + return self.apply_default_policy(apply_list, firewall) + + def delete_firewall(self, apply_list, firewall): + LOG.debug(_("delete_firewall (%s)"), firewall['id']) + + return self.apply_default_policy(apply_list, firewall) + + def apply_default_policy(self, apply_list, firewall): + LOG.debug(_("apply_default_policy (%s)"), firewall['id']) + + self.rest.auth() + + for ri in apply_list: + self._clear_policy(ri, firewall) + + return True + + def _update_firewall(self, apply_list, firewall): + LOG.debug(_("Updating firewall (%s)"), firewall['id']) + + self.rest.auth() + + for ri in apply_list: + self._clear_policy(ri, firewall) + self._setup_policy(ri, firewall) + + return True + + def _setup_policy(self, ri, fw): + # create zones no matter if they exist. Interfaces are added by router + body = { + 'type': 'L3', + 'interface': [] + } + + body['name'] = va_utils.get_trusted_zone_name(ri) + self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body) + body['name'] = va_utils.get_untrusted_zone_name(ri) + self.rest.rest_api('POST', va_utils.REST_URL_CONF_ZONE, body) + self.rest.commit() + + servs = dict() + addrs = dict() + for rule in fw['firewall_rule_list']: + if not rule['enabled']: + continue + + if rule['ip_version'] == 4: + service = self._make_service(ri, fw, rule, servs) + s_addr = self._make_address(ri, fw, rule, addrs, True) + d_addr = self._make_address(ri, fw, rule, addrs, False) + + policy = va_utils.get_firewall_policy_name(ri, fw, rule) + z0 = va_utils.get_trusted_zone_name(ri) + z1 = va_utils.get_untrusted_zone_name(ri) + body = self._make_policy(policy + '_0', rule, + z0, z0, s_addr, d_addr, service) + self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body) + body = self._make_policy(policy + '_1', rule, + z0, z1, s_addr, d_addr, service) + self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body) + body = self._make_policy(policy + '_2', rule, + z1, z0, s_addr, d_addr, service) + self.rest.rest_api('POST', va_utils.REST_URL_CONF_POLICY, body) + + self.rest.commit() + else: + LOG.warn(_("Unsupported IP version rule.")) + + def _clear_policy(self, ri, fw): + prefix = va_utils.get_firewall_object_prefix(ri, fw) + self.rest.del_cfg_objs(va_utils.REST_URL_CONF_POLICY, prefix) + self.rest.del_cfg_objs(va_utils.REST_URL_CONF_ADDR, prefix) + self.rest.del_cfg_objs(va_utils.REST_URL_CONF_SERVICE, prefix) + + def _make_service(self, ri, fw, rule, servs): + prefix = va_utils.get_firewall_object_prefix(ri, fw) + + if rule.get('protocol'): + key = rule.get('protocol') + if rule.get('source_port'): + key += '-' + rule.get('source_port') + if rule.get('destination_port'): + key += '-' + rule.get('destination_port') + else: + return + + if key in servs: + name = '%s_%d' % (prefix, servs[key]) + else: + # create new service object with index + idx = len(servs) + servs[key] = idx + name = '%s_%d' % (prefix, idx) + + body = {'name': name} + self.rest.rest_api('POST', + va_utils.REST_URL_CONF_SERVICE, + body) + body = self._make_service_rule(rule) + self.rest.rest_api('POST', + va_utils.REST_URL_CONF + + va_utils.REST_SERVICE_NAME % name, + body) + self.rest.commit() + + return name + + def _make_service_rule(self, rule): + body = { + 'name': '1', + 'protocol': rule.get('protocol') + } + if 'source_port' in rule: + body['source-start'] = rule['source_port'] + body['source-end'] = rule['source_port'] + if 'destination_port' in rule: + body['dest-start'] = rule['destination_port'] + body['dest-end'] = rule['destination_port'] + + return body + + def _make_address(self, ri, fw, rule, addrs, is_src): + prefix = va_utils.get_firewall_object_prefix(ri, fw) + + if is_src: + key = rule.get('source_ip_address') + else: + key = rule.get('destination_ip_address') + + if not key: + return + + if key in addrs: + name = '%s_%d' % (prefix, addrs[key]) + else: + # create new address object with idx + idx = len(addrs) + addrs[key] = idx + name = '%s_%d' % (prefix, idx) + + body = { + 'name': name, + 'type': 'ipv4', + 'ipv4': key + } + self.rest.rest_api('POST', va_utils.REST_URL_CONF_ADDR, body) + self.rest.commit() + + return name + + def _make_policy(self, name, rule, zone0, zone1, s_addr, d_addr, service): + body = { + 'name': name, + 'action': 'permit' if rule.get('action') == 'allow' else 'deny', + 'from': zone0, + 'to': zone1, + 'match-source-address': [s_addr or 'Any'], + 'match-dest-address': [d_addr or 'Any'], + 'match-service': [service or 'Any'] + } + + return body diff --git a/neutron/tests/unit/services/firewall/agents/varmour/__init__.py b/neutron/tests/unit/services/firewall/agents/varmour/__init__.py new file mode 100755 index 0000000000..5e8da711fb --- /dev/null +++ b/neutron/tests/unit/services/firewall/agents/varmour/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py b/neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py new file mode 100644 index 0000000000..410821eda9 --- /dev/null +++ b/neutron/tests/unit/services/firewall/agents/varmour/test_varmour_router.py @@ -0,0 +1,322 @@ +# 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 mock +from oslo.config import cfg + +from neutron.agent.common import config as agent_config +from neutron.agent import l3_agent +from neutron.agent.linux import interface +from neutron.common import config as base_config +from neutron.common import constants as l3_constants +from neutron.openstack.common import uuidutils +from neutron.services.firewall.agents.varmour import varmour_router +from neutron.services.firewall.agents.varmour import varmour_utils +from neutron.tests import base + +_uuid = uuidutils.generate_uuid +HOSTNAME = 'myhost' +FAKE_DIRECTOR = '1.1.1.1' + + +class TestVarmourRouter(base.BaseTestCase): + + def setUp(self): + super(TestVarmourRouter, self).setUp() + self.conf = cfg.ConfigOpts() + self.conf.register_opts(base_config.core_opts) + self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS) + agent_config.register_root_helper(self.conf) + self.conf.register_opts(interface.OPTS) + self.conf.set_override('interface_driver', + 'neutron.agent.linux.interface.NullDriver') + self.conf.root_helper = 'sudo' + + self.device_exists_p = mock.patch( + 'neutron.agent.linux.ip_lib.device_exists') + self.device_exists = self.device_exists_p.start() + + self.utils_exec_p = mock.patch( + 'neutron.agent.linux.utils.execute') + self.utils_exec = self.utils_exec_p.start() + + self.external_process_p = mock.patch( + 'neutron.agent.linux.external_process.ProcessManager') + self.external_process = self.external_process_p.start() + + self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver') + driver_cls = self.dvr_cls_p.start() + self.mock_driver = mock.MagicMock() + self.mock_driver.DEV_NAME_LEN = ( + interface.LinuxInterfaceDriver.DEV_NAME_LEN) + driver_cls.return_value = self.mock_driver + + self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper') + ip_cls = self.ip_cls_p.start() + self.mock_ip = mock.MagicMock() + ip_cls.return_value = self.mock_ip + + self.looping_call_p = mock.patch( + 'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall') + self.looping_call_p.start() + + self.addCleanup(mock.patch.stopall) + + def _create_router(self): + router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf) + router.rest.server = FAKE_DIRECTOR + router.rest.user = 'varmour' + router.rest.passwd = 'varmour' + return router + + def _del_all_internal_ports(self, router): + router[l3_constants.INTERFACE_KEY] = [] + + def _del_internal_ports(self, router, port_idx): + del router[l3_constants.INTERFACE_KEY][port_idx] + + def _add_internal_ports(self, router, port_count=1): + self._del_all_internal_ports(router) + for i in range(0, port_count): + port = {'id': _uuid(), + 'network_id': _uuid(), + 'admin_state_up': True, + 'fixed_ips': [{'ip_address': '10.0.%s.4' % i, + 'subnet_id': _uuid()}], + 'mac_address': 'ca:fe:de:ad:be:ef', + 'subnet': {'cidr': '10.0.%s.0/24' % i, + 'gateway_ip': '10.0.%s.1' % i}} + router[l3_constants.INTERFACE_KEY].append(port) + + def _del_all_floating_ips(self, router): + router[l3_constants.FLOATINGIP_KEY] = [] + + def _del_floating_ips(self, router, port_idx): + del router[l3_constants.FLOATINGIP_KEY][port_idx] + + def _add_floating_ips(self, router, port_count=1): + self._del_all_floating_ips(router) + for i in range(0, port_count): + fip = {'id': _uuid(), + 'port_id': router['gw_port']['id'], + 'floating_ip_address': '172.24.4.%s' % (100 + i), + 'fixed_ip_address': '10.0.0.%s' % (100 + i)} + router[l3_constants.FLOATINGIP_KEY].append(fip) + + def _prepare_router_data(self, enable_snat=None): + router_id = _uuid() + ex_gw_port = {'id': _uuid(), + 'network_id': _uuid(), + 'fixed_ips': [{'ip_address': '172.24.4.2', + 'subnet_id': _uuid()}], + 'subnet': {'cidr': '172.24.4.0/24', + 'gateway_ip': '172.24.4.1'}, + 'ip_cidr': '172.24.4.226/28'} + int_ports = [] + + router = { + 'id': router_id, + l3_constants.INTERFACE_KEY: int_ports, + 'routes': [], + 'gw_port': ex_gw_port} + if enable_snat is not None: + router['enable_snat'] = enable_snat + + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + return ri + + def test_agent_add_internal_network(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_NAT_RULE + prefix = varmour_utils.get_snat_rule_name(ri) + + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + self._add_internal_ports(ri.router, port_count=1) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + def test_agent_remove_internal_network(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_NAT_RULE + prefix = varmour_utils.get_snat_rule_name(ri) + + self._add_internal_ports(ri.router, port_count=2) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 2, 'prefix %s' % prefix) + + self._del_internal_ports(ri.router, 0) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + self._del_all_internal_ports(ri.router) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + def test_agent_add_floating_ips(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + self._add_internal_ports(ri.router, port_count=1) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_NAT_RULE + prefix = varmour_utils.get_dnat_rule_name(ri) + + self._add_floating_ips(ri.router, port_count=1) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + self._add_floating_ips(ri.router, port_count=2) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 2, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + def test_agent_remove_floating_ips(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + self._add_internal_ports(ri.router, port_count=1) + self._add_floating_ips(ri.router, port_count=2) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_NAT_RULE + prefix = varmour_utils.get_dnat_rule_name(ri) + + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 2, 'prefix %s' % prefix) + + self._del_floating_ips(ri.router, 0) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + self._del_all_floating_ips(ri.router) + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + def test_agent_external_gateway(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_ZONE + prefix = varmour_utils.get_untrusted_zone_name(ri) + + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + del ri.router['gw_port'] + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 1, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + def test_agent_snat_enable(self): + router = self._create_router() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + router._router_added(ri.router['id'], ri.router) + + url = varmour_utils.REST_URL_CONF_NAT_RULE + prefix = varmour_utils.get_snat_rule_name(ri) + + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + ri.router['enable_snat'] = False + router.process_router(ri) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) + + router._router_removed(ri.router['id']) + n = router.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0, 'prefix %s' % prefix) diff --git a/neutron/tests/unit/services/firewall/drivers/varmour/__init__.py b/neutron/tests/unit/services/firewall/drivers/varmour/__init__.py new file mode 100755 index 0000000000..5e8da711fb --- /dev/null +++ b/neutron/tests/unit/services/firewall/drivers/varmour/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py b/neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py new file mode 100644 index 0000000000..95674f13f6 --- /dev/null +++ b/neutron/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py @@ -0,0 +1,290 @@ +# 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 mock +from oslo.config import cfg + +from neutron.agent.common import config as agent_config +from neutron.agent import l3_agent +from neutron.agent.linux import interface +from neutron.common import config as base_config +from neutron.common import constants as l3_constants +from neutron.openstack.common import uuidutils +from neutron.services.firewall.agents.varmour import varmour_router +from neutron.services.firewall.agents.varmour import varmour_utils +from neutron.services.firewall.drivers.varmour import varmour_fwaas +from neutron.tests import base + +_uuid = uuidutils.generate_uuid +HOSTNAME = 'myhost' +FAKE_DIRECTOR = '1.1.1.1' + + +class TestBasicRouterOperations(base.BaseTestCase): + + def setUp(self): + super(TestBasicRouterOperations, self).setUp() + self.conf = cfg.ConfigOpts() + self.conf.register_opts(base_config.core_opts) + self.conf.register_opts(varmour_router.vArmourL3NATAgent.OPTS) + agent_config.register_root_helper(self.conf) + self.conf.register_opts(interface.OPTS) + self.conf.set_override('interface_driver', + 'neutron.agent.linux.interface.NullDriver') + self.conf.root_helper = 'sudo' + + self.device_exists_p = mock.patch( + 'neutron.agent.linux.ip_lib.device_exists') + self.device_exists = self.device_exists_p.start() + + self.utils_exec_p = mock.patch( + 'neutron.agent.linux.utils.execute') + self.utils_exec = self.utils_exec_p.start() + + self.external_process_p = mock.patch( + 'neutron.agent.linux.external_process.ProcessManager') + self.external_process = self.external_process_p.start() + + self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver') + driver_cls = self.dvr_cls_p.start() + self.mock_driver = mock.MagicMock() + self.mock_driver.DEV_NAME_LEN = ( + interface.LinuxInterfaceDriver.DEV_NAME_LEN) + driver_cls.return_value = self.mock_driver + + self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper') + ip_cls = self.ip_cls_p.start() + self.mock_ip = mock.MagicMock() + ip_cls.return_value = self.mock_ip + + self.looping_call_p = mock.patch( + 'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall') + self.looping_call_p.start() + + self.addCleanup(mock.patch.stopall) + + def _create_router(self): + router = varmour_router.vArmourL3NATAgent(HOSTNAME, self.conf) + router.rest.server = FAKE_DIRECTOR + router.rest.user = 'varmour' + router.rest.passwd = 'varmour' + return router + + def _create_fwaas(self): + fwaas = varmour_fwaas.vArmourFwaasDriver() + fwaas.rest.server = FAKE_DIRECTOR + fwaas.rest.user = 'varmour' + fwaas.rest.passwd = 'varmour' + return fwaas + + def _del_all_internal_ports(self, router): + router[l3_constants.INTERFACE_KEY] = [] + + def _del_internal_ports(self, router, port_idx): + del router[l3_constants.INTERFACE_KEY][port_idx] + + def _add_internal_ports(self, router, port_count=1): + self._del_all_internal_ports(router) + for i in range(0, port_count): + port = {'id': _uuid(), + 'network_id': _uuid(), + 'admin_state_up': True, + 'fixed_ips': [{'ip_address': '10.0.%s.4' % i, + 'subnet_id': _uuid()}], + 'mac_address': 'ca:fe:de:ad:be:ef', + 'subnet': {'cidr': '10.0.%s.0/24' % i, + 'gateway_ip': '10.0.%s.1' % i}} + router[l3_constants.INTERFACE_KEY].append(port) + + def _del_all_floating_ips(self, router): + router[l3_constants.FLOATINGIP_KEY] = [] + + def _del_floating_ips(self, router, port_idx): + del router[l3_constants.FLOATINGIP_KEY][port_idx] + + def _add_floating_ips(self, router, port_count=1): + self._del_all_floating_ips(router) + for i in range(0, port_count): + fip = {'id': _uuid(), + 'port_id': router['gw_port']['id'], + 'floating_ip_address': '172.24.4.%s' % (100 + i), + 'fixed_ip_address': '10.0.0.%s' % (100 + i)} + router[l3_constants.FLOATINGIP_KEY].append(fip) + + def _prepare_router_data(self, enable_snat=None): + router_id = _uuid() + ex_gw_port = {'id': _uuid(), + 'network_id': _uuid(), + 'fixed_ips': [{'ip_address': '172.24.4.2', + 'subnet_id': _uuid()}], + 'subnet': {'cidr': '172.24.4.0/24', + 'gateway_ip': '172.24.4.1'}, + 'ip_cidr': '172.24.4.226/28'} + int_ports = [] + + router = { + 'id': router_id, + l3_constants.INTERFACE_KEY: int_ports, + 'routes': [], + 'gw_port': ex_gw_port} + if enable_snat is not None: + router['enable_snat'] = enable_snat + + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + return ri + + def _add_firewall_rules(self, fw, rule_count=1): + rules = [] + for i in range(0, rule_count): + rule = {'id': _uuid(), + 'enabled': True, + 'action': 'deny' if (i % 2 == 0) else 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'source_ip_address': '10.0.0.%s/24' % (100 + i), + 'destination_port': '%s' % (100 + i)} + rules.append(rule) + fw['firewall_rule_list'] = rules + + def _prepare_firewall_data(self): + fw = {'id': _uuid(), + 'admin_state_up': True, + 'firewall_rule_list': []} + return fw + + def test_firewall_without_rule(self): + router = self._create_router() + fwaas = self._create_fwaas() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + self._add_internal_ports(ri.router, port_count=1) + self._add_floating_ips(ri.router, port_count=1) + router._router_added(ri.router['id'], ri.router) + + rl = [ri] + + fw = self._prepare_firewall_data() + fwaas.create_firewall(rl, fw) + + url = varmour_utils.REST_URL_CONF_POLICY + prefix = varmour_utils.get_firewall_object_prefix(ri, fw) + + n = fwaas.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0) + + fwaas.delete_firewall(rl, fw) + n = fwaas.rest.count_cfg_objs(url, prefix) + self.assertEqual(n, 0) + + router._router_removed(ri.router['id']) + + def test_firewall_with_rules(self): + router = self._create_router() + fwaas = self._create_fwaas() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + self._add_internal_ports(ri.router, port_count=1) + self._add_floating_ips(ri.router, port_count=1) + router._router_added(ri.router['id'], ri.router) + + rl = [ri] + + fw = self._prepare_firewall_data() + self._add_firewall_rules(fw, 2) + fwaas.create_firewall(rl, fw) + + prefix = varmour_utils.get_firewall_object_prefix(ri, fw) + pol_url = varmour_utils.REST_URL_CONF_POLICY + serv_url = varmour_utils.REST_URL_CONF_SERVICE + addr_url = varmour_utils.REST_URL_CONF_ADDR + + # 3x number of policies + n = fwaas.rest.count_cfg_objs(pol_url, prefix) + self.assertEqual(n, 6) + n = fwaas.rest.count_cfg_objs(addr_url, prefix) + self.assertEqual(n, 2) + n = fwaas.rest.count_cfg_objs(serv_url, prefix) + self.assertEqual(n, 2) + + fwaas.delete_firewall(rl, fw) + n = fwaas.rest.count_cfg_objs(pol_url, prefix) + self.assertEqual(n, 0) + + router._router_removed(ri.router['id']) + + def test_firewall_add_remove_rules(self): + router = self._create_router() + fwaas = self._create_fwaas() + try: + router.rest.auth() + except Exception: + # skip the test, firewall is not deployed + return + + ri = self._prepare_router_data(enable_snat=True) + self._add_internal_ports(ri.router, port_count=1) + self._add_floating_ips(ri.router, port_count=1) + router._router_added(ri.router['id'], ri.router) + + rl = [ri] + + fw = self._prepare_firewall_data() + self._add_firewall_rules(fw, 2) + fwaas.create_firewall(rl, fw) + + prefix = varmour_utils.get_firewall_object_prefix(ri, fw) + pol_url = varmour_utils.REST_URL_CONF_POLICY + serv_url = varmour_utils.REST_URL_CONF_SERVICE + addr_url = varmour_utils.REST_URL_CONF_ADDR + + # 3x number of policies + n = fwaas.rest.count_cfg_objs(pol_url, prefix) + self.assertEqual(n, 6) + n = fwaas.rest.count_cfg_objs(addr_url, prefix) + self.assertEqual(n, 2) + n = fwaas.rest.count_cfg_objs(serv_url, prefix) + self.assertEqual(n, 2) + + self._add_firewall_rules(fw, 1) + fwaas.create_firewall(rl, fw) + n = fwaas.rest.count_cfg_objs(pol_url, prefix) + self.assertEqual(n, 3) + n = fwaas.rest.count_cfg_objs(addr_url, prefix) + self.assertEqual(n, 1) + n = fwaas.rest.count_cfg_objs(serv_url, prefix) + self.assertEqual(n, 1) + + fwaas.delete_firewall(rl, fw) + n = fwaas.rest.count_cfg_objs(pol_url, prefix) + self.assertEqual(n, 0) + + router._router_removed(ri.router['id'])