Skip to content

Commit

Permalink
[aws|compute] Update security group operations.
Browse files Browse the repository at this point in the history
Changes and features include:

* Bulk operations support via indexed params
* Mocking updated for bulk operations
* Mocking updated to reflect more real behavior
* Many more tests
  • Loading branch information
danp committed Oct 28, 2011
1 parent 8f65b7b commit 99704bd
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 103 deletions.
153 changes: 119 additions & 34 deletions lib/fog/aws/requests/compute/authorize_security_group_ingress.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ class Real
# * 'SourceSecurityGroupName'<~String> - Name of security group to authorize
# * 'SourceSecurityGroupOwnerId'<~String> - Name of owner to authorize
# or
# * 'CidrIp' - CIDR range
# * 'FromPort' - Start of port range (or -1 for ICMP wildcard)
# * 'GroupName' - Name of group to modify
# * 'IpProtocol' - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'ToPort' - End of port range (or -1 for ICMP wildcard)
# * 'CidrIp'<~String> - CIDR range
# * 'FromPort'<~Integer> - Start of port range (or -1 for ICMP wildcard)
# * 'IpProtocol'<~String> - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'ToPort'<~Integer> - End of port range (or -1 for ICMP wildcard)
# or
# * 'IpPermissions'<~Array>:
# * permission<~Hash>:
# * 'FromPort'<~Integer> - Start of port range (or -1 for ICMP wildcard)
# * 'Groups'<~Array>:
# * group<~Hash>:
# * 'GroupName'<~String> - Name of security group to authorize
# * 'UserId'<~String> - Name of owner to authorize
# * 'IpProtocol'<~String> - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'IpRanges'<~Array>:
# * ip_range<~Hash>:
# * 'CidrIp'<~String> - CIDR range
# * 'ToPort'<~Integer> - End of port range (or -1 for ICMP wildcard)
#
# === Returns
# * response<~Excon::Response>:
Expand All @@ -30,8 +42,13 @@ def authorize_security_group_ingress(group_name, options = {})
if group_name.is_a?(Hash)
Fog::Logger.deprecation("Fog::AWS::Compute#authorize_security_group_ingress now requires the 'group_name' parameter. Only specifying an options hash is now deprecated [light_black](#{caller.first})[/]")
options = group_name
group_name = options['GroupName']
group_name = options.delete('GroupName')
end

if ip_permissions = options.delete('IpPermissions')
options.merge!(indexed_ip_permissions_params(ip_permissions))
end

request({
'Action' => 'AuthorizeSecurityGroupIngress',
'GroupName' => group_name,
Expand All @@ -40,6 +57,29 @@ def authorize_security_group_ingress(group_name, options = {})
}.merge!(options))
end

private

def indexed_ip_permissions_params(ip_permissions)
params = {}
ip_permissions.each_with_index do |permission, key_index|
key_index += 1
params[format('IpPermissions.%d.IpProtocol', key_index)] = permission['IpProtocol']
params[format('IpPermissions.%d.FromPort', key_index)] = permission['FromPort']
params[format('IpPermissions.%d.ToPort', key_index)] = permission['ToPort']
(permission['Groups'] || []).each_with_index do |group, group_index|
group_index += 1
params[format('IpPermissions.%d.Groups.%d.UserId', key_index, group_index)] = group['UserId']
params[format('IpPermissions.%d.Groups.%d.GroupName', key_index, group_index)] = group['GroupName']
params[format('IpPermissions.%d.Groups.%d.GroupId', key_index, group_index)] = group['GroupId']
end
(permission['IpRanges'] || []).each_with_index do |ip_range, range_index|
range_index += 1
params[format('IpPermissions.%d.IpRanges.%d.CidrIp', key_index, range_index)] = ip_range['CidrIp']
end
end
params.reject {|k, v| v.nil? }
end

end

class Mock
Expand All @@ -48,43 +88,36 @@ def authorize_security_group_ingress(group_name, options = {})
if group_name.is_a?(Hash)
Fog::Logger.deprecation("Fog::AWS::Compute#authorize_security_group_ingress now requires the 'group_name' parameter. Only specifying an options hash is now deprecated [light_black](#{caller.first})[/]")
options = group_name
group_name = options['GroupName']
group_name = options.delete('GroupName')
end

response = Excon::Response.new
group = self.data[:security_groups][group_name]

if group
group['ipPermissions'] ||= []
if group_name && source_group_name = options['SourceSecurityGroupName']
['tcp', 'udp'].each do |protocol|
group['ipPermissions'] << {
'groups' => [{'groupName' => source_group_name, 'userId' => (options['SourceSecurityGroupOwnerId'] || self.data[:owner_id]) }],
'fromPort' => 1,
'ipRanges' => [],
'ipProtocol' => protocol,
'toPort' => 65535
}
normalized_permissions = normalize_permissions(options)

normalized_permissions.each do |permission|
if matching_group_permission = find_matching_permission(group, permission)
if permission['groups'].any? {|pg| matching_group_permission['groups'].include?(pg) }
raise Fog::Compute::AWS::Error, "InvalidPermission.Duplicate => The permission '123' has already been authorized in the specified group"
end

if permission['ipRanges'].any? {|pr| matching_group_permission['ipRanges'].include?(pr) }
raise Fog::Compute::AWS::Error, "InvalidPermission.Duplicate => The permission '123' has already been authorized in the specified group"
end
end
group['ipPermissions'] << {
'groups' => [{'groupName' => source_group_name, 'userId' => (options['SourceSecurityGroupOwnerId'] || self.data[:owner_id]) }],
'fromPort' => -1,
'ipRanges' => [],
'ipProtocol' => 'icmp',
'toPort' => -1
}
else
group['ipPermissions'] << {
'groups' => [],
'fromPort' => options['FromPort'],
'ipRanges' => [],
'ipProtocol' => options['IpProtocol'],
'toPort' => options['ToPort']
}
if options['CidrIp']
group['ipPermissions'].last['ipRanges'] << { 'cidrIp' => options['CidrIp'] }
end

normalized_permissions.each do |permission|
if matching_group_permission = find_matching_permission(group, permission)
matching_group_permission['groups'] += permission['groups']
matching_group_permission['ipRanges'] += permission['ipRanges']
else
group['ipPermissions'] << permission
end
end

response.status = 200
response.body = {
'requestId' => Fog::AWS::Mock.request_id,
Expand All @@ -96,6 +129,58 @@ def authorize_security_group_ingress(group_name, options = {})
end
end

private

def normalize_permissions(options)
normalized_permissions = []

if options['SourceSecurityGroupName']
['tcp', 'udp'].each do |protocol|
normalized_permissions << {
'ipProtocol' => protocol,
'fromPort' => 1,
'toPort' => 65535,
'groups' => [{'groupName' => options['SourceSecurityGroupName'], 'userId' => options['SourceSecurityGroupOwnerId'] || self.data[:owner_id]}],
'ipRanges' => []
}
end
normalized_permissions << {
'ipProtocol' => 'icmp',
'fromPort' => -1,
'toPort' => -1,
'groups' => [{'groupName' => options['SourceSecurityGroupName'], 'userId' => options['SourceSecurityGroupOwnerId'] || self.data[:owner_id]}],
'ipRanges' => []
}
elsif options['CidrIp']
normalized_permissions << {
'ipProtocol' => options['IpProtocol'],
'fromPort' => Integer(options['FromPort']),
'toPort' => Integer(options['ToPort']),
'groups' => [],
'ipRanges' => [{'cidrIp' => options['CidrIp']}]
}
elsif options['IpPermissions']
options['IpPermissions'].each do |permission|
normalized_permissions << {
'ipProtocol' => permission['IpProtocol'],
'fromPort' => Integer(permission['FromPort']),
'toPort' => Integer(permission['ToPort']),
'groups' => (permission['Groups'] || []).map {|g| {'groupName' => g['GroupName'], 'userId' => g['UserId'] || self.data[:owner_id]} },
'ipRanges' => (permission['IpRanges'] || []).map {|r| { 'cidrIp' => r['CidrIp'] } }
}
end
end

normalized_permissions
end

def find_matching_permission(group, permission)
group['ipPermissions'].detect {|group_permission|
permission['ipProtocol'] == group_permission['ipProtocol'] &&
permission['fromPort'] == group_permission['fromPort'] &&
permission['toPort'] == group_permission['toPort'] }
end

end
end
end
Expand Down
25 changes: 25 additions & 0 deletions lib/fog/aws/requests/compute/delete_security_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,33 @@ def delete_security_group(name)

class Mock
def delete_security_group(name)
if name == 'default'
raise Fog::Compute::AWS::Error.new("InvalidGroup.Reserved => The security group 'default' is reserved")
end

response = Excon::Response.new
if self.data[:security_groups][name]

used_by_groups = []
self.region_data.each do |access_key, key_data|
key_data[:security_groups].each do |group_name, group|
next if group == self.data[:security_groups][name]

group['ipPermissions'].each do |group_ip_permission|
group_ip_permission['groups'].each do |group_group_permission|
if group_group_permission['groupName'] == name &&
group_group_permission['userId'] == self.data[:owner_id]
used_by_groups << "#{key_data[:owner_id]}:#{group_name}"
end
end
end
end
end

unless used_by_groups.empty?
raise Fog::Compute::AWS::Error.new("InvalidGroup.InUse => Group #{self.data[:owner_id]}:#{name} is used by groups: #{used_by_groups.uniq.join(" ")}")
end

self.data[:security_groups].delete(name)
response.status = 200
response.body = {
Expand Down
64 changes: 37 additions & 27 deletions lib/fog/aws/requests/compute/revoke_security_group_ingress.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@ class Real
# Remove permissions from a security group
#
# ==== Parameters
# * 'GroupName'<~String> - Name of group
# * group_name<~String> - Name of group
# * options<~Hash>:
# * 'SourceSecurityGroupName'<~String> - Name of security group to authorize
# * 'SourceSecurityGroupOwnerId'<~String> - Name of owner to authorize
# or
# * 'CidrIp' - CIDR range
# * 'FromPort' - Start of port range (or -1 for ICMP wildcard)
# * 'IpProtocol' - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'ToPort' - End of port range (or -1 for ICMP wildcard)
# * 'CidrIp'<~String> - CIDR range
# * 'FromPort'<~Integer> - Start of port range (or -1 for ICMP wildcard)
# * 'IpProtocol'<~String> - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'ToPort'<~Integer> - End of port range (or -1 for ICMP wildcard)
# or
# * 'IpPermissions'<~Array>:
# * permission<~Hash>:
# * 'FromPort'<~Integer> - Start of port range (or -1 for ICMP wildcard)
# * 'Groups'<~Array>:
# * group<~Hash>:
# * 'GroupName'<~String> - Name of security group to authorize
# * 'UserId'<~String> - Name of owner to authorize
# * 'IpProtocol'<~String> - Ip protocol, must be in ['tcp', 'udp', 'icmp']
# * 'IpRanges'<~Array>:
# * ip_range<~Hash>:
# * 'CidrIp'<~String> - CIDR range
# * 'ToPort'<~Integer> - End of port range (or -1 for ICMP wildcard)
#
# === Returns
# * response<~Excon::Response>:
Expand All @@ -29,8 +42,13 @@ def revoke_security_group_ingress(group_name, options = {})
if group_name.is_a?(Hash)
Fog::Logger.deprecation("Fog::AWS::Compute#revoke_security_group_ingress now requires the 'group_name' parameter. Only specifying an options hash is now deprecated [light_black](#{caller.first})[/]")
options = group_name
group_name = options['GroupName']
group_name = options.delete('GroupName')
end

if ip_permissions = options.delete('IpPermissions')
options.merge!(indexed_ip_permissions_params(ip_permissions))
end

request({
'Action' => 'RevokeSecurityGroupIngress',
'GroupName' => group_name,
Expand All @@ -47,34 +65,26 @@ def revoke_security_group_ingress(group_name, options = {})
if group_name.is_a?(Hash)
Fog::Logger.deprecation("Fog::AWS::Compute#revoke_security_group_ingress now requires the 'group_name' parameter. Only specifying an options hash is now deprecated [light_black](#{caller.first})[/]")
options = group_name
group_name = options['GroupName']
group_name = options.delete('GroupName')
end

response = Excon::Response.new
group = self.data[:security_groups][group_name]

if group
if source_group_name = options['SourceSecurityGroupName']
group['ipPermissions'].delete_if do |permission|
if source_owner_id = options['SourceSecurityGroupOwnerId']
permission['groups'].first['groupName'] == source_group_name && permission['groups'].first['userId'] == source_owner_id
else
permission['groups'].first['groupName'] == source_group_name
normalized_permissions = normalize_permissions(options)

normalized_permissions.each do |permission|
if matching_permission = find_matching_permission(group, permission)
matching_permission['ipRanges'] -= permission['ipRanges']
matching_permission['groups'] -= permission['groups']

if matching_permission['ipRanges'].empty? && matching_permission['groups'].empty?
group['ipPermissions'].delete(matching_permission)
end
end
else
ingress = group['ipPermissions'].select {|permission|
permission['fromPort'] == options['FromPort'] &&
permission['ipProtocol'] == options['IpProtocol'] &&
permission['toPort'] == options['ToPort'] &&
(
permission['ipRanges'].empty? ||
(
permission['ipRanges'].first &&
permission['ipRanges'].first['cidrIp'] == options['CidrIp']
)
)
}.first
group['ipPermissions'].delete(ingress)
end

response.status = 200
response.body = {
'requestId' => Fog::AWS::Mock.request_id,
Expand Down
25 changes: 15 additions & 10 deletions tests/aws/models/compute/security_group_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@

model_tests(Fog::Compute[:aws].security_groups, {:description => 'foggroupdescription', :name => 'foggroupname'}, true)

tests("a group with trailing whitespace") do
@group = Fog::Compute[:aws].security_groups.create(:name => "foggroup with spaces ", :description => " fog group desc ")
test("name is correct") do
@group.name == "foggroup with spaces "
end

test("description is correct") do
@group.description == " fog group desc "
end
tests("authorize and revoke helpers") do
@group = Fog::Compute[:aws].security_groups.create(:name => "foggroup", :description => "fog group desc")

@other_group = Fog::Compute[:aws].security_groups.create(:name => 'other group', :description => 'another group')
@other_group = Fog::Compute[:aws].security_groups.create(:name => 'fog other group', :description => 'another fog group')

test("authorize access by another security group") do
@group.authorize_group_and_owner(@other_group.name)
Expand All @@ -26,6 +19,18 @@
@group.ip_permissions.empty?
end

test("authorize access to a port range") do
@group.authorize_port_range(5000..6000)
@group.reload
@group.ip_permissions.size == 1
end

test("revoke access to a port range") do
@group.revoke_port_range(5000..6000)
@group.reload
@group.ip_permissions.empty?
end

@other_group.destroy
@group.destroy
end
Expand Down
Loading

0 comments on commit 99704bd

Please sign in to comment.