Add support for source security groups

This patch adds support for the SourceSecurityGroupName and
SourceSecurityGroupId properties. It covers Nova and Neutron.

Change-Id: Ic12512dfb4375ccccbe1282bb48b80cde16ceb9d
Fixes: bug #1193415
This commit is contained in:
Simon Pasquier 2013-08-14 15:00:44 +02:00
parent 093638e0c6
commit 82f7086e23
4 changed files with 352 additions and 47 deletions

View File

@ -337,3 +337,8 @@ class HTTPExceptionDisguise(Exception):
class TemplateTooBig(HeatException):
message = _('Template exceeds maximum allowed size.')
class EgressRuleNotAllowed(HeatException):
message = _("Egress rules are only allowed when "
"Neutron is used and the 'VpcId' property is set.")

View File

@ -16,6 +16,7 @@
from heat.engine import clients
from heat.engine import resource
from heat.common import exception
from heat.openstack.common import log as logging
logger = logging.getLogger(__name__)
@ -34,6 +35,20 @@ class SecurityGroup(resource.Resource):
else:
self._handle_create_nova()
def _convert_to_neutron_rule(self, direction, sg_rule):
return {
'direction': direction,
'ethertype': 'IPv4',
'remote_ip_prefix': sg_rule.get('CidrIp'),
'port_range_min': sg_rule.get('FromPort'),
'port_range_max': sg_rule.get('ToPort'),
'protocol': sg_rule.get('IpProtocol'),
# Neutron understands both names and ids
'remote_group_id': sg_rule.get('SourceSecurityGroupId') or
sg_rule.get('SourceSecurityGroupName'),
'security_group_id': self.resource_id
}
def _handle_create_neutron(self):
from neutronclient.common.exceptions import NeutronClientException
client = self.neutron()
@ -43,28 +58,23 @@ class SecurityGroup(resource.Resource):
'description': self.properties['GroupDescription']}
})['security_group']
def sanitize_security_group(i):
# Neutron only accepts positive ints
if i.get('FromPort') is not None and int(i['FromPort']) < 0:
i['FromPort'] = None
if i.get('ToPort') is not None and int(i['ToPort']) < 0:
i['ToPort'] = None
if i.get('FromPort') is None and i.get('ToPort') is None:
i['CidrIp'] = None
self.resource_id_set(sec['id'])
if self.properties['SecurityGroupIngress']:
for i in self.properties['SecurityGroupIngress']:
# Neutron only accepts positive ints
if int(i['FromPort']) < 0:
i['FromPort'] = None
if int(i['ToPort']) < 0:
i['ToPort'] = None
if i['FromPort'] is None and i['ToPort'] is None:
i['CidrIp'] = None
sanitize_security_group(i)
try:
rule = client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_ip_prefix': i['CidrIp'],
'port_range_min': i['FromPort'],
'ethertype': 'IPv4',
'port_range_max': i['ToPort'],
'protocol': i['IpProtocol'],
'security_group_id': sec['id']
}
'security_group_rule':
self._convert_to_neutron_rule('ingress', i)
})
except NeutronClientException as ex:
if ex.status_code == 409:
@ -74,18 +84,17 @@ class SecurityGroup(resource.Resource):
# unexpected error
raise
if self.properties['SecurityGroupEgress']:
# Delete the default rules which allow all egress traffic
for rule in sec['security_group_rules']:
if rule['direction'] == 'egress':
client.delete_security_group_rule(rule['id'])
for i in self.properties['SecurityGroupEgress']:
sanitize_security_group(i)
try:
rule = client.create_security_group_rule({
'security_group_rule': {
'direction': 'egress',
'remote_ip_prefix': i['CidrIp'],
'port_range_min': i['FromPort'],
'ethertype': 'IPv4',
'port_range_max': i['ToPort'],
'protocol': i['IpProtocol'],
'security_group_id': sec['id']
}
'security_group_rule':
self._convert_to_neutron_rule('egress', i)
})
except NeutronClientException as ex:
if ex.status_code == 409:
@ -113,12 +122,22 @@ class SecurityGroup(resource.Resource):
if self.properties['SecurityGroupIngress']:
rules_client = self.nova().security_group_rules
for i in self.properties['SecurityGroupIngress']:
source_group_id = None
if i.get('SourceSecurityGroupId') is not None:
source_group_id = i['SourceSecurityGroupId']
elif i.get('SourceSecurityGroupName') is not None:
for group in groups:
if group.name == i['SourceSecurityGroupName']:
source_group_id = group.id
break
try:
rule = rules_client.create(sec.id,
i['IpProtocol'],
i['FromPort'],
i['ToPort'],
i['CidrIp'])
rule = rules_client.create(
sec.id,
i.get('IpProtocol'),
i.get('FromPort'),
i.get('ToPort'),
i.get('CidrIp'),
source_group_id)
except clients.novaclient.exceptions.BadRequest as ex:
if ex.message.find('already exists') >= 0:
# no worries, the rule is already there
@ -181,6 +200,16 @@ class SecurityGroup(resource.Resource):
else:
return self.physical_resource_name()
def validate(self):
res = super(SecurityGroup, self).validate()
if res:
return res
if self.properties['SecurityGroupEgress'] and not(
self.properties['VpcId'] and
clients.neutronclient is not None):
raise exception.EgressRuleNotAllowed()
def resource_mapping():
return {

View File

@ -15,6 +15,7 @@
import collections
from heat.engine import clients
from heat.common import exception
from heat.common import template_format
from heat.engine import parser
from heat.engine import resource
@ -55,6 +56,24 @@ Resources:
FromPort : 80
ToPort : 80
CidrIp : 0.0.0.0/0
- IpProtocol: tcp
SourceSecurityGroupName: test
- IpProtocol: icmp
SourceSecurityGroupId: 1
'''
test_template_nova_with_egress = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
the_sg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: HTTP and SSH access
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
'''
test_template_neutron = '''
@ -74,11 +93,14 @@ Resources:
FromPort : 80
ToPort : 80
CidrIp : 0.0.0.0/0
- IpProtocol: tcp
SourceSecurityGroupId: wwww
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 10.0.1.0/24
- SourceSecurityGroupName: xxxx
'''
def setUp(self):
@ -142,9 +164,13 @@ Resources:
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', 22, 22, '0.0.0.0/0').AndReturn(None)
2, 'tcp', 22, 22, '0.0.0.0/0', None).AndReturn(None)
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', 80, 80, '0.0.0.0/0').AndReturn(None)
2, 'tcp', 80, 80, '0.0.0.0/0', None).AndReturn(None)
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', None, None, None, 1).AndReturn(None)
nova_sgr.SecurityGroupRuleManager.create(
2, 'icmp', None, None, None, 1).AndReturn(None)
# delete script
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
@ -172,6 +198,28 @@ Resources:
'cidr': '0.0.0.0/0'
},
'id': 131
}, {
'from_port': None,
'group': {
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'name': 'test'
},
'ip_protocol': 'tcp',
'to_port': None,
'parent_group_id': 2,
'ip_range': {},
'id': 132
}, {
'from_port': None,
'group': {
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'name': 'test'
},
'ip_protocol': 'icmp',
'to_port': None,
'parent_group_id': 2,
'ip_range': {},
'id': 133
}]
))
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
@ -179,6 +227,10 @@ Resources:
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.delete(131).AndReturn(None)
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.delete(132).AndReturn(None)
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.delete(133).AndReturn(None)
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sg.SecurityGroupManager.delete(2).AndReturn(None)
self.m.ReplayAll()
@ -197,20 +249,36 @@ Resources:
#create script
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
sg_name = utils.PhysName('test_stack', 'the_sg')
nova_sg.SecurityGroupManager.list().AndReturn([NovaSG(
id=2,
name=sg_name,
description='HTTP and SSH access',
rules=[],
)])
nova_sg.SecurityGroupManager.list().AndReturn([
NovaSG(
id=2,
name=sg_name,
description='HTTP and SSH access',
rules=[],
),
NovaSG(
id=1,
name='test',
description='FAKE_SECURITY_GROUP',
rules=[],
)
])
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', 22, 22, '0.0.0.0/0').AndRaise(
2, 'tcp', 22, 22, '0.0.0.0/0', None).AndRaise(
clients.novaclient.exceptions.BadRequest(
400, 'Rule already exists'))
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', 80, 80, '0.0.0.0/0').AndReturn(
2, 'tcp', 80, 80, '0.0.0.0/0', None).AndReturn(
clients.novaclient.exceptions.BadRequest(
400, 'Rule already exists'))
nova_sgr.SecurityGroupRuleManager.create(
2, 'tcp', None, None, None, 1).AndReturn(
clients.novaclient.exceptions.BadRequest(
400, 'Rule already exists'))
nova_sgr.SecurityGroupRuleManager.create(
2, 'icmp', None, None, None, 1).AndReturn(
clients.novaclient.exceptions.BadRequest(
400, 'Rule already exists'))
@ -240,6 +308,28 @@ Resources:
'cidr': '0.0.0.0/0'
},
'id': 131
}, {
'from_port': None,
'group': {
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'name': 'test'
},
'ip_protocol': 'tcp',
'to_port': None,
'parent_group_id': 2,
'ip_range': {},
'id': 132
}, {
'from_port': None,
'group': {
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'name': 'test'
},
'ip_protocol': 'icmp',
'to_port': None,
'parent_group_id': 2,
'ip_range': {},
'id': 133
}]
))
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
@ -249,6 +339,12 @@ Resources:
nova_sgr.SecurityGroupRuleManager.delete(131).AndRaise(
clients.novaclient.exceptions.NotFound('goneburger'))
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.delete(132).AndRaise(
clients.novaclient.exceptions.NotFound('goneburger'))
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sgr.SecurityGroupRuleManager.delete(133).AndRaise(
clients.novaclient.exceptions.NotFound('goneburger'))
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
nova_sg.SecurityGroupManager.delete(2).AndReturn(None)
clients.OpenStackClients.nova('compute').AndReturn(self.fc)
@ -271,6 +367,13 @@ Resources:
self.m.VerifyAll()
def test_security_group_nova_with_egress_rules(self):
t = template_format.parse(self.test_template_nova_with_egress)
stack = self.parse_stack(t)
sg = stack['the_sg']
self.assertRaises(exception.EgressRuleNotAllowed, sg.validate)
@utils.stack_delete_after
def test_security_group_neutron(self):
#create script
@ -287,7 +390,29 @@ Resources:
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'name': sg_name,
'description': 'HTTP and SSH access',
'security_group_rules': [],
'security_group_rules': [{
"direction": "egress",
"ethertype": "IPv4",
"id": "aaaa-1",
"port_range_max": None,
"port_range_min": None,
"protocol": None,
"remote_group_id": None,
"remote_ip_prefix": None,
"security_group_id": "aaaa",
"tenant_id": "f18ca530cc05425e8bac0a5ff92f7e88"
}, {
"direction": "egress",
"ethertype": "IPv6",
"id": "aaaa-2",
"port_range_max": None,
"port_range_min": None,
"protocol": None,
"remote_group_id": None,
"remote_ip_prefix": None,
"security_group_id": "aaaa",
"tenant_id": "f18ca530cc05425e8bac0a5ff92f7e88"
}],
'id': 'aaaa'
}
})
@ -295,6 +420,7 @@ Resources:
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -305,6 +431,7 @@ Resources:
}).AndReturn({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -317,6 +444,7 @@ Resources:
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 80,
'ethertype': 'IPv4',
@ -327,6 +455,7 @@ Resources:
}).AndReturn({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 80,
'ethertype': 'IPv4',
@ -336,9 +465,38 @@ Resources:
'id': 'cccc'
}
})
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': 'wwww',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': 'tcp',
'security_group_id': 'aaaa'
}
}).AndReturn({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': 'wwww',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': 'tcp',
'security_group_id': 'aaaa',
'id': 'dddd'
}
})
neutronclient.Client.delete_security_group_rule('aaaa-1').AndReturn(
None)
neutronclient.Client.delete_security_group_rule('aaaa-2').AndReturn(
None)
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': None,
'remote_ip_prefix': '10.0.1.0/24',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -349,13 +507,38 @@ Resources:
}).AndReturn({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': None,
'remote_ip_prefix': '10.0.1.0/24',
'port_range_min': 22,
'ethertype': 'IPv4',
'port_range_max': 22,
'protocol': 'tcp',
'security_group_id': 'aaaa',
'id': 'dddd'
'id': 'eeee'
}
})
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': 'xxxx',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': None,
'security_group_id': 'aaaa'
}
}).AndReturn({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': 'xxxx',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': None,
'security_group_id': 'aaaa',
'id': 'ffff'
}
})
@ -372,6 +555,7 @@ Resources:
'id': 'bbbb',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 22
@ -382,24 +566,50 @@ Resources:
'id': 'cccc',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 80
}, {
'direction': 'egress',
'direction': 'ingress',
'protocol': 'tcp',
'port_range_max': 22,
'port_range_max': None,
'id': 'dddd',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': 'wwww',
'remote_ip_prefix': None,
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': None
}, {
'direction': 'egress',
'protocol': 'tcp',
'port_range_max': 22,
'id': 'eeee',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '10.0.1.0/24',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 22
}, {
'direction': 'egress',
'protocol': None,
'port_range_max': None,
'id': 'ffff',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': None,
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': None
}],
'id': 'aaaa'}})
neutronclient.Client.delete_security_group_rule('bbbb').AndReturn(None)
neutronclient.Client.delete_security_group_rule('cccc').AndReturn(None)
neutronclient.Client.delete_security_group_rule('dddd').AndReturn(None)
neutronclient.Client.delete_security_group_rule('eeee').AndReturn(None)
neutronclient.Client.delete_security_group_rule('ffff').AndReturn(None)
neutronclient.Client.delete_security_group('aaaa').AndReturn(None)
self.m.ReplayAll()
@ -437,6 +647,7 @@ Resources:
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -449,6 +660,7 @@ Resources:
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 80,
'ethertype': 'IPv4',
@ -458,9 +670,23 @@ Resources:
}
}).AndRaise(
NeutronClientException(status_code=409))
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': 'wwww',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': 'tcp',
'security_group_id': 'aaaa'
}
}).AndRaise(
NeutronClientException(status_code=409))
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': None,
'remote_ip_prefix': '10.0.1.0/24',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -470,6 +696,19 @@ Resources:
}
}).AndRaise(
NeutronClientException(status_code=409))
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'egress',
'remote_group_id': 'xxxx',
'remote_ip_prefix': None,
'port_range_min': None,
'ethertype': 'IPv4',
'port_range_max': None,
'protocol': None,
'security_group_id': 'aaaa'
}
}).AndRaise(
NeutronClientException(status_code=409))
# delete script
neutronclient.Client.show_security_group('aaaa').AndReturn({
@ -484,6 +723,7 @@ Resources:
'id': 'bbbb',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 22
@ -494,19 +734,43 @@ Resources:
'id': 'cccc',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 80
}, {
'direction': 'egress',
'direction': 'ingress',
'protocol': 'tcp',
'port_range_max': 22,
'port_range_max': None,
'id': 'dddd',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': 'wwww',
'remote_ip_prefix': None,
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': None
}, {
'direction': 'egress',
'protocol': 'tcp',
'port_range_max': 22,
'id': 'eeee',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': '10.0.1.0/24',
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': 22
}, {
'direction': 'egress',
'protocol': None,
'port_range_max': None,
'id': 'ffff',
'ethertype': 'IPv4',
'security_group_id': 'aaaa',
'remote_group_id': None,
'remote_ip_prefix': None,
'tenant_id': 'f18ca530cc05425e8bac0a5ff92f7e88',
'port_range_min': None
}],
'id': 'aaaa'}})
neutronclient.Client.delete_security_group_rule('bbbb').AndRaise(
@ -515,6 +779,10 @@ Resources:
NeutronClientException(status_code=404))
neutronclient.Client.delete_security_group_rule('dddd').AndRaise(
NeutronClientException(status_code=404))
neutronclient.Client.delete_security_group_rule('eeee').AndRaise(
NeutronClientException(status_code=404))
neutronclient.Client.delete_security_group_rule('ffff').AndRaise(
NeutronClientException(status_code=404))
neutronclient.Client.delete_security_group('aaaa').AndRaise(
NeutronClientException(status_code=404))

View File

@ -192,6 +192,7 @@ class VPCTestBase(HeatTestCase):
neutronclient.Client.create_security_group_rule({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -202,6 +203,7 @@ class VPCTestBase(HeatTestCase):
}).AndReturn({
'security_group_rule': {
'direction': 'ingress',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'port_range_min': 22,
'ethertype': 'IPv4',
@ -226,6 +228,7 @@ class VPCTestBase(HeatTestCase):
'id': 'bbbb',
'ethertype': 'IPv4',
'security_group_id': 'eeee',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'tenant_id': 'c1210485b2424d48804aad5d39c61b8f',
'port_range_min': 22