# Copyright 2011-2012 Amazon.com, Inc. or its affiliates. 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. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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.
module AWS
class EC2
# Represents a security group in EC2.
#
# @attr_reader [String] description The short informal description
# given when the group was created.
#
# @attr_reader [String] name The name of the security group.
#
# @attr_reader [String] owner_id The security group owner's id.
#
# @attr_reader [String,nil] vpc_id If this is a VPC security group,
# vpc_id is the ID of the VPC this group was created in.
# Returns false otherwise.
#
class SecurityGroup < Resource
AWS.register_autoloads(self, 'aws/ec2/security_group') do
autoload :IpPermission, 'ip_permission'
autoload :IpPermissionCollection, 'ingress_ip_permission_collection'
autoload :IngressIpPermissionCollection, 'ingress_ip_permission_collection'
autoload :EgressIpPermissionCollection, 'egress_ip_permission_collection'
end
include TaggedItem
def initialize security_group_id, options = {}
@security_group_id = security_group_id
super
end
# @return [String]
attr_reader :security_group_id
alias_method :group_id, :security_group_id
alias_method :id, :security_group_id
attribute :name, :as => :group_name, :static => true
attribute :owner_id, :static => true
attribute :vpc_id, :static => true
attribute :description, :as => :group_description, :static => true
attribute :ip_permissions_list, :as => :ip_permissions
attribute :ip_permissions_list_egress, :as => :ip_permissions_egress
populates_from(:describe_security_groups) do |resp|
resp.security_group_index[id]
end
# @return [Boolean] True if the security group exists.
def exists?
client.describe_security_groups(:filters => [
{ :name => "group-id", :values => [id] }
]).security_group_index.key?(id)
end
# Returns true if this security group is a VPC security group and
# not an EC2 security group. VPC security groups belong to a VPC
# subnet and can have egress rules.
# @return [Boolean] Returns true if this is a VPC security group and
# false if this is an EC2 security group.
def vpc?
vpc_id ? true : false
end
# @return [VPC,nil] Returns the VPC this security group belongs to,
# or nil if this is not a VPC security group.
def vpc
if vpc_id
VPC.new(vpc_id, :config => config)
end
end
# @return [SecurityGroup::IngressIpPermissionCollection] Returns a
# collection of {IpPermission} objects that represents all of
# the (ingress) permissions this security group has
# authorizations for.
def ingress_ip_permissions
IngressIpPermissionCollection.new(self, :config => config)
end
alias_method :ip_permissions, :ingress_ip_permissions
# @return [SecurityGroup::EgressIpPermissionCollection] Returns a
# collection of {IpPermission} objects that represents all of
# the egress permissions this security group has authorizations for.
def egress_ip_permissions
EgressIpPermissionCollection.new(self, :config => config)
end
# Adds ingress rules for ICMP pings. Defaults to 0.0.0.0/0 for
# the list of allowed IP ranges the ping can come from.
#
# security_group.allow_ping # anyone can ping servers in this group
#
# # only allow ping from a particular address
# security_group.allow_ping('123.123.123.123/0')
#
# @param [String] sources One or more IP ranges to allow ping from.
# Defaults to 0.0.0.0/0
#
# @return [nil]
#
def allow_ping *sources
sources << '0.0.0.0/0' if sources.empty?
authorize_ingress('icmp', -1, *sources)
end
# Removes ingress rules for ICMP pings. Defaults to 0.0.0.0/0 for
# the list of IP ranges to revoke.
#
# @param [String] sources One or more IP ranges to disallow ping from.
# Defaults to 0.0.0.0/0
#
# @return [nil]
#
def disallow_ping *sources
sources << '0.0.0.0/0' if sources.empty?
revoke_ingress('icmp', -1, *sources)
end
# Add an ingress rules to this security group.
# Ingress rules permit inbound traffic over a given protocol for
# a given port range from one or more souce ip addresses.
#
# This example grants the whole internet (0.0.0.0/0) access to port 80
# over TCP (HTTP web traffic).
#
# security_group.authorize_ingress(:tcp, 80)
#
# You can specify port ranges as well:
#
# # ftp
# security_group.authorize_ingress(:tcp, 20..21)
#
# == Sources
#
# Security groups accept ingress trafic from:
#
# * CIDR IP addresses
# * security groups
# * load balancers
#
# === Ip Addresses
#
# In the following example allow incoming SSH from a list of
# IP address ranges.
#
# security_group.authorize_ingress(:tcp, 22,
# '111.111.111.111/0', '222.222.222.222/0')
#
# === Security Groups
#
# To autohrize ingress traffic from all EC2 instance in another
# security group, just pass the security group:
#
# web = security_groups.create('webservers')
# db = security_groups.create('database')
# db.authorize_ingress(:tcp, 3306, web)
#
# You can also pass a hash of security group details instead of
# a {SecurityGroup} object.
#
# # by security group name
# sg.authorize_ingress(:tcp, 80, { :group_name => 'other-group' })
#
# # by security group id
# sg.authorize_ingress(:tcp, 80, { :group_id => 'sg-1234567' })
#
# If the security group belongs to a different account, just make
# sure it has the correct owner ID populated:
#
# not_my_sg = SecurityGroup.new('sg-1234567', :owner_id => 'abcxyz123')
# my_sg.authorize_ingress(:tcp, 80, not_my_sg)
#
# You can do the same with a hash as well (with either +:group_id+
# or +:group_name+):
#
# sg.authorize_ingress(:tcp, 21..22, { :group_id => 'sg-id', :user_id => 'abcxyz123' })
#
# === Load Balancers
#
# If you use ELB to manage load balancers, then you need to add
# ingress permissions to the security groups they route traffic into.
# You can do this by passing the {ELB::LoadBalancer} into
# authorize_ingress:
#
# load_balancer = AWS::ELB.new.load_balancers['web-load-balancer']
#
# sg.authorize_ingress(:tcp, 80, load_balancer)
#
# === Multiple Sources
#
# You can provide multiple sources each time you call authorize
# ingress, and you can mix and match the source types:
#
# sg.authorize_ingress(:tcp, 80, other_sg, '1.2.3.4/0', load_balancer)
#
# @param [String, Symbol] protocol Should be :tcp, :udp or :icmp
# or the string equivalent.
#
# @param [Integer, Range] ports The port (or port range) to allow
# traffic through. You can pass a single integer (like 80)
# or a range (like 20..21).
#
# @param [Mixed] sources One or more CIDR IP addresses,
# security groups, or load balancers. Security groups
# can be specified as hashes.
#
# A security group hash must provide either +:group_id+ or
# +:group_name+ for the security group. If the security group
# does not belong to you aws account then you must also
# provide +:user_id+ (which can be an AWS account ID or alias).
#
# @return [nil]
#
def authorize_ingress protocol, ports, *sources
client.authorize_security_group_ingress(
:group_id => id,
:ip_permissions => [ingress_opts(protocol, ports, sources)]
)
nil
end
# Revokes an ingress (inbound) ip permission. This is the inverse
# operation to {#authorize_ingress}. See {#authorize_ingress}
# for param and option documentation.
#
# @see #authorize_ingress
#
# @return [nil]
#
def revoke_ingress protocol, ports, *sources
client.revoke_security_group_ingress(
:group_id => id,
:ip_permissions => [ingress_opts(protocol, ports, sources)]
)
nil
end
# Authorize egress (outbound) traffic for a VPC security group.
#
# # allow traffic for all protocols/ports from the given sources
# security_group.authorize_egress('10.0.0.0/16', '10.0.0.1/16')
#
# # allow tcp traffic outband via port 80
# security_group.authorize_egress('10.0.0.0/16',
# :protocol => :tcp, :ports => 80..80)
#
# @note Calling this method on a non-VPC security group raises an error.
#
# @overload authorize_egress(*sources, options = {})
#
# @param [Mixed] sources One or more CIDR IP addresses,
# security groups or load balancers. See {#authorize_ingress}
# for more information on accepted formats for sources.
#
# @param [Hash] options
#
# @option options [Symbol] :protocol (:any) The protocol name or number
# to authorize egress traffic for. For a complete list of protocols
# see: {http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml}
#
# @option options [Range<Integer>,Integer] :ports (nil) An optional
# port or range of ports. This option is required depending on
# the protocol.
#
# @return [nil]
#
def authorize_egress *sources
client.authorize_security_group_egress(
:group_id => id,
:ip_permissions => [egress_opts(sources)])
nil
end
# Revokes an egress (outound) ip permission. This is the inverse
# operation to {#authorize_egress}. See {#authorize_egress}
# for param and option documentation.
#
# @see #authorize_egress
#
# @return [nil]
#
def revoke_egress *sources
client.revoke_security_group_egress(
:group_id => id,
:ip_permissions => [egress_opts(sources)])
nil
end
# Deletes this security group.
#
# If you attempt to delete a security group that contains
# instances, or attempt to delete a security group that is referenced
# by another security group, an error is raised. For example, if
# security group B has a rule that allows access from security
# group A, security group A cannot be deleted until the rule is
# removed.
# @return [nil]
def delete
client.delete_security_group(:group_id => id)
nil
end
# @private
def resource_type
'security-group'
end
# @private
def inflected_name
"group"
end
# @private
def self.describe_call_name
:describe_security_groups
end
def describe_call_name; self.class.describe_call_name; end
# @private
protected
def ingress_opts protocol, ports, sources
opts = {}
opts[:ip_protocol] = protocol.to_s.downcase
opts[:from_port] = Array(ports).first.to_i
opts[:to_port] = Array(ports).last.to_i
ips, groups = parse_sources(sources)
opts[:ip_ranges] = ips unless ips.empty?
opts[:user_id_group_pairs] = groups unless groups.empty?
opts
end
# @private
protected
def egress_opts args
ensure_vpc do
last = args.last
if last.is_a?(Hash) and (last.key?(:protocol) or last.key?(:ports))
# hashes at the end of egress methods could be a hash intedned
# to be a source, like:
#
# { :group_id => ..., :user_id => ... }
#
options = args.pop
else
options = {}
end
opts = {}
opts[:ip_protocol] = [nil,:any, '-1'].include?(options[:protocol]) ?
'-1' : options[:protocol].to_s.downcase
if options[:ports]
opts[:from_port] = Array(options[:ports]).first.to_i
opts[:to_port] = Array(options[:ports]).last.to_i
end
ips, groups = parse_sources(args)
opts[:ip_ranges] = ips unless ips.empty?
opts[:user_id_group_pairs] = groups unless groups.empty?
opts
end
end
# @private
protected
def parse_sources sources
ips = []
groups = []
sources.each do |source|
case source
when String
ips << { :cidr_ip => source }
when SecurityGroup
groups << { :group_id => source.id, :user_id => source.owner_id }
when ELB::LoadBalancer
groups << source.source_security_group
when Hash
# group name or id required
unless source.has_key?(:group_id) or source.has_key?(:group_name)
raise ArgumentError, 'invalid ip permission hash, ' +
'must provide :group_id or :group_name'
end
# prevent typos
unless source.keys - [:group_id, :group_name, :user_id] == []
raise ArgumentError, 'invalid ip permission hash, ' +
'only accepts the following keys, :group_id, :group_name, :user_id'
end
groups << source
else
raise ArgumentError, 'invalid ingress ip permission, ' +
'expected CIDR IP address or SecurityGroup'
end
end
ips << { :cidr_ip => '0.0.0.0/0' } if ips.empty? and groups.empty?
[ips, groups]
end
# @private
protected
def ensure_vpc &block
raise 'operation permitted for VPC security groups only' unless vpc?
yield
end
# @private
protected
def find_in_response(resp)
resp.security_group_index[id]
end
end
end
end