339 lines
12 KiB
Python
339 lines
12 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Copyright (c) 2012, Intel Performance Learning Solutions Ltd.
|
|
#
|
|
# 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 random
|
|
from nova.openstack.common import importutils
|
|
|
|
from occi import backend
|
|
from occi import core_model
|
|
from webob import exc
|
|
|
|
from nova import compute
|
|
from nova import db
|
|
from nova import flags
|
|
from nova import log as logging
|
|
|
|
|
|
# TODO(dizz): Remove SSH Console and VNC Console once URI support is added to
|
|
# pyssf
|
|
|
|
#Hi I'm a logger, use me! :-)
|
|
LOG = logging.getLogger('nova.api.occi.backends.securityrule')
|
|
|
|
FLAGS = flags.FLAGS
|
|
|
|
|
|
####################### OCCI Candidate Spec Additions ########################
|
|
def get_extensions():
|
|
return [
|
|
{
|
|
'categories': [CONSOLE_LINK, SSH_CONSOLE, VNC_CONSOLE, ],
|
|
'handler': backend.KindBackend(),
|
|
},
|
|
{
|
|
'categories': [SEC_RULE, ],
|
|
'handler': SecurityRuleBackend(),
|
|
},
|
|
{
|
|
'categories': [SEC_GROUP, ],
|
|
'handler': SecurityGroupBackend(),
|
|
},
|
|
]
|
|
|
|
|
|
# Console Link Extension
|
|
CONSOLE_LINK = core_model.Kind(
|
|
'http://schemas.ogf.org/infrastructure/compute#',
|
|
'console',
|
|
[core_model.Link.kind],
|
|
None,
|
|
'This is a link to the VMs console',
|
|
None,
|
|
'/compute/consolelink/')
|
|
|
|
|
|
# SSH Console Kind Extension
|
|
_SSH_CONSOLE_ATTRIBUTES = {'org.openstack.compute.console.ssh': '', }
|
|
SSH_CONSOLE = core_model.Kind(
|
|
'http://schemas.openstack.org/occi/infrastructure/compute#',
|
|
'ssh_console',
|
|
None,
|
|
None,
|
|
'SSH console kind',
|
|
_SSH_CONSOLE_ATTRIBUTES,
|
|
'/compute/console/ssh/')
|
|
|
|
|
|
# VNC Console Kind Extension
|
|
_VNC_CONSOLE_ATTRIBUTES = {'org.openstack.compute.console.vnc': '', }
|
|
VNC_CONSOLE = core_model.Kind(
|
|
'http://schemas.openstack.org/occi/infrastructure/compute#',
|
|
'vnc_console',
|
|
None,
|
|
None,
|
|
'VNC console kind',
|
|
_VNC_CONSOLE_ATTRIBUTES,
|
|
'/compute/console/vnc/')
|
|
|
|
|
|
# Network security rule extension to specify firewall rules
|
|
_SEC_RULE_ATTRIBUTES = {
|
|
'occi.network.security.protocol': '',
|
|
'occi.network.security.to': '',
|
|
'occi.network.security.from': '',
|
|
'occi.network.security.range': '',
|
|
}
|
|
SEC_RULE = core_model.Kind(
|
|
'http://schemas.openstack.org/occi/infrastructure/network/security#',
|
|
'rule',
|
|
[core_model.Resource.kind],
|
|
None,
|
|
'Network security rule kind',
|
|
_SEC_RULE_ATTRIBUTES,
|
|
'/network/security/rule/')
|
|
|
|
|
|
# Network security rule group
|
|
SEC_GROUP = core_model.Mixin(
|
|
'http://schemas.ogf.org/occi/infrastructure/security#',
|
|
'group', attributes=None)
|
|
|
|
|
|
# An extended Mixin, an extension
|
|
class UserSecurityGroupMixin(core_model.Mixin):
|
|
pass
|
|
|
|
|
|
# The same approach can be used to create and delete VM images.
|
|
class SecurityGroupBackend(backend.UserDefinedMixinBackend):
|
|
|
|
def __init__(self):
|
|
super(SecurityGroupBackend, self).__init__()
|
|
self.compute_api = compute.API()
|
|
self.sgh = importutils.import_object(FLAGS.security_group_handler)
|
|
|
|
def init_sec_group(self, category, extras):
|
|
"""
|
|
Creates the security group as specified in the request.
|
|
"""
|
|
#do not recreate default openstack security groups
|
|
if (category.scheme ==
|
|
'http://schemas.openstack.org/infrastructure/security/group#'):
|
|
return
|
|
|
|
context = extras['nova_ctx']
|
|
|
|
group_name = category.term.strip()
|
|
group_description = (category.title.strip()
|
|
if category.title else group_name)
|
|
|
|
# TODO(dizz): method seems to be gone!
|
|
#self.compute_api.ensure_default_security_group(context)
|
|
if db.security_group_exists(context, context.project_id, group_name):
|
|
raise exc.HTTPBadRequest(
|
|
explanation=_('Security group %s already exists') % group_name)
|
|
|
|
group = {'user_id': context.user_id,
|
|
'project_id': context.project_id,
|
|
'name': group_name,
|
|
'description': group_description}
|
|
db.security_group_create(context, group)
|
|
self.sgh.trigger_security_group_create_refresh(context, group)
|
|
|
|
def destroy(self, category, extras):
|
|
"""
|
|
Deletes the specified security group.
|
|
"""
|
|
context = extras['nova_ctx']
|
|
security_group = self._get_sec_group(extras, category)
|
|
if db.security_group_in_use(context, security_group['id']):
|
|
raise exc.HTTPBadRequest(
|
|
explanation=_("Security group is still in use"))
|
|
|
|
db.security_group_destroy(context, security_group['id'])
|
|
self.sgh.trigger_security_group_destroy_refresh(
|
|
context, security_group['id'])
|
|
|
|
def _get_sec_group(self, extras, sec_mixin):
|
|
"""
|
|
Retreive the security group by name associated with the security mixin.
|
|
"""
|
|
try:
|
|
sec_group = db.security_group_get_by_name(extras['nova_ctx'],
|
|
extras['nova_ctx'].project_id, sec_mixin.term)
|
|
except Exception:
|
|
msg = _('Security group does not exist.')
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
return sec_group
|
|
|
|
|
|
class SecurityRuleBackend(backend.KindBackend):
|
|
|
|
def __init__(self):
|
|
super(SecurityRuleBackend, self).__init__()
|
|
self.compute_api = compute.API()
|
|
self.sgh = importutils.import_object(FLAGS.security_group_handler)
|
|
|
|
def create(self, entity, extras):
|
|
"""
|
|
Creates a security rule.
|
|
The group to add the rule to must exist.
|
|
In OCCI-speak this means the mixin must be supplied with the request
|
|
"""
|
|
msg = _('Creating a network security rule')
|
|
LOG.info(msg)
|
|
|
|
sec_mixin = self._get_sec_mixin(entity)
|
|
security_group = self._get_sec_group(extras, sec_mixin)
|
|
sg_rule = self._make_sec_rule(entity, security_group['id'])
|
|
|
|
if self._security_group_rule_exists(security_group, sg_rule):
|
|
#This rule already exists in group
|
|
msg = _('This rule already exists in group. %s') % \
|
|
str(security_group)
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
db.security_group_rule_create(extras['nova_ctx'], sg_rule)
|
|
|
|
def _make_sec_rule(self, entity, sec_grp_id):
|
|
"""
|
|
Create and validate the security rule.
|
|
"""
|
|
sg_rule = {}
|
|
sg_rule['id'] = random.randrange(0, 99999999)
|
|
entity.attributes['occi.core.id'] = str(sg_rule['id'])
|
|
sg_rule['parent_group_id'] = sec_grp_id
|
|
prot = \
|
|
entity.attributes['occi.network.security.protocol'].lower().strip()
|
|
if prot in ('tcp', 'udp', 'icmp'):
|
|
sg_rule['protocol'] = prot
|
|
else:
|
|
raise exc.HTTPBadRequest()
|
|
from_p = entity.attributes['occi.network.security.to'].strip()
|
|
from_p = int(from_p)
|
|
if (type(from_p) is int) and from_p > 0 and from_p <= 65535:
|
|
sg_rule['from_port'] = from_p
|
|
else:
|
|
raise exc.HTTPBadRequest()
|
|
to_p = entity.attributes['occi.network.security.to'].strip()
|
|
to_p = int(to_p)
|
|
if (type(to_p) is int) and to_p > 0 and to_p <= 65535:
|
|
sg_rule['to_port'] = to_p
|
|
else:
|
|
raise exc.HTTPBadRequest()
|
|
if from_p > to_p:
|
|
raise exc.HTTPBadRequest()
|
|
cidr = entity.attributes['occi.network.security.range'].strip()
|
|
if len(cidr) <= 0:
|
|
cidr = '0.0.0.0/0'
|
|
# TODO(dizz): find corresponding call in master!
|
|
#if utils.is_valid_cidr(cidr):
|
|
if True:
|
|
sg_rule['cidr'] = cidr
|
|
else:
|
|
raise exc.HTTPBadRequest()
|
|
sg_rule['group'] = {}
|
|
return sg_rule
|
|
|
|
def _get_sec_group(self, extras, sec_mixin):
|
|
"""
|
|
Retreive the security group associated with the security mixin.
|
|
"""
|
|
try:
|
|
sec_group = db.security_group_get_by_name(extras['nova_ctx'],
|
|
extras['nova_ctx'].project_id, sec_mixin.term)
|
|
except Exception:
|
|
# ensure that an OpenStack sec group matches the mixin
|
|
# if not, create one.
|
|
# This has to be done as pyssf has no way to associate
|
|
# a handler for the creation of mixins at the query interface
|
|
msg = _('Security group does not exist.')
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
return sec_group
|
|
|
|
def _get_sec_mixin(self, entity):
|
|
"""
|
|
Get the security mixin of the supplied entity.
|
|
"""
|
|
sec_mixin_present = 0
|
|
sec_mixin = None
|
|
for mixin in entity.mixins:
|
|
if SEC_GROUP in mixin.related:
|
|
sec_mixin = mixin
|
|
sec_mixin_present = sec_mixin_present + 1
|
|
|
|
if not sec_mixin_present:
|
|
# no mixin of the type security group was found
|
|
msg = _('No security group mixin was found')
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
if sec_mixin_present > 1:
|
|
msg = _('More than one security group mixin was found')
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
return sec_mixin
|
|
|
|
def _security_group_rule_exists(self, security_group, values):
|
|
"""
|
|
Indicates whether the specified rule values are already
|
|
defined in the given security group.
|
|
"""
|
|
# Taken directly from security_groups.py as that method is not
|
|
# directly import-able.
|
|
for rule in security_group['rules']:
|
|
is_duplicate = True
|
|
keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
|
|
for key in keys:
|
|
if rule.get(key) != values.get(key):
|
|
is_duplicate = False
|
|
break
|
|
if is_duplicate:
|
|
return True
|
|
return False
|
|
|
|
def delete(self, entity, extras):
|
|
"""
|
|
Deletes the security rule.
|
|
"""
|
|
msg = _('Deleting a network security rule')
|
|
LOG.info(msg)
|
|
# TODO(dizz): method seems to be gone!
|
|
# self.compute_api.ensure_default_security_group(extras['nova_ctx'])
|
|
try:
|
|
rule = db.security_group_rule_get(extras['nova_ctx'],
|
|
int(entity.attributes['occi.core.id']))
|
|
except Exception:
|
|
raise exc.HTTPNotFound()
|
|
|
|
group_id = rule['parent_group_id']
|
|
# TODO(dizz): method seems to be gone!
|
|
# self.compute_api.ensure_default_security_group(extras['nova_ctx'])
|
|
security_group = db.security_group_get(extras['nova_ctx'], group_id)
|
|
|
|
db.security_group_rule_destroy(extras['nova_ctx'], rule['id'])
|
|
self.sgh.trigger_security_group_rule_destroy_refresh(
|
|
extras['nova_ctx'], [rule['id']])
|
|
self.compute_api.trigger_security_group_rules_refresh(
|
|
extras['nova_ctx'],
|
|
security_group['id'])
|