507392be7d
This patch implements logging driver in L3 for firewall group base discussed on the patch [1] [1] https://review.openstack.org/#/c/509725/ Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com> Co-Authored-By: Kim Bao Long <longkb@vn.fujitsu.com> Partial-Bug: #1720727 Change-Id: I1194a622c546068991f44559e3f9e343430fd6f9
438 lines
18 KiB
Python
438 lines
18 KiB
Python
# 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 import service
|
|
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 constants as plugin_const
|
|
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
|
|
from neutron_fwaas.services.firewall.service_drivers import driver_api
|
|
from neutron_fwaas.services.logapi.agents.drivers.iptables \
|
|
import driver as logging_driver
|
|
|
|
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]
|
|
|
|
# start rpc listener if driver required
|
|
if isinstance(self.driver, driver_api.FirewallDriverRPCMixin):
|
|
rpc_worker = service.RpcWorker([self], worker_process_count=0)
|
|
self.add_worker(rpc_worker)
|
|
|
|
log_plugin = directory.get_plugin(plugin_const.LOG_API)
|
|
logging_driver.register()
|
|
# If log_plugin was loaded before firewall plugin
|
|
if log_plugin:
|
|
# Register logging driver with LoggingServiceDriverManager again
|
|
log_plugin.driver_manager.register_driver(logging_driver.DRIVER)
|
|
|
|
def start_rpc_listeners(self):
|
|
return self.driver.start_rpc_listener()
|
|
|
|
@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)
|