vmware-nsx/vmware_nsx/db/extended_security_group.py
Roey Chen 2cfc1231dc Provider Security groups
This patch set introduces a new feature called provider-security-groups.
Provider security groups allow the provider to create a security group
that is automatically attached to a specific tenants ports. The one
important thing to note is that rules inside of a provider security
group are set to DENY where as a normal security group they are set
to ALLOW. Provider security groups allow the admin tenant to block specific
traffic for any tenant they like by creatng a provider group. To use this
feature the admin tenant must first create a provider security group
on behalf of the other tenant (i.e):

$ neutron security-group-create no-pokemon-go-access --provider=True \
	--tenant-id=<shall remain nameless>

Then, whenever the above tenant id creates a port they will see a an
additional field on the port "provider-security-groups" which will
contain the uuid of the provider security group. This user can then
query neutron to see which rules are in it that are blocking them.

NOTE: one needs to use the correct policy.json file from this repo
for neutron inorder to prevent the tenant from removing the group.

Co-Authored-By: Aaron Rosen <aaronorosen@gmail.com>

Change-Id: I57b130437327b0bbe5cc0068695f226b76b4e2ba
2016-08-02 13:34:37 +00:00

276 lines
13 KiB
Python

# Copyright 2016 VMware, 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.
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy import orm
from neutron.api.v2 import attributes
from neutron.common import utils as n_utils
from neutron.db import api as db_api
from neutron.db import db_base_plugin_v2
from neutron.db import model_base
from neutron.db import securitygroups_db
from neutron.extensions import securitygroup as ext_sg
from neutron_lib.api import validators
from neutron_lib import constants as n_constants
from vmware_nsx._i18n import _
from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.extensions import securitygrouplogging as sg_logging
class NsxExtendedSecurityGroupProperties(model_base.BASEV2):
__tablename__ = 'nsx_extended_security_group_properties'
security_group_id = sa.Column(sa.String(36),
sa.ForeignKey('securitygroups.id',
ondelete="CASCADE"),
primary_key=True)
logging = sa.Column(sa.Boolean, default=False, nullable=False)
provider = sa.Column(sa.Boolean, default=False, nullable=False)
security_group = orm.relationship(
securitygroups_db.SecurityGroup,
backref=orm.backref('ext_properties', lazy='joined',
uselist=False, cascade='delete'))
class ExtendedSecurityGroupPropertiesMixin(object):
# NOTE(arosen): here we add a relationship so that from the ports model
# it provides us access to SecurityGroupPortBinding and
# NsxExtendedSecurityGroupProperties
securitygroups_db.SecurityGroupPortBinding.extended_grp = orm.relationship(
'NsxExtendedSecurityGroupProperties',
foreign_keys="SecurityGroupPortBinding.security_group_id",
primaryjoin=("NsxExtendedSecurityGroupProperties.security_group_id"
"==SecurityGroupPortBinding.security_group_id"))
def create_provider_security_group(self, context, security_group):
"""Create a provider security group.
This method creates a security group that does not by default
enable egress traffic which normal neutron security groups do.
"""
s = security_group['security_group']
tenant_id = s['tenant_id']
with db_api.autonested_transaction(context.session):
security_group_db = securitygroups_db.SecurityGroup(
id=s.get('id') or (uuidutils.generate_uuid()),
description=s.get('description', ''),
tenant_id=tenant_id,
name=s.get('name', ''))
context.session.add(security_group_db)
secgroup_dict = self._make_security_group_dict(security_group_db)
secgroup_dict[provider_sg.PROVIDER] = True
return secgroup_dict
def _process_security_group_properties_create(self, context,
sg_res, sg_req,
default_sg=False):
self._validate_security_group_properties_create(
context, sg_req, default_sg)
with context.session.begin(subtransactions=True):
properties = NsxExtendedSecurityGroupProperties(
security_group_id=sg_res['id'],
logging=sg_req.get(sg_logging.LOGGING, False),
provider=sg_req.get(provider_sg.PROVIDER, False))
context.session.add(properties)
sg_res[sg_logging.LOGGING] = sg_req.get(sg_logging.LOGGING, False)
sg_res[provider_sg.PROVIDER] = sg_req.get(provider_sg.PROVIDER, False)
def _get_security_group_properties(self, context, security_group_id):
with context.session.begin(subtransactions=True):
prop = context.session.query(
NsxExtendedSecurityGroupProperties).filter_by(
security_group_id=security_group_id).one()
return prop
def _process_security_group_properties_update(self, context,
sg_res, sg_req):
if (sg_logging.LOGGING in sg_req
and (sg_req[sg_logging.LOGGING] !=
sg_res.get(sg_logging.LOGGING, False))):
prop = self._get_security_group_properties(context, sg_res['id'])
with context.session.begin(subtransactions=True):
prop.update({sg_logging.LOGGING: sg_req[sg_logging.LOGGING]})
sg_res[sg_logging.LOGGING] = sg_req[sg_logging.LOGGING]
def _is_security_group_logged(self, context, security_group_id):
prop = self._get_security_group_properties(context, security_group_id)
return prop.logging
def _is_provider_security_group(self, context, security_group_id):
sg_prop = self._get_security_group_properties(context,
security_group_id)
return sg_prop.provider
def _check_provider_security_group_exists(self, context,
security_group_id):
# NOTE(roeyc): We want to retrieve the security-group info by calling
# get_security_group, this will also validate that the provider
# security-group belongs to the same tenant this request is made for.
sg = self.get_security_group(context, security_group_id)
if not sg[provider_sg.PROVIDER]:
raise provider_sg.SecurityGroupNotProvider(id=sg)
def _check_invalid_security_groups_specified(self, context, port):
if validators.is_attr_set(port.get(ext_sg.SECURITYGROUPS)):
for sg in port.get(ext_sg.SECURITYGROUPS, []):
# makes sure user doesn't add non-provider secgrp as secgrp
if self._is_provider_security_group(context, sg):
raise provider_sg.SecurityGroupIsProvider(id=sg)
if validators.is_attr_set(
port.get(provider_sg.PROVIDER_SECURITYGROUPS)):
# also check all provider groups are provider.
for sg in port.get(provider_sg.PROVIDER_SECURITYGROUPS, []):
self._check_provider_security_group_exists(context, sg)
def _get_tenant_provider_security_groups(self, context, tenant_id):
res = context.session.query(
NsxExtendedSecurityGroupProperties.security_group_id
).join(securitygroups_db.SecurityGroup).filter(
securitygroups_db.SecurityGroup.tenant_id == tenant_id,
NsxExtendedSecurityGroupProperties.provider == sa.true()).scalar()
return [res] if res else []
def _validate_security_group_properties_create(self, context,
security_group, default_sg):
self._validate_provider_security_group_create(context, security_group,
default_sg)
def _validate_provider_security_group_create(self, context, security_group,
default_sg):
if not security_group.get(provider_sg.PROVIDER, False):
return
if default_sg:
raise provider_sg.DefaultSecurityGroupIsNotProvider()
tenant_id = security_group['tenant_id']
ssg = self._get_tenant_provider_security_groups(context, tenant_id)
if ssg:
# REVISIT(roeyc): At the moment we only allow on provider
# security-group per tenant, this might change in the future.
raise Exception(_("Provider Security-group already exists"
"(%(pvdsg)s) for tenant %(tenant_id)s.")
% {'pvdsg': ssg, 'tenant_id': tenant_id})
def _get_provider_security_groups_on_port(self, context, port):
p = port['port']
tenant_id = p['tenant_id']
provider_sgs = p.get(provider_sg.PROVIDER_SECURITYGROUPS,
n_constants.ATTR_NOT_SPECIFIED)
if p.get('device_owner') and n_utils.is_port_trusted(p):
return
self._check_invalid_security_groups_specified(context, p)
if not validators.is_attr_set(provider_sgs):
if provider_sgs is n_constants.ATTR_NOT_SPECIFIED:
provider_sgs = self._get_tenant_provider_security_groups(
context, tenant_id)
else:
# Accept None as indication that this port should not be
# associated with any provider security-group.
provider_sgs = []
return provider_sgs
def _process_port_create_provider_security_group(self, context, p,
security_group_ids):
if validators.is_attr_set(security_group_ids):
for security_group_id in security_group_ids:
self._create_port_security_group_binding(context, p['id'],
security_group_id)
p[provider_sg.PROVIDER_SECURITYGROUPS] = security_group_ids or []
def _process_port_update_provider_security_group(self, context, port,
original_port,
updated_port):
p = port['port']
provider_sg_specified = (provider_sg.PROVIDER_SECURITYGROUPS in p and
p[provider_sg.PROVIDER_SECURITYGROUPS] !=
n_constants.ATTR_NOT_SPECIFIED)
provider_sg_changed = (
provider_sg_specified and not n_utils.compare_elements(
original_port[provider_sg.PROVIDER_SECURITYGROUPS],
p[provider_sg.PROVIDER_SECURITYGROUPS]))
sg_changed = (
set(original_port[ext_sg.SECURITYGROUPS]) !=
set(updated_port[ext_sg.SECURITYGROUPS]))
if provider_sg_changed:
port['port']['tenant_id'] = original_port['id']
port['port']['id'] = original_port['id']
updated_port[provider_sg.PROVIDER_SECURITYGROUPS] = (
self._get_provider_security_groups_on_port(context, port))
else:
if sg_changed:
self._check_invalid_security_groups_specified(context, p)
updated_port[provider_sg.PROVIDER_SECURITYGROUPS] = (
original_port[provider_sg.PROVIDER_SECURITYGROUPS])
if provider_sg_changed or sg_changed:
if not sg_changed:
query = context.session.query(
securitygroups_db.SecurityGroupPortBinding)
for sg in original_port[provider_sg.PROVIDER_SECURITYGROUPS]:
binding = query.filter_by(
port_id=p['id'], security_group_id=sg).one()
context.session.delete(binding)
self._process_port_create_provider_security_group(
context, updated_port,
updated_port[provider_sg.PROVIDER_SECURITYGROUPS])
def _extend_security_group_with_properties(self, sg_res, sg_db):
if sg_db.ext_properties:
sg_res[sg_logging.LOGGING] = sg_db.ext_properties.logging
sg_res[provider_sg.PROVIDER] = sg_db.ext_properties.provider
def _extend_port_dict_provider_security_group(self, port_res, port_db):
# NOTE(arosen): this method overrides the one in the base
# security group db class. The reason this is needed is because
# we are storing provider security groups in the same security
# groups db model. We need to do this here to remove the provider
# security groups and put those on the port resource as their
# own attribute.
# Security group bindings will be retrieved from the SQLAlchemy
# model. As they're loaded eagerly with ports because of the
# joined load they will not cause an extra query.
provider_groups = []
not_provider_groups = []
for sec_group_mapping in port_db.security_groups:
if sec_group_mapping.extended_grp.provider is True:
provider_groups.append(sec_group_mapping['security_group_id'])
else:
not_provider_groups.append(
sec_group_mapping['security_group_id'])
port_res[ext_sg.SECURITYGROUPS] = not_provider_groups
port_res[provider_sg.PROVIDER_SECURITYGROUPS] = provider_groups
return port_res
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.PORTS, ['_extend_port_dict_provider_security_group'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
ext_sg.SECURITYGROUPS, ['_extend_security_group_with_properties'])