neutron-fwaas/neutron_fwaas/db/firewall/v2/firewall_db_v2.py

1080 lines
49 KiB
Python

# Copyright (c) 2016
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import netaddr
from neutron.db import _model_query as model_query
from neutron.db import common_db_mixin
from neutron_lib.api.definitions import constants as fw_const
from neutron_lib import constants as nl_constants
from neutron_lib.db import api as db_api
from neutron_lib.db import constants as db_constants
from neutron_lib.db import model_base
from neutron_lib import exceptions
from neutron_lib.exceptions import firewall_v2 as f_exc
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy import or_
from sqlalchemy import orm
from sqlalchemy.orm import exc
from neutron_fwaas.common import fwaas_constants as const
LOG = logging.getLogger(__name__)
class FirewallDefaultParameterExists(exceptions.InUse):
"""Default Firewall Parameter conflict exception
Occurs when user creates/updates any existing firewall resource with
reserved parameter names.
"""
message = ("Operation cannot be performed since '%(name)s' "
"is a reserved name for %(resource_type)s.")
class FirewallDefaultObjectUpdateRestricted(FirewallDefaultParameterExists):
message = ("Operation cannot be performed on default object "
"'%(resource_id)s' of type %(resource_type)s.")
class HasName(object):
name = sa.Column(sa.String(db_constants.NAME_FIELD_SIZE))
class HasDescription(object):
description = sa.Column(
sa.String(db_constants.LONG_DESCRIPTION_FIELD_SIZE))
class FirewallRuleV2(model_base.BASEV2, model_base.HasId, HasName,
HasDescription, model_base.HasProject):
__tablename__ = "firewall_rules_v2"
shared = sa.Column(sa.Boolean)
protocol = sa.Column(sa.String(40))
ip_version = sa.Column(sa.Integer)
source_ip_address = sa.Column(sa.String(46))
destination_ip_address = sa.Column(sa.String(46))
source_port_range_min = sa.Column(sa.Integer)
source_port_range_max = sa.Column(sa.Integer)
destination_port_range_min = sa.Column(sa.Integer)
destination_port_range_max = sa.Column(sa.Integer)
action = sa.Column(sa.Enum('allow', 'deny', 'reject',
name='firewallrules_action'))
enabled = sa.Column(sa.Boolean)
class FirewallGroup(model_base.BASEV2, model_base.HasId, HasName,
HasDescription, model_base.HasProject):
__tablename__ = 'firewall_groups_v2'
port_associations = orm.relationship(
'FirewallGroupPortAssociation',
backref=orm.backref('firewall_group_port_associations_v2',
cascade='all, delete'))
name = sa.Column(sa.String(db_constants.NAME_FIELD_SIZE))
description = sa.Column(
sa.String(db_constants.LONG_DESCRIPTION_FIELD_SIZE))
ingress_firewall_policy_id = sa.Column(
sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_policies_v2.id'))
egress_firewall_policy_id = sa.Column(
sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_policies_v2.id'))
admin_state_up = sa.Column(sa.Boolean)
status = sa.Column(sa.String(db_constants.STATUS_FIELD_SIZE))
shared = sa.Column(sa.Boolean)
class DefaultFirewallGroup(model_base.BASEV2, model_base.HasProjectPrimaryKey):
__tablename__ = "default_firewall_groups"
firewall_group_id = sa.Column(sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_groups_v2.id',
ondelete="CASCADE"),
nullable=False)
firewall_group = orm.relationship(
FirewallGroup, lazy='joined',
backref=orm.backref('default_firewall_group', cascade='all,delete'),
primaryjoin="FirewallGroup.id==DefaultFirewallGroup.firewall_group_id",
)
class FirewallGroupPortAssociation(model_base.BASEV2):
__tablename__ = 'firewall_group_port_associations_v2'
firewall_group_id = sa.Column(sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_groups_v2.id',
ondelete="CASCADE"),
primary_key=True)
port_id = sa.Column(sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
unique=True,
primary_key=True)
class FirewallPolicyRuleAssociation(model_base.BASEV2):
"""Tracks FW Policy and Rule(s) Association"""
__tablename__ = 'firewall_policy_rule_associations_v2'
firewall_policy_id = sa.Column(sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_policies_v2.id',
ondelete="CASCADE"),
primary_key=True)
firewall_rule_id = sa.Column(sa.String(db_constants.UUID_FIELD_SIZE),
sa.ForeignKey('firewall_rules_v2.id',
ondelete="CASCADE"),
primary_key=True)
position = sa.Column(sa.Integer)
class FirewallPolicy(model_base.BASEV2, model_base.HasId, HasName,
HasDescription, model_base.HasProject):
__tablename__ = 'firewall_policies_v2'
name = sa.Column(sa.String(db_constants.NAME_FIELD_SIZE))
description = sa.Column(
sa.String(db_constants.LONG_DESCRIPTION_FIELD_SIZE))
rule_count = sa.Column(sa.Integer)
audited = sa.Column(sa.Boolean)
rule_associations = orm.relationship(
FirewallPolicyRuleAssociation,
backref=orm.backref('firewall_policies_v2', cascade='all, delete'),
order_by='FirewallPolicyRuleAssociation.position',
collection_class=ordering_list('position', count_from=1))
shared = sa.Column(sa.Boolean)
def _list_firewall_groups_result_filter_hook(query, filters):
values = filters and filters.get('ports', [])
if values:
query = query.join(FirewallGroupPortAssociation)
query = query.filter(FirewallGroupPortAssociation.port_id.in_(values))
return query
def _list_firewall_policies_result_filter_hook(query, filters):
values = filters and filters.get('firewall_rules', [])
if values:
query = query.join(FirewallPolicyRuleAssociation)
query = query.filter(
FirewallPolicyRuleAssociation.firewall_rule_id.in_(values))
return query
class FirewallPluginDb(common_db_mixin.CommonDbMixin):
def __new__(cls, *args, **kwargs):
model_query.register_hook(
FirewallGroup,
"firewall_group_v2_filter_by_port_association",
query_hook=None,
filter_hook=None,
result_filters=_list_firewall_groups_result_filter_hook)
model_query.register_hook(
FirewallPolicy,
"firewall_policy_v2_filter_by_firewall_rule_association",
query_hook=None,
filter_hook=None,
result_filters=_list_firewall_policies_result_filter_hook)
return super(FirewallPluginDb, cls).__new__(cls, *args, **kwargs)
def _get_firewall_group(self, context, id):
try:
return self._get_by_id(context, FirewallGroup, id)
except exc.NoResultFound:
raise f_exc.FirewallGroupNotFound(firewall_id=id)
def _get_firewall_policy(self, context, id):
try:
return self._get_by_id(context, FirewallPolicy, id)
except exc.NoResultFound:
raise f_exc.FirewallPolicyNotFound(firewall_policy_id=id)
def _get_firewall_rule(self, context, id):
try:
return self._get_by_id(context, FirewallRuleV2, id)
except exc.NoResultFound:
raise f_exc.FirewallRuleNotFound(firewall_rule_id=id)
def _validate_fwr_protocol_parameters(self, fwr, fwr_db=None):
protocol = fwr.get('protocol', None)
if fwr_db and not protocol:
protocol = fwr_db.protocol
if protocol not in (nl_constants.PROTO_NAME_TCP,
nl_constants.PROTO_NAME_UDP):
if (fwr.get('source_port', None) or
fwr.get('destination_port', None)):
raise f_exc.FirewallRuleInvalidICMPParameter(
param="Source, destination port")
def _validate_fwr_src_dst_ip_version(self, fwr, fwr_db=None):
src_version = dst_version = None
if fwr.get('source_ip_address', None):
src_version = netaddr.IPNetwork(fwr['source_ip_address']).version
if fwr.get('destination_ip_address', None):
dst_version = netaddr.IPNetwork(
fwr['destination_ip_address']).version
rule_ip_version = fwr.get('ip_version', None)
if not rule_ip_version and fwr_db:
rule_ip_version = fwr_db.ip_version
if ((src_version and src_version != rule_ip_version) or
(dst_version and dst_version != rule_ip_version)):
raise f_exc.FirewallIpAddressConflict()
def _validate_fwr_port_range(self, min_port, max_port):
if int(min_port) > int(max_port):
port_range = '%s:%s' % (min_port, max_port)
raise f_exc.FirewallRuleInvalidPortValue(port=port_range)
def _get_min_max_ports_from_range(self, port_range):
if not port_range:
return [None, None]
min_port, sep, max_port = port_range.partition(":")
if not max_port:
max_port = min_port
self._validate_fwr_port_range(min_port, max_port)
return [int(min_port), int(max_port)]
def _get_port_range_from_min_max_ports(self, min_port, max_port):
if not min_port:
return None
if min_port == max_port:
return str(min_port)
self._validate_fwr_port_range(min_port, max_port)
return '%s:%s' % (min_port, max_port)
def _make_firewall_rule_dict(self, firewall_rule, fields=None,
policies=None):
src_port_range = self._get_port_range_from_min_max_ports(
firewall_rule['source_port_range_min'],
firewall_rule['source_port_range_max'])
dst_port_range = self._get_port_range_from_min_max_ports(
firewall_rule['destination_port_range_min'],
firewall_rule['destination_port_range_max'])
res = {'id': firewall_rule['id'],
'tenant_id': firewall_rule['tenant_id'],
'name': firewall_rule['name'],
'description': firewall_rule['description'],
'protocol': firewall_rule['protocol'],
'firewall_policy_id': policies,
'ip_version': firewall_rule['ip_version'],
'source_ip_address': firewall_rule['source_ip_address'],
'destination_ip_address':
firewall_rule['destination_ip_address'],
'source_port': src_port_range,
'destination_port': dst_port_range,
'action': firewall_rule['action'],
'enabled': firewall_rule['enabled'],
'shared': firewall_rule['shared']}
return self._fields(res, fields)
def _make_firewall_policy_dict(self, firewall_policy, fields=None):
fw_rules = [
rule_association.firewall_rule_id
for rule_association in firewall_policy['rule_associations']]
res = {'id': firewall_policy['id'],
'tenant_id': firewall_policy['tenant_id'],
'name': firewall_policy['name'],
'description': firewall_policy['description'],
'audited': firewall_policy['audited'],
'firewall_rules': fw_rules,
'shared': firewall_policy['shared']}
return self._fields(res, fields)
def _make_firewall_group_dict(self, firewall_group_db, fields=None):
fwg_ports = [port_assoc.port_id for port_assoc in
firewall_group_db.port_associations]
res = {'id': firewall_group_db['id'],
'tenant_id': firewall_group_db['tenant_id'],
'name': firewall_group_db['name'],
'description': firewall_group_db['description'],
'ingress_firewall_policy_id':
firewall_group_db['ingress_firewall_policy_id'],
'egress_firewall_policy_id':
firewall_group_db['egress_firewall_policy_id'],
'admin_state_up': firewall_group_db['admin_state_up'],
'ports': fwg_ports,
'status': firewall_group_db['status'],
'shared': firewall_group_db['shared']}
return self._fields(res, fields)
def _get_policy_ordered_rules(self, context, policy_id):
query = (context.session.query(FirewallRuleV2)
.join(FirewallPolicyRuleAssociation)
.filter_by(firewall_policy_id=policy_id)
.order_by(FirewallPolicyRuleAssociation.position))
return [self._make_firewall_rule_dict(rule) for rule in query]
def make_firewall_group_dict_with_rules(self, context, firewall_group_id):
firewall_group = self.get_firewall_group(context, firewall_group_id)
ingress_policy_id = firewall_group['ingress_firewall_policy_id']
if ingress_policy_id:
firewall_group['ingress_rule_list'] = (
self._get_policy_ordered_rules(context, ingress_policy_id))
else:
firewall_group['ingress_rule_list'] = []
egress_policy_id = firewall_group['egress_firewall_policy_id']
if egress_policy_id:
firewall_group['egress_rule_list'] = (
self._get_policy_ordered_rules(context, egress_policy_id))
else:
firewall_group['egress_rule_list'] = []
return firewall_group
def _check_firewall_rule_conflict(self, fwr_db, fwp_db):
if not fwr_db['shared']:
if fwr_db['tenant_id'] != fwp_db['tenant_id']:
raise f_exc.FirewallRuleConflict(
firewall_rule_id=fwr_db['id'],
project_id=fwr_db['tenant_id'])
def _process_rule_for_policy(self, context, firewall_policy_id,
firewall_rule_id, position, association_db):
with context.session.begin(subtransactions=True):
fwp_query = context.session.query(
FirewallPolicy).with_lockmode('update')
fwp_db = fwp_query.filter_by(id=firewall_policy_id).one()
if position:
# Note that although position numbering starts at 1,
# internal ordering of the list starts at 0, so we compensate.
fwp_db.rule_associations.insert(
position - 1,
FirewallPolicyRuleAssociation(
firewall_rule_id=firewall_rule_id))
else:
fwp_db.rule_associations.remove(association_db)
context.session.delete(association_db)
fwp_db.rule_associations.reorder()
fwp_db.audited = False
return self._make_firewall_policy_dict(fwp_db)
def _get_policy_rule_association_query(self, context, firewall_policy_id,
firewall_rule_id):
fwpra_query = context.session.query(FirewallPolicyRuleAssociation)
return fwpra_query.filter_by(firewall_policy_id=firewall_policy_id,
firewall_rule_id=firewall_rule_id)
def _ensure_rule_not_already_associated(self, context, firewall_policy_id,
firewall_rule_id):
"""Checks that a rule is not already associated with a particular
policy. If it is the function will throw an exception.
"""
try:
self._get_policy_rule_association_query(
context, firewall_policy_id, firewall_rule_id).one()
raise f_exc.FirewallRuleAlreadyAssociated(
firewall_rule_id=firewall_rule_id,
firewall_policy_id=firewall_policy_id)
except exc.NoResultFound:
return
def _get_policy_rule_association(self, context, firewall_policy_id,
firewall_rule_id):
"""Returns the association between a firewall rule and a firewall
policy. Throws an exception if the assocition does not exist.
"""
try:
return self._get_policy_rule_association_query(
context, firewall_policy_id, firewall_rule_id).one()
except exc.NoResultFound:
raise f_exc.FirewallRuleNotAssociatedWithPolicy(
firewall_rule_id=firewall_rule_id,
firewall_policy_id=firewall_policy_id)
def _create_default_firewall_rules(self, context, tenant_id):
# NOTE(xgerman) Maybe generating the final set of rules from a
# configuration file makes sense. Can be done some time later
# 1. Drop any IPv4 packets for ingress traffic
in_fwr_v4 = {
'description': 'default ingress rule for IPv4',
'name': 'default ingress ipv4 (deny all)',
'shared': False,
'protocol': None,
'tenant_id': tenant_id,
'ip_version': nl_constants.IP_VERSION_4,
'action': fw_const.FWAAS_DENY,
'enabled': True,
'source_port': None,
'source_ip_address': None,
'destination_port': None,
'destination_ip_address': None,
}
# 2. Drop any IPv6 packets for ingress traffic
in_fwr_v6 = copy.deepcopy(in_fwr_v4)
in_fwr_v6['description'] = 'default ingress rule for IPv6'
in_fwr_v6['name'] = 'default ingress ipv6 (deny all)'
in_fwr_v6['ip_version'] = nl_constants.IP_VERSION_6
# 3. Allow any IPv4 packets for egress traffic
eg_fwr_v4 = copy.deepcopy(in_fwr_v4)
eg_fwr_v4['description'] = 'default egress rule for IPv4'
eg_fwr_v4['action'] = fw_const.FWAAS_ALLOW
eg_fwr_v4['name'] = 'default egress ipv4 (allow all)'
# 4. Allow any IPv6 packets for egress traffic
eg_fwr_v6 = copy.deepcopy(in_fwr_v6)
eg_fwr_v6['description'] = 'default egress rule for IPv6'
eg_fwr_v6['name'] = 'default egress ipv6 (allow all)'
eg_fwr_v6['action'] = fw_const.FWAAS_ALLOW
return {
'in_ipv4': self.create_firewall_rule(context, in_fwr_v4)['id'],
'in_ipv6': self.create_firewall_rule(context, in_fwr_v6)['id'],
'eg_ipv4': self.create_firewall_rule(context, eg_fwr_v4)['id'],
'eg_ipv6': self.create_firewall_rule(context, eg_fwr_v6)['id'],
}
def create_firewall_rule(self, context, firewall_rule):
fwr = firewall_rule
self._validate_fwr_protocol_parameters(fwr)
self._validate_fwr_src_dst_ip_version(fwr)
if not fwr['protocol'] and (fwr['source_port'] or
fwr['destination_port']):
raise f_exc.FirewallRuleWithPortWithoutProtocolInvalid()
src_port_min, src_port_max = self._get_min_max_ports_from_range(
fwr['source_port'])
dst_port_min, dst_port_max = self._get_min_max_ports_from_range(
fwr['destination_port'])
with context.session.begin(subtransactions=True):
fwr_db = FirewallRuleV2(
id=uuidutils.generate_uuid(),
tenant_id=fwr['tenant_id'],
name=fwr['name'],
description=fwr['description'],
protocol=fwr['protocol'],
ip_version=fwr['ip_version'],
source_ip_address=fwr['source_ip_address'],
destination_ip_address=fwr['destination_ip_address'],
source_port_range_min=src_port_min,
source_port_range_max=src_port_max,
destination_port_range_min=dst_port_min,
destination_port_range_max=dst_port_max,
action=fwr['action'],
enabled=fwr['enabled'],
shared=fwr['shared'])
context.session.add(fwr_db)
return self._make_firewall_rule_dict(fwr_db)
def update_firewall_rule(self, context, id, firewall_rule):
fwr = firewall_rule
fwr_db = self._get_firewall_rule(context, id)
self._validate_fwr_protocol_parameters(fwr, fwr_db=fwr_db)
self._validate_fwr_src_dst_ip_version(fwr, fwr_db=fwr_db)
if 'source_port' in fwr:
src_port_min, src_port_max = self._get_min_max_ports_from_range(
fwr['source_port'])
fwr['source_port_range_min'] = src_port_min
fwr['source_port_range_max'] = src_port_max
del fwr['source_port']
if 'destination_port' in fwr:
dst_port_min, dst_port_max = self._get_min_max_ports_from_range(
fwr['destination_port'])
fwr['destination_port_range_min'] = dst_port_min
fwr['destination_port_range_max'] = dst_port_max
del fwr['destination_port']
with context.session.begin(subtransactions=True):
protocol = fwr.get('protocol', fwr_db['protocol'])
if not protocol:
sport = fwr.get('source_port_range_min',
fwr_db['source_port_range_min'])
dport = fwr.get('destination_port_range_min',
fwr_db['destination_port_range_min'])
if sport or dport:
raise f_exc.FirewallRuleWithPortWithoutProtocolInvalid()
fwr_db.update(fwr)
# if the rule on a policy, fix audited flag
fwp_ids = self.get_policies_with_rule(context, id)
for fwp_id in fwp_ids:
fwp_db = self._get_firewall_policy(context, fwp_id)
fwp_db['audited'] = False
return self._make_firewall_rule_dict(fwr_db)
def delete_firewall_rule(self, context, id):
with context.session.begin(subtransactions=True):
fwr = self._get_firewall_rule(context, id)
# make sure rule is not associated with any policy
if self.get_policies_with_rule(context, id):
raise f_exc.FirewallRuleInUse(firewall_rule_id=id)
context.session.delete(fwr)
def insert_rule(self, context, id, rule_info):
firewall_rule_id = rule_info['firewall_rule_id']
# ensure rule is not already assigned to the policy
self._ensure_rule_not_already_associated(context, id, firewall_rule_id)
insert_before = True
ref_firewall_rule_id = None
if 'insert_before' in rule_info:
ref_firewall_rule_id = rule_info['insert_before']
if not ref_firewall_rule_id and 'insert_after' in rule_info:
# If insert_before is set, we will ignore insert_after.
ref_firewall_rule_id = rule_info['insert_after']
insert_before = False
with context.session.begin(subtransactions=True):
fwr_db = self._get_firewall_rule(context, firewall_rule_id)
fwp_db = self._get_firewall_policy(context, id)
self._check_firewall_rule_conflict(fwr_db, fwp_db)
if ref_firewall_rule_id:
# If reference_firewall_rule_id is set, the new rule
# is inserted depending on the value of insert_before.
# If insert_before is set, the new rule is inserted before
# reference_firewall_rule_id, and if it is not set the new
# rule is inserted after reference_firewall_rule_id.
fwpra_db = self._get_policy_rule_association(
context, id, ref_firewall_rule_id)
if insert_before:
position = fwpra_db.position
else:
position = fwpra_db.position + 1
else:
# If reference_firewall_rule_id is not set, it is assumed
# that the new rule needs to be inserted at the top.
# insert_before field is ignored.
# So default insertion is always at the top.
# Also note that position numbering starts at 1.
position = 1
return self._process_rule_for_policy(context, id, firewall_rule_id,
position, None)
def remove_rule(self, context, id, rule_info):
firewall_rule_id = rule_info['firewall_rule_id']
with context.session.begin(subtransactions=True):
self._get_firewall_rule(context, firewall_rule_id)
fwpra_db = self._get_policy_rule_association(context, id,
firewall_rule_id)
return self._process_rule_for_policy(context, id, firewall_rule_id,
None, fwpra_db)
def get_firewall_rule(self, context, id, fields=None):
fwr = self._get_firewall_rule(context, id)
policies = self.get_policies_with_rule(context, id) or None
return self._make_firewall_rule_dict(fwr, fields, policies=policies)
def get_firewall_rules(self, context, filters=None, fields=None):
return self._get_collection(context, FirewallRuleV2,
self._make_firewall_rule_dict,
filters=filters, fields=fields)
def _get_rules_in_policy(self, context, fwpid):
"""Gets rules in a firewall policy"""
with context.session.begin(subtransactions=True):
fw_pol_rule_qry = context.session.query(
FirewallPolicyRuleAssociation).filter_by(
firewall_policy_id=fwpid)
fwp_rules = [entry.firewall_rule_id for entry in fw_pol_rule_qry]
return fwp_rules
def get_policies_with_rule(self, context, fwrid):
"""Gets rules in a firewall policy"""
with context.session.begin(subtransactions=True):
fw_pol_rule_qry = context.session.query(
FirewallPolicyRuleAssociation).filter_by(
firewall_rule_id=fwrid)
fwps = [entry.firewall_policy_id for entry in fw_pol_rule_qry]
return fwps
def _set_rules_in_policy_rule_assoc(self, context, fwp_db, fwp):
# Pull the rules and add it to policy - rule association table
# Set the position (this can be used in the making the dict)
# might be good to track the last position
rule_id_list = fwp['firewall_rules']
if not rule_id_list:
return
position = 0
with context.session.begin(subtransactions=True):
for rule_id in rule_id_list:
fw_pol_rul_db = FirewallPolicyRuleAssociation(
firewall_policy_id=fwp_db['id'],
firewall_rule_id=rule_id,
position=position)
context.session.add(fw_pol_rul_db)
position += 1
def _check_rules_for_policy_is_valid(self, context, fwp, fwp_db,
rule_id_list, filters):
rules_in_fwr_db = self._get_collection_query(context, FirewallRuleV2,
filters=filters)
rules_dict = dict((fwr_db['id'], fwr_db) for fwr_db in rules_in_fwr_db)
for fwrule_id in rule_id_list:
if fwrule_id not in rules_dict:
# Bail as soon as we find an invalid rule.
raise f_exc.FirewallRuleNotFound(
firewall_rule_id=fwrule_id)
if 'shared' in fwp:
if fwp['shared'] and not rules_dict[fwrule_id]['shared']:
raise f_exc.FirewallRuleSharingConflict(
firewall_rule_id=fwrule_id,
firewall_policy_id=fwp_db['id'])
elif fwp_db['shared'] and not rules_dict[fwrule_id]['shared']:
raise f_exc.FirewallRuleSharingConflict(
firewall_rule_id=fwrule_id,
firewall_policy_id=fwp_db['id'])
else:
# the policy is not shared, the rule and policy should be in
# the same project if the rule is not shared.
if not rules_dict[fwrule_id]['shared']:
if (rules_dict[fwrule_id]['tenant_id'] != fwp_db[
'tenant_id']):
raise f_exc.FirewallRuleConflict(
firewall_rule_id=fwrule_id,
project_id=rules_dict[fwrule_id]['tenant_id'])
def _check_if_rules_shared_for_policy_shared(self, context, fwp_db, fwp):
if fwp['shared']:
rules_in_db = fwp_db.rule_associations
for entry in rules_in_db:
fwr_db = self._get_firewall_rule(context,
entry.firewall_rule_id)
if not fwp_db['shared']:
raise f_exc.FirewallPolicySharingConflict(
firewall_rule_id=fwr_db['id'],
firewall_policy_id=fwp_db['id'])
def get_fwgs_with_policy(self, context, fwp_id):
with context.session.begin(subtransactions=True):
fwg_ing_pol_qry = context.session.query(
FirewallGroup).filter_by(
ingress_firewall_policy_id=fwp_id)
ing_fwg_ids = [entry.id for entry in fwg_ing_pol_qry]
fwg_eg_pol_qry = context.session.query(
FirewallGroup).filter_by(
egress_firewall_policy_id=fwp_id)
eg_fwg_ids = [entry.id for entry in fwg_eg_pol_qry]
return ing_fwg_ids, eg_fwg_ids
def _check_fwgs_associated_with_policy_in_same_project(self, context,
fwp_id,
fwp_tenant_id):
with context.session.begin(subtransactions=True):
fwg_with_fwp_id_db = context.session.query(FirewallGroup).filter(
or_(FirewallGroup.ingress_firewall_policy_id == fwp_id,
FirewallGroup.egress_firewall_policy_id == fwp_id))
for entry in fwg_with_fwp_id_db:
if entry.tenant_id != fwp_tenant_id:
raise f_exc.FirewallPolicyInUse(
firewall_policy_id=fwp_id)
def _delete_all_rules_from_policy(self, context, fwp_db):
"""Deletes all FirewallPolicyRuleAssociation objects
fwp_db is an DB dict representing firewall policy.
Returns a dictionary with updated rule_associations.
"""
for rule_id in [rule_assoc.firewall_rule_id
for rule_assoc in fwp_db['rule_associations']]:
fwpra_db = self._get_policy_rule_association(
context, fwp_db['id'], rule_id)
fwp_db.rule_associations.remove(fwpra_db)
context.session.delete(fwpra_db)
fwp_db.rule_associations = []
return fwp_db
def _set_rules_for_policy(self, context, firewall_policy_db, fwp):
rule_id_list = fwp['firewall_rules']
fwp_db = firewall_policy_db
with context.session.begin(subtransactions=True):
if not rule_id_list:
self._delete_all_rules_from_policy(context, fwp_db)
return
# We will first check if the new list of rules is valid
filters = {'firewall_rule_id': [r_id for r_id in rule_id_list]}
# Run a validation on the Firewall Rules table
self._check_rules_for_policy_is_valid(context, fwp, fwp_db,
rule_id_list, filters)
# new rules are valid, lets delete the old association
self._delete_all_rules_from_policy(context, fwp_db)
# and add in the new association
self._set_rules_in_policy_rule_assoc(context, fwp_db, fwp)
# we need care about the associations related with this policy
# and its rules only.
filters['firewall_policy_id'] = [fwp_db['id']]
rules_in_fpol_rul_db = self._get_collection_query(
context,
FirewallPolicyRuleAssociation,
filters=filters)
rules_dict = dict((fpol_rul_db['firewall_rule_id'], fpol_rul_db)
for fpol_rul_db in rules_in_fpol_rul_db)
fwp_db.rule_associations = []
for fwrule_id in rule_id_list:
fwp_db.rule_associations.append(rules_dict[fwrule_id])
fwp_db.rule_associations.reorder()
def _create_default_firewall_policy(self, context, tenant_id, policy_type,
**kwargs):
fwrs = kwargs.get('firewall_rules', [])
description = kwargs.get('description', '')
name = (const.DEFAULT_FWP_INGRESS
if policy_type == 'ingress' else const.DEFAULT_FWP_EGRESS)
firewall_policy = {
'name': name,
'description': description,
'audited': False,
'shared': False,
'firewall_rules': fwrs,
'tenant_id': tenant_id,
}
return self._do_create_firewall_policy(context, firewall_policy)
def _do_create_firewall_policy(self, context, firewall_policy):
fwp = firewall_policy
with context.session.begin(subtransactions=True):
fwp_db = FirewallPolicy(
id=uuidutils.generate_uuid(),
tenant_id=fwp['tenant_id'],
name=fwp['name'],
description=fwp['description'],
audited=fwp['audited'],
shared=fwp['shared'])
context.session.add(fwp_db)
self._set_rules_for_policy(context, fwp_db, fwp)
return self._make_firewall_policy_dict(fwp_db)
def create_firewall_policy(self, context, firewall_policy):
self._ensure_not_default_resource(firewall_policy, 'firewall_policy')
return self._do_create_firewall_policy(context, firewall_policy)
def update_firewall_policy(self, context, id, firewall_policy):
fwp = firewall_policy
with context.session.begin(subtransactions=True):
fwp_db = self._get_firewall_policy(context, id)
self._ensure_not_default_resource(fwp_db, 'firewall_policy',
action="update")
if not fwp.get('shared', True):
# an update is setting shared to False, make sure associated
# firewall groups are in the same project.
self._check_fwgs_associated_with_policy_in_same_project(
context, id, fwp_db['tenant_id'])
if 'shared' in fwp and 'firewall_rules' not in fwp:
self._check_if_rules_shared_for_policy_shared(
context, fwp_db, fwp)
if 'firewall_rules' in fwp:
self._set_rules_for_policy(context, fwp_db, fwp)
del fwp['firewall_rules']
if 'audited' not in fwp:
fwp['audited'] = False
fwp_db.update(fwp)
return self._make_firewall_policy_dict(fwp_db)
def delete_firewall_policy(self, context, id):
with context.session.begin(subtransactions=True):
fwp_db = self._get_firewall_policy(context, id)
# check if policy in use
qry = context.session.query(FirewallGroup)
if qry.filter_by(ingress_firewall_policy_id=id).first():
raise f_exc.FirewallPolicyInUse(firewall_policy_id=id)
elif qry.filter_by(egress_firewall_policy_id=id).first():
raise f_exc.FirewallPolicyInUse(firewall_policy_id=id)
else:
fwp_db = self._delete_all_rules_from_policy(context, fwp_db)
context.session.delete(fwp_db)
def get_firewall_policy(self, context, id, fields=None):
fwp = self._get_firewall_policy(context, id)
return self._make_firewall_policy_dict(fwp, fields)
def get_firewall_policies(self, context, filters=None, fields=None):
return self._get_collection(context, FirewallPolicy,
self._make_firewall_policy_dict,
filters=filters, fields=fields)
def _set_ports_for_firewall_group(self, context, fwg_db, fwg):
port_id_list = fwg['ports']
if not port_id_list:
return
exc_ports = []
for port_id in port_id_list:
try:
with context.session.begin(subtransactions=True):
fwg_port_db = FirewallGroupPortAssociation(
firewall_group_id=fwg_db['id'],
port_id=port_id)
context.session.add(fwg_port_db)
except db_exc.DBDuplicateEntry:
exc_ports.append(port_id)
if exc_ports:
raise f_exc.FirewallGroupPortInUse(port_ids=exc_ports)
def get_ports_in_firewall_group(self, context, firewall_group_id):
"""Get the Ports associated with the firewall group."""
with context.session.begin(subtransactions=True):
fw_group_port_qry = context.session.query(
FirewallGroupPortAssociation)
fw_group_port_rows = fw_group_port_qry.filter_by(
firewall_group_id=firewall_group_id)
fw_ports = [entry.port_id for entry in fw_group_port_rows]
return fw_ports
def _delete_ports_in_firewall_group(self, context, firewall_group_id):
"""Delete the Ports associated with the firewall group."""
with context.session.begin(subtransactions=True):
fw_group_port_qry = context.session.query(
FirewallGroupPortAssociation)
fw_group_port_qry.filter_by(
firewall_group_id=firewall_group_id).delete()
return
def _get_default_fwg_id(self, context, tenant_id):
"""Returns an id of default firewall group for given tenant or None"""
default_fwg = self._model_query(context, FirewallGroup).filter_by(
project_id=tenant_id, name=const.DEFAULT_FWG).first()
if default_fwg:
return default_fwg.id
return None
def get_fwg_attached_to_port(self, context, port_id):
"""Return a firewall group ID that is attached to a given port"""
fwg_port = self._model_query(context, FirewallGroupPortAssociation).\
filter_by(port_id=port_id).first()
if fwg_port:
return fwg_port.firewall_group_id
return None
def get_fwg_ports_in_tenant(self, context, tenant_id):
"""Return a list of ports under a given tenant"""
try:
fwg_id = FirewallGroupPortAssociation.firewall_group_id
with context.session.begin(subtransactions=True):
port_qry = context.session.query(
FirewallGroupPortAssociation.port_id).join(
FirewallGroup, FirewallGroup.id == fwg_id).filter(
FirewallGroup.tenant_id == tenant_id).all()
return list({port for (port,) in port_qry})
except exc.NoResultFound:
return []
def _ensure_default_firewall_group(self, context, tenant_id):
"""Create a default firewall group if one doesn't exist for a tenant
Returns the default firewall group id for a given tenant.
"""
exists = self._get_default_fwg_id(context, tenant_id)
if exists:
return exists
try:
# NOTE(cby): default fwg not created => we try to create it!
with db_api.autonested_transaction(context.session):
fwr_ids = self._create_default_firewall_rules(
context, tenant_id)
ingress_fwp = {
'description': 'Ingress firewall policy',
'firewall_rules': [fwr_ids['in_ipv4'],
fwr_ids['in_ipv6']],
}
egress_fwp = {
'description': 'Egress firewall policy',
'firewall_rules': [fwr_ids['eg_ipv4'],
fwr_ids['eg_ipv6']],
}
ingress_fwp_db = self._create_default_firewall_policy(
context, tenant_id, 'ingress', **ingress_fwp)
egress_fwp_db = self._create_default_firewall_policy(
context, tenant_id, 'egress', **egress_fwp)
fwg = {
'name': const.DEFAULT_FWG,
'tenant_id': tenant_id,
'ingress_firewall_policy_id': ingress_fwp_db['id'],
'egress_firewall_policy_id': egress_fwp_db['id'],
'ports': [],
'shared': False,
'status': nl_constants.INACTIVE,
'admin_state_up': True,
'description': 'Default firewall group',
}
fwg_db = self._create_firewall_group(
context, fwg, default_fwg=True)
context.session.add(DefaultFirewallGroup(
firewall_group_id=fwg_db['id'],
project_id=tenant_id))
return fwg_db['id']
except db_exc.DBDuplicateEntry:
# NOTE(cby): default fwg created concurrently
LOG.debug("Default FWG was concurrently created")
return self._get_default_fwg_id(context, tenant_id)
def _create_firewall_group(self, context, firewall_group,
default_fwg=False):
"""Create a firewall group
If default_fwg is True then a default firewall group is being created
for a given tenant.
"""
fwg = firewall_group
tenant_id = fwg['tenant_id']
if firewall_group.get('status') is None:
fwg['status'] = nl_constants.CREATED
if default_fwg:
# A default firewall group is being created.
default_fwg_id = self._get_default_fwg_id(context, tenant_id)
if default_fwg_id is not None:
# Default fwg for a given tenant exists, fetch it and return
return self.get_firewall_group(context, default_fwg_id)
else:
# An ordinary firewall group is being created BUT let's make sure
# that a default firewall group for given tenant exists
self._ensure_default_firewall_group(context, tenant_id)
with context.session.begin(subtransactions=True):
fwg_db = FirewallGroup(
id=uuidutils.generate_uuid(),
tenant_id=tenant_id,
name=fwg['name'],
description=fwg['description'],
status=fwg['status'],
ingress_firewall_policy_id=fwg['ingress_firewall_policy_id'],
egress_firewall_policy_id=fwg['egress_firewall_policy_id'],
admin_state_up=fwg['admin_state_up'],
shared=fwg['shared'])
context.session.add(fwg_db)
self._set_ports_for_firewall_group(context, fwg_db, fwg)
return self._make_firewall_group_dict(fwg_db)
def create_firewall_group(self, context, firewall_group):
self._ensure_not_default_resource(firewall_group, 'firewall_group')
return self._create_firewall_group(context, firewall_group)
def update_firewall_group(self, context, id, firewall_group):
fwg = firewall_group
# make sure that no group can be updated to have name=default
self._ensure_not_default_resource(fwg, 'firewall_group')
with context.session.begin(subtransactions=True):
fwg_db = self.get_firewall_group(context, id)
if _is_default(fwg_db):
attrs = [
'name', 'description', 'admin_state_up',
'ingress_firewall_policy_id', 'egress_firewall_policy_id'
]
if context.is_admin:
attrs = ['name']
for attr in attrs:
if attr in fwg:
raise FirewallDefaultObjectUpdateRestricted(
resource_type='Firewall Group',
resource_id=fwg_db['id'])
if 'ports' in fwg:
LOG.debug("Ports are updated in Firewall Group")
self._delete_ports_in_firewall_group(context, id)
self._set_ports_for_firewall_group(context, fwg_db, fwg)
del fwg['ports']
# If fwg is empty, skip updating
if fwg:
count = context.session.query(
FirewallGroup).filter_by(id=id).update(fwg)
if not count:
raise f_exc.FirewallGroupNotFound(firewall_id=id)
return self.get_firewall_group(context, id)
def update_firewall_group_status(self, context, id, status, not_in=None):
"""Conditionally update firewall_group status.
Status transition is performed only if firewall is not in the specified
states as defined by 'not_in' list.
"""
# filter in_ wants iterable objects, None isn't.
not_in = not_in or []
with context.session.begin(subtransactions=True):
return (context.session.query(FirewallGroup).
filter(FirewallGroup.id == id).
filter(~FirewallGroup.status.in_(not_in)).
update({'status': status}, synchronize_session=False))
def delete_firewall_group(self, context, id):
# Note: Plugin should ensure that it's okay to delete if the
# firewall is active
with context.session.begin(subtransactions=True):
# if no such group exists -> don't raise an exception according to
# 80fe2ba1, return None
try:
fwg_db = self._get_firewall_group(context, id)
except f_exc.FirewallGroupNotFound:
return
if _is_default(fwg_db):
if context.is_admin:
# Like Rules in Default SG, when the Default FWG is deleted
# its associated Rules and policies would also be deleted.
# Delete fwg first and then associated policies
context.session.query(
FirewallGroup).filter_by(id=id).delete()
fwp = [fwg_db['ingress_firewall_policy_id'],
fwg_db['egress_firewall_policy_id']]
for fwp_id in fwp:
self.delete_firewall_policy(context, fwp_id)
else:
# only admin can delete default fwg
raise f_exc.FirewallGroupCannotRemoveDefault()
else:
context.session.query(
FirewallGroup).filter_by(id=id).delete()
def _ensure_not_default_resource(self, resource_dict, r_type, action=None):
"""Checks that a resource is not default by checking its name
A resource_dict can be either a dictionary in form {r_type : {}} or a
serialized object from db.
Action is used to determine type of exception to be raised.
"""
resource = resource_dict.get(r_type) or resource_dict
if r_type == 'firewall_group':
if resource.get('name', '') == const.DEFAULT_FWG:
if action == "update":
raise FirewallDefaultObjectUpdateRestricted(
resource_type='Firewall Group',
resource_id=resource['id'])
raise FirewallDefaultParameterExists(
resource_type='Firewall Group', name=resource['name'])
elif r_type == 'firewall_policy':
if resource.get('name', '') in [const.DEFAULT_FWP_INGRESS,
const.DEFAULT_FWP_EGRESS]:
if action == "update":
raise FirewallDefaultObjectUpdateRestricted(
resource_type='Firewall Group',
resource_id=resource['id'])
raise FirewallDefaultParameterExists(
resource_type='Firewall Policy', name=resource['name'])
def get_firewall_group(self, context, id, fields=None):
fw = self._get_firewall_group(context, id)
return self._make_firewall_group_dict(fw, fields)
def get_firewall_groups(self, context, filters=None, fields=None):
if context.tenant_id:
tenant_id = filters.get('tenant_id') if filters else None
tenant_id = tenant_id[0] if tenant_id else context.tenant_id
self._ensure_default_firewall_group(context, tenant_id)
return self._get_collection(context, FirewallGroup,
self._make_firewall_group_dict,
filters=filters, fields=fields)
def _is_default(fwg_db):
return fwg_db['name'] == const.DEFAULT_FWG