# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db import servicetype_db as st_db from neutron.services import provider_configuration as provider_conf from neutron.services import service_base from neutron_lib.api.definitions import firewall_v2 from neutron_lib.api.definitions import portbindings as pb_def from neutron_lib.api import validators from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as nl_constants from neutron_lib.exceptions import firewall_v2 as f_exc from neutron_lib.plugins import directory from oslo_log import helpers as log_helpers from oslo_log import log as logging from neutron_fwaas.common import exceptions from neutron_fwaas.common import fwaas_constants from neutron_fwaas.extensions.firewall_v2 import Firewallv2PluginBase LOG = logging.getLogger(__name__) @registry.has_registry_receivers class FirewallPluginV2(Firewallv2PluginBase): """Firewall v2 Neutron service plugin class""" supported_extension_aliases = [firewall_v2.ALIAS] path_prefix = firewall_v2.API_PREFIX def __init__(self): super(FirewallPluginV2, self).__init__() """Do the initialization for the firewall service plugin here.""" # Initialize the Firewall v2 service plugin service_type_manager = st_db.ServiceTypeManager.get_instance() service_type_manager.add_provider_configuration( fwaas_constants.FIREWALL_V2, provider_conf.ProviderConfiguration('neutron_fwaas')) # Load the default driver drivers, default_provider = service_base.load_drivers( fwaas_constants.FIREWALL_V2, self) LOG.info("Firewall v2 Service Plugin using Service Driver: %s", default_provider) if len(drivers) > 1: LOG.warning("Multiple drivers configured for Firewall v2, " "although running multiple drivers in parallel is " "not yet supported") self.driver = drivers[default_provider] @property def _core_plugin(self): return directory.get_plugin() def _ensure_update_firewall_group(self, context, fwg_id): """Checks if the firewall group can be updated Raises FirewallGroupInPendingState if the firewall group is in pending state. :param context: neutron context :param fwg_id: firewall group ID to check :return: Firewall group dict """ fwg = self.get_firewall_group(context, fwg_id) if fwg['status'] in [nl_constants.PENDING_CREATE, nl_constants.PENDING_UPDATE, nl_constants.PENDING_DELETE]: raise f_exc.FirewallGroupInPendingState( firewall_id=fwg_id, pending_state=fwg['status']) return fwg def _ensure_update_firewall_policy(self, context, fwp_id): """Checks if the firewall policy can be updated Fetch firewall group associated to the policy and checks if they can be updated. :param context: neutron context :param fwp_id: firewall policy ID to check """ fwp = self.get_firewall_policy(context, fwp_id) ing_fwg_ids, eg_fwg_ids = self._get_fwgs_with_policy(context, fwp) for fwg_id in list(set(ing_fwg_ids + eg_fwg_ids)): self._ensure_update_firewall_group(context, fwg_id) def _ensure_update_firewall_rule(self, context, fwr_id): """Checks if the firewall rule can be updated Fetch firewall policy associated to the rule and checks if they can be updated. :param context: neutron context :param fwr_id: firewall policy ID to check """ fwr = self.get_firewall_rule(context, fwr_id) fwp_ids = self._get_policies_with_rule(context, fwr) for fwp_id in fwp_ids: self._ensure_update_firewall_policy(context, fwp_id) def _validate_firewall_policies_for_firewall_group(self, context, fwg): """Validate firewall group and policy owner Check if the firewall policy is not shared, it have the same project owner than the friewall group. :param context: neutron context :param fwg: firewall group to validate """ for policy_type in ['ingress_firewall_policy_id', 'egress_firewall_policy_id']: if fwg.get(policy_type): fwp = self.get_firewall_policy(context, fwg[policy_type]) if fwg['tenant_id'] != fwp['tenant_id'] and not fwp['shared']: raise f_exc.FirewallPolicyConflict( firewall_policy_id=fwg[policy_type]) def _validate_ports_for_firewall_group(self, context, tenant_id, fwg_ports): """Validate firewall group associated ports Check if the firewall group associated ports have the same project owner and is router interface type or a compute layer 2. :param context: neutron context :param tenant_id: firewall group project ID :param fwg_ports: firewall group associated ports """ # TODO(sridar): elevated context and do we want to use public ? for port_id in fwg_ports: port = self._core_plugin.get_port(context, port_id) if port['tenant_id'] != tenant_id: raise f_exc.FirewallGroupPortInvalidProject( port_id=port_id, project_id=port['tenant_id']) device_owner = port.get('device_owner', '') if (device_owner not in [nl_constants.DEVICE_OWNER_ROUTER_INTF] and not device_owner.startswith( nl_constants.DEVICE_OWNER_COMPUTE_PREFIX)): raise f_exc.FirewallGroupPortInvalid(port_id=port_id) if (device_owner.startswith( nl_constants.DEVICE_OWNER_COMPUTE_PREFIX) and not self._is_supported_by_fw_l2_driver(context, port_id)): raise exceptions.FirewallGroupPortNotSupported(port_id=port_id) # TODO(ethuleau): move that check in the driver. Each driver can have # different support def _is_supported_by_fw_l2_driver(self, context, port_id): """Whether this port is supported by firewall l2 driver""" # Re-fetch to get up-to-date data from db port = self._core_plugin.get_port(context, id=port_id) # Skip port binding is unbound or failed if port[pb_def.VIF_TYPE] in [pb_def.VIF_TYPE_UNBOUND, pb_def.VIF_TYPE_BINDING_FAILED]: return False if not port['port_security_enabled']: return True if port[pb_def.VIF_TYPE] == pb_def.VIF_TYPE_OVS: # TODO(annp): remove these lines after we fully support for hybrid # port if not port[pb_def.VIF_DETAILS][pb_def.OVS_HYBRID_PLUG]: return True LOG.warning("Doesn't support hybrid port at the moment") else: LOG.warning("Doesn't support vif type %s", port[pb_def.VIF_TYPE]) return False def _validate_if_firewall_group_on_ports(self, context, firewall_group, id=None): """Validate if ports are not associated with any firewall_group. If any of the ports in the list is already associated with a firewall group, raise an exception else just return. :param context: neutron context :param fwg: firewall group to validate """ if 'ports' not in firewall_group or not firewall_group['ports']: return filters = { 'tenant_id': [firewall_group['tenant_id']], 'ports': firewall_group['ports'], } ports_in_use = set() for fwg in self.get_firewall_groups(context, filters=filters): if id is not None and fwg['id'] == id: continue ports_in_use |= set(fwg.get('ports', [])) & \ set(firewall_group['ports']) if ports_in_use: raise f_exc.FirewallGroupPortInUse(port_ids=list(ports_in_use)) def _get_fwgs_with_policy(self, context, firewall_policy): """List firewall group IDs which use a firewall policy List all firewall group IDs which have the given firewall policy as ingress or egress. :param context: neutron context :param firewall_policy: firewall policy to filter """ filters = { 'tenant_id': [firewall_policy['tenant_id']], 'ingress_firewall_policy_id': [firewall_policy['id']], } ingress_fwp_ids = [fwg['id'] for fwg in self.get_firewall_groups( context, filters=filters)] filters = { 'tenant_id': [firewall_policy['tenant_id']], 'egress_firewall_policy_id': [firewall_policy['id']], } egress_fwp_ids = [fwg['id'] for fwg in self.get_firewall_groups( context, filters=filters)] return ingress_fwp_ids, egress_fwp_ids def _get_policies_with_rule(self, context, firewall_rule): filters = { 'tenant_id': [firewall_rule['tenant_id']], 'firewall_rules': [firewall_rule['id']], } return [fwp['id'] for fwp in self.get_firewall_policies( context, filters=filters)] def _validate_insert_remove_rule_request(self, rule_info): """Validate rule_info dict Check that all mandatory fields are present, otherwise raise proper exception. """ if not rule_info or 'firewall_rule_id' not in rule_info: raise f_exc.FirewallRuleInfoMissing() # Validator doesn't return anything if the check passes if validators.validate_uuid(rule_info['firewall_rule_id']): raise f_exc.FirewallRuleNotFound( firewall_rule_id=rule_info['firewall_rule_id']) @registry.receives(resources.PORT, [events.AFTER_UPDATE]) def handle_update_port(self, resource, event, trigger, **kwargs): updated_port = kwargs['port'] if not updated_port['device_owner'].startswith( nl_constants.DEVICE_OWNER_COMPUTE_PREFIX): return if (kwargs.get('original_port')[pb_def.VIF_TYPE] != pb_def.VIF_TYPE_UNBOUND): # Checking newly vm port binding allows us to avoid call to DB # when a port update_event like restart, setting name, etc... # Moreover, that will help us in case of tenant admin wants to # only attach security group to vm port. return context = kwargs['context'] port_id = updated_port['id'] # Check port is supported by firewall l2 driver or not if not self._is_supported_by_fw_l2_driver(context, port_id): return project_id = updated_port['project_id'] fwgs = self.get_firewall_groups( context, filters={ 'tenant_id': [project_id], 'name': [fwaas_constants.DEFAULT_FWG], }, fields=['id', 'ports'], ) if len(fwgs) != 1: # Cannot found default Firewall Group, abandon LOG.warning("Cannot found default firewall group of project %s", project_id) return default_fwg = fwgs[0] # Add default firewall group to the port port_ids = default_fwg.get('ports', []) + [port_id] try: self.update_firewall_group(context, default_fwg['id'], {'firewall_group': {'ports': port_ids}}) except f_exc.FirewallGroupPortInUse: LOG.warning("Port %s has been already associated with default " "firewall group %s and skip association", port_id, default_fwg['id']) # Firewall Group @log_helpers.log_method_call def create_firewall_group(self, context, firewall_group): firewall_group = firewall_group['firewall_group'] ports = firewall_group.get('ports', []) self._validate_firewall_policies_for_firewall_group(context, firewall_group) # Validate ports owner type and project self._validate_ports_for_firewall_group(context, firewall_group['tenant_id'], ports) self._validate_if_firewall_group_on_ports(context, firewall_group) return self.driver.create_firewall_group(context, firewall_group) @log_helpers.log_method_call def delete_firewall_group(self, context, id): # if no such group exists -> don't raise an exception according to # 80fe2ba1, return None try: fwg = self.get_firewall_group(context, id) except f_exc.FirewallGroupNotFound: return if fwg['status'] == nl_constants.ACTIVE: raise f_exc.FirewallGroupInUse(firewall_id=id) self.driver.delete_firewall_group(context, id) @log_helpers.log_method_call def get_firewall_group(self, context, id, fields=None): return self.driver.get_firewall_group(context, id, fields=fields) @log_helpers.log_method_call def get_firewall_groups(self, context, filters=None, fields=None): return self.driver.get_firewall_groups(context, filters, fields) @log_helpers.log_method_call def update_firewall_group(self, context, id, firewall_group): firewall_group = firewall_group['firewall_group'] ports = firewall_group.get('ports', []) old_firewall_group = self._ensure_update_firewall_group(context, id) firewall_group['tenant_id'] = old_firewall_group['tenant_id'] self._validate_firewall_policies_for_firewall_group(context, firewall_group) # Validate ports owner type and project self._validate_ports_for_firewall_group(context, firewall_group['tenant_id'], ports) self._validate_if_firewall_group_on_ports(context, firewall_group, id=id) return self.driver.update_firewall_group(context, id, firewall_group) # Firewall Policy @log_helpers.log_method_call def create_firewall_policy(self, context, firewall_policy): firewall_policy = firewall_policy['firewall_policy'] return self.driver.create_firewall_policy(context, firewall_policy) @log_helpers.log_method_call def delete_firewall_policy(self, context, id): self.driver.delete_firewall_policy(context, id) @log_helpers.log_method_call def get_firewall_policy(self, context, id, fields=None): return self.driver.get_firewall_policy(context, id, fields) @log_helpers.log_method_call def get_firewall_policies(self, context, filters=None, fields=None): return self.driver.get_firewall_policies(context, filters, fields) @log_helpers.log_method_call def update_firewall_policy(self, context, id, firewall_policy): firewall_policy = firewall_policy['firewall_policy'] self._ensure_update_firewall_policy(context, id) return self.driver.update_firewall_policy(context, id, firewall_policy) # Firewall Rule @log_helpers.log_method_call def create_firewall_rule(self, context, firewall_rule): firewall_rule = firewall_rule['firewall_rule'] return self.driver.create_firewall_rule(context, firewall_rule) @log_helpers.log_method_call def delete_firewall_rule(self, context, id): self.driver.delete_firewall_rule(context, id) @log_helpers.log_method_call def get_firewall_rule(self, context, id, fields=None): return self.driver.get_firewall_rule(context, id, fields) @log_helpers.log_method_call def get_firewall_rules(self, context, filters=None, fields=None): return self.driver.get_firewall_rules(context, filters, fields) @log_helpers.log_method_call def update_firewall_rule(self, context, id, firewall_rule): firewall_rule = firewall_rule['firewall_rule'] self._ensure_update_firewall_rule(context, id) return self.driver.update_firewall_rule(context, id, firewall_rule) @log_helpers.log_method_call def insert_rule(self, context, policy_id, rule_info): self._ensure_update_firewall_policy(context, policy_id) self._validate_insert_remove_rule_request(rule_info) return self.driver.insert_rule(context, policy_id, rule_info) @log_helpers.log_method_call def remove_rule(self, context, policy_id, rule_info): self._ensure_update_firewall_policy(context, policy_id) self._validate_insert_remove_rule_request(rule_info) return self.driver.remove_rule(context, policy_id, rule_info)