ec2-api/ec2api/api/security_group.py

300 lines
12 KiB
Python

# Copyright 2014
# The Cloudscaling Group, Inc.
#
# 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 neutronclient.common import exceptions as neutron_exception
from oslo.config import cfg
from ec2api.api import clients
from ec2api.api import ec2client
from ec2api.api import ec2utils
from ec2api.api import utils
from ec2api.db import api as db_api
from ec2api import exception
from ec2api.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
"""Security Groups related API implementation
"""
SECURITY_GROUP_MAP = {'domain-name-servers': 'dns-servers',
'domain-name': 'domain-name',
'ntp-servers': 'ntp-server',
'netbios-name-servers': 'netbios-ns',
'netbios-node-type': 'netbios-nodetype'}
FILTER_MAP = {'vpc-id': 'vpcId',
'group-name': 'groupName',
'group-id': 'groupId'}
def create_security_group(context, group_name, group_description,
vpc_id=None):
if vpc_id is None:
ec2 = ec2client.ec2client(context)
return ec2.create_security_groups(group_name=group_name,
group_description=group_description)
vpc = ec2utils.get_db_item(context, 'vpc', vpc_id)
neutron = clients.neutron(context)
with utils.OnCrashCleaner() as cleaner:
os_security_group = neutron.create_security_group(
{'security_group':
{'name': group_name,
'description': group_description}})['security_group']
cleaner.addCleanup(neutron.delete_security_group,
os_security_group['id'])
security_group = db_api.add_item(context, 'sg',
{'vpc_id': vpc['id'],
'os_id': os_security_group['id']})
return {'return': 'true',
'groupId': security_group['id']}
def _create_default_security_group(context, vpc):
neutron = clients.neutron(context)
os_security_group = neutron.create_security_group(
{'security_group':
{'name': 'Default',
'description': 'Default VPC security group'}})['security_group']
security_group = db_api.add_item(context, 'sg',
{'vpc_id': vpc['id'],
'os_id': os_security_group['id']})
def delete_security_group(context, group_name=None, group_id=None):
if group_id is None or not group_id.startswith('sg-'):
ec2 = ec2client.ec2client(context)
return ec2.delete_security_groups(group_name=group_name,
group_id=group_id)
security_group = ec2utils.get_db_item(context, 'sg', group_id)
# TODO(Alex) Check dependencies - instances and other security groups
neutron = clients.neutron(context)
try:
neutron.delete_security_group(security_group['os_id'])
except neutron_exception.Conflict as ex:
# TODO(Alex): Instance ID is unknown here, report exception message
# in its place - looks readable.
raise exception.DependencyViolation(
obj1_id=group_id,
obj2_id=ex.message)
except neutron_exception.NeutronClientException as ex:
# TODO(Alex): do log error
# TODO(Alex): adjust caught exception classes to catch:
# the port doesn't exist
pass
db_api.delete_item(context, security_group['id'])
return True
def describe_security_groups(context, group_name=None, group_id=None,
filter=None):
# TODO(Alex): implement filters
neutron = clients.neutron(context)
os_security_groups = neutron.list_security_groups()['security_groups']
security_groups = ec2utils.get_db_items(context, 'sg', group_id)
formatted_security_groups = []
for os_security_group in os_security_groups:
security_group = next((g for g in security_groups
if g['os_id'] == os_security_group['id']), None)
if group_id is not None and security_group is None:
continue
formatted_security_group = _format_security_group(
context, security_group,
os_security_group, os_security_groups,
security_groups)
if not utils.filtered_out(formatted_security_group, filter,
FILTER_MAP):
formatted_security_groups.append(formatted_security_group)
return {'securityGroupInfo': formatted_security_groups}
def authorize_security_group_ingress(context, group_id,
group_name, ip_permissions):
if group_id is None or not group_id.startswith('sg-'):
ec2 = ec2client.ec2client(context)
return ec2.authorize_security_groups_ingress(
group_name=group_name,
group_id=group_id,
ip_permissions=ip_permissions)
return _authorize_security_group(context, group_id, ip_permissions,
'ingress')
def authorize_security_group_egress(context, group_id, ip_permissions):
return _authorize_security_group(context, group_id, ip_permissions,
'egress')
def _authorize_security_group(context, group_id, ip_permissions, direction):
rule_body = _build_rule(context, group_id, ip_permissions, direction)
neutron = clients.neutron(context)
try:
os_security_group_rule = neutron.create_security_group_rule(
{'security_group_rule': rule_body})['security_group_rule']
except neutron_exception.Conflict as ex:
raise exception.RuleAlreadyExists()
return True
def _build_rule(context, group_id, ip_permissions, direction):
security_group = ec2utils.get_db_item(context, 'sg', group_id)
os_security_group_rule_body = (
{'security_group_id': security_group['os_id'],
'direction': direction,
'ethertype': 'IPv4'})
if ip_permissions is None:
ip_permissions = []
for rule in ip_permissions:
if rule.get('ip_protocol', -1) != -1:
os_security_group_rule_body['protocol'] = rule['ip_protocol']
if rule.get('from_port', -1) != -1:
os_security_group_rule_body['port_range_min'] = rule['from_port']
if rule.get('to_port', -1) != -1:
os_security_group_rule_body['port_range_max'] = rule['to_port']
# TODO(Alex) AWS protocol claims support of multiple groups and cidrs,
# however, neither aws cli, nor neutron support it at the moment.
# It's possible in the future to convert list values incoming from
# REST API into several neutron rules and squeeze them back into one
# for describing.
# For now only 1 value is supported for either.
if rule.get('groups'):
os_security_group_rule_body['remote_group_id'] = (
ec2utils.get_db_item(context, 'sg',
rule['groups'][0]['group_id'])['os_id'])
elif rule.get('ip_ranges'):
os_security_group_rule_body['remote_ip_prefix'] = (
rule['ip_ranges'][0]['cidr_ip'])
return os_security_group_rule_body
def revoke_security_group_ingress(context, group_id,
group_name, ip_permissions):
if group_id is None or not group_id.startswith('sg-'):
ec2 = ec2client.ec2client(context)
return ec2.revoke_security_groups_ingress(
group_name=group_name,
group_id=group_id,
ip_permissions=ip_permissions)
return _revoke_security_group(context, group_id, ip_permissions,
'ingress')
def revoke_security_group_egress(context, group_id, ip_permissions):
return _revoke_security_group(context, group_id, ip_permissions, 'egress')
def _are_identical_rules(rule1, rule2):
def significant_values(rule):
dict = {}
for key, value in rule.items():
if (value is not None and value != -1 and
value != '0.0.0.0/0' and
key not in ['id', 'tenant_id']):
dict[key] = str(value)
return dict
r1 = significant_values(rule1)
r2 = significant_values(rule2)
return r1 == r2
def _revoke_security_group(context, group_id, ip_permissions, direction):
rule_body = _build_rule(context, group_id, ip_permissions, direction)
neutron = clients.neutron(context)
os_security_group = neutron.show_security_group(
rule_body['security_group_id'])['security_group']
if not os_security_group.get('security_group_rules'):
return True
for os_rule in os_security_group['security_group_rules']:
if _are_identical_rules(os_rule, rule_body):
neutron.delete_security_group_rule(
os_rule['id'])
return True
raise exception.InvalidPermissionNotFound()
def _format_security_groups_ids_names(context):
neutron = clients.neutron(context)
os_security_groups = neutron.list_security_groups()['security_groups']
security_groups = db_api.get_items(context, 'sg')
ec2_security_groups = {}
for os_security_group in os_security_groups:
security_group = next((g for g in security_groups
if g['os_id'] == os_security_group['id']), None)
if security_group is None:
continue
ec2_security_groups[os_security_group['id']] = (
{'groupId': security_group['id'],
'groupName': os_security_group['name']})
return ec2_security_groups
def _format_security_group(context, security_group, os_security_group,
os_security_groups, security_groups):
ec2_security_group = {}
if security_group is not None:
ec2_security_group['groupId'] = security_group['id']
ec2_security_group['vpcId'] = security_group['vpc_id']
ec2_security_group['ownerId'] = os_security_group['tenant_id']
ec2_security_group['groupName'] = os_security_group['name']
ec2_security_group['groupDescription'] = os_security_group['description']
ingress_permissions = []
egress_permissions = []
for os_rule in os_security_group['security_group_rules']:
# NOTE(Alex) We're skipping IPv6 rules because AWS doesn't support
# them.
if os_rule.get('ethertype', 'IPv4') == 'IPv6':
continue
ec2_rule = {'ipProtocol': -1 if os_rule['protocol'] is None
else os_rule['protocol'],
'fromPort': -1 if os_rule['port_range_min'] is None
else os_rule['port_range_min'],
'toPort': -1 if os_rule['port_range_max'] is None
else os_rule['port_range_max']}
remote_group_id = os_rule['remote_group_id']
if remote_group_id is not None:
ec2_remote_group = {}
db_remote_group = next((g for g in security_groups
if g['os_id'] == remote_group_id), None)
if db_remote_group is not None:
ec2_remote_group['groupId'] = db_remote_group['id']
else:
# TODO(Alex) Log absence of remote_group
pass
os_remote_group = next((g for g in os_security_groups
if g['id'] == remote_group_id), None)
if os_remote_group is not None:
ec2_remote_group['groupName'] = os_remote_group['name']
ec2_remote_group['userId'] = os_remote_group['tenant_id']
else:
# TODO(Alex) Log absence of remote_group
pass
ec2_rule['groups'] = [ec2_remote_group]
elif os_rule['remote_ip_prefix'] is not None:
ec2_rule['ipRanges'] = [{'cidrIp': os_rule['remote_ip_prefix']}]
if os_rule['direction'] == 'egress':
egress_permissions.append(ec2_rule)
elif os_rule['direction'] == 'ingress':
ingress_permissions.append(ec2_rule)
ec2_security_group['ipPermissions'] = ingress_permissions
ec2_security_group['ipPermissionsEgress'] = egress_permissions
return ec2_security_group