class Chef::Provider::AwsSecurityGroup
Public Instance Methods
action_create()
click to toggle source
Calls superclass method
# File lib/chef/provider/aws_security_group.rb, line 11 def action_create sg = super apply_rules(sg) end
Protected Instance Methods
create_aws_object()
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 19 def create_aws_object converge_by "create security group #{new_resource.name} in #{region}" do options = { description: new_resource.description.to_s } options[:vpc_id] = new_resource.vpc if new_resource.vpc options[:group_name] = new_resource.name if options[:description].nil? || (options[:description] == "") options[:description] = new_resource.name.to_s end options = AWSResource.lookup_options(options, resource: new_resource) Chef::Log.debug("VPC: #{options[:vpc_id]}") sg = new_resource.driver.ec2_resource.create_security_group(options) retry_with_backoff(::Aws::EC2::Errors::InvalidSecurityGroupsIDNotFound, ::Aws::EC2::Errors::InvalidGroupNotFound) do new_resource.driver.ec2_resource.create_tags(resources: [sg.id], tags: [{ key: "Name", value: new_resource.name }]) end sg end end
destroy_aws_object(sg)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 51 def destroy_aws_object(sg) converge_by "delete security group #{new_resource} in #{region}" do sg.delete(dry_run: false) end end
update_aws_object(sg)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 38 def update_aws_object(sg) if !new_resource.description.nil? && new_resource.description != sg.description raise "Security group descriptions cannot be changed after being created! Desired description for #{new_resource.name} (#{sg.id}) was \"#{new_resource.description}\" and actual description is \"#{sg.description}\"" end unless new_resource.vpc.nil? desired_vpc = Chef::Resource::AwsVpc.get_aws_object_id(new_resource.vpc, resource: new_resource) if desired_vpc != sg.vpc_id raise "Security group VPC cannot be changed after being created! Desired VPC for #{new_resource.name} (#{sg.id}) was #{new_resource.vpc} (#{desired_vpc}) and actual VPC is #{sg.vpc_id}" end end apply_rules(sg) end
Private Instance Methods
add_rule(rules, port_ranges, actors)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 346 def add_rule(rules, port_ranges, actors) unless actors.empty? port_ranges.each do |port_range| rules[port_range] ||= Set.new rules[port_range] += actors end end end
apply_rules(sg)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 59 def apply_rules(sg) vpc = sg.vpc_id update_outbound_rules(sg, vpc) unless new_resource.outbound_rules.nil? update_inbound_rules(sg, vpc) unless new_resource.inbound_rules.nil? end
get_actors(vpc, actor_spec)
click to toggle source
Turns an actor_spec into a uniform array, containing CIDRs, ::Aws::EC2::LoadBalancers and ::Aws::EC2::SecurityGroups.
# File lib/chef/provider/aws_security_group.rb, line 398 def get_actors(vpc, actor_spec) result = case actor_spec # An array is always considered a list of actors. Each one may follow any supported format. when Array actor_spec.map { |a| get_actors(vpc, a) } # Hashes come in several forms: when Hash # The default AWS Ruby SDK form with :user_id, :group_id and :group_name forms if actor_spec.keys.all? { |key| %i{user_id group_id group_name}.include?(key) } if actor_spec.key?(:group_name) vpc_object = Chef::Resource::AwsVpc.get_aws_object(vpc, resource: new_resource) actor_spec[:group_id] ||= vpc_object.security_groups(filters: [name: "group-name", values: [actor_spec[:group_name]]]).first.id end actor_spec[:user_id] ||= new_resource.driver.account_id { user_id: actor_spec[:user_id], group_id: actor_spec[:group_id] } # load_balancer: <load balancer name> elsif actor_spec.keys == [:load_balancer] lb = Chef::Resource::AwsLoadBalancer.get_aws_object(actor_spec[:load_balancer], resource: new_resource) get_actors(vpc, lb) # security_group: <security group name> elsif actor_spec.keys == [:security_group] Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec[:security_group], resource: new_resource) else raise "Unable to reference security group with spec #{actor_spec}" end # If a load balancer is specified, grab it and then get its automatic security group when /^elb-[a-fA-F0-9]+$/, Aws::ElasticLoadBalancing::Types::LoadBalancerDescription, Chef::Resource::AwsLoadBalancer lb = actor_spec if lb.class != Aws::ElasticLoadBalancing::Types::LoadBalancerDescription lb = Chef::Resource::AwsLoadBalancer.get_aws_object(actor_spec, resource: new_resource) end # get secgroup via vpc_id vpc_object = Chef::Resource::AwsVpc.get_aws_object(vpc, resource: new_resource) results = vpc_object.security_groups.to_a.select { |s| s.group_name == lb.source_security_group.group_name } if results.size == 1 get_actors(vpc, results.first.id) else raise ::Chef::Provisioning::AWSDriver::Exceptions::MultipleSecurityGroupError.new(lb.source_security_group.group_name, results) end # If a security group is specified, grab it when /^sg-[a-fA-F0-9]+$/, ::Aws::EC2::SecurityGroup, Chef::Resource::AwsSecurityGroup Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec, resource: new_resource) # If an IP addresses / CIDR are passed, return it verbatim; otherwise, assume it's the # name of a security group. when String begin IPAddr.new(actor_spec) # Add /32 to the end of raw IP addresses actor_spec =~ /\// ? actor_spec : "#{actor_spec}/32" rescue IPAddr::InvalidAddressError Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec, resource: new_resource) end else raise "Unexpected actor #{actor_spec} / #{actor_spec.class} in rules list" end result = { user_id: result.owner_id, group_id: result.id } if result.is_a?(::Aws::EC2::SecurityGroup) [result].flatten end
get_port_ranges(port_spec)
click to toggle source
When protocol is unspecified (anything besides tcp, udp or icmp) then you cannot specify ports. When specifying tcp, udp, or icmp AWS
wants port_range 0..0. -1..-1 will cause error
# File lib/chef/provider/aws_security_group.rb, line 358 def get_port_ranges(port_spec) case port_spec when Integer port_spec = 0 if port_spec == -1 [{ port_range: port_spec..port_spec, protocol: :tcp }] when Range port_spec = 0..0 if port_spec == (-1..-1) [{ port_range: port_spec, protocol: :tcp }] when Array port_spec.map { |p| get_port_ranges(p) }.flatten when String, Symbol protocol = port_spec.to_s.downcase.to_sym if protocol.to_s =~ /(any|all|-1)/i [{ port_range: -1..-1, protocol: :"-1" }] else [{ port_range: 0..0, protocol: protocol }] end when Hash port_range = port_spec[:port_range] || port_spec[:ports] || port_spec[:port] || 0 port_range = port_range..port_range if port_range.is_a?(Integer) if port_spec[:protocol] protocol = port_spec[:protocol].to_s.downcase.to_sym if protocol.to_s =~ /(any|all|-1)/i [{ port_range: -1..-1, protocol: :"-1" }] else [{ port_range: port_range, protocol: protocol }] end else get_port_ranges(port_range) end # The to_s.to_sym dance is because if you specify a protocol number, AWS symbolifies it, # but 26.to_sym doesn't work (so we have to to_s it first). when nil [{ port_range: -1..-1, protocol: :"-1" }] end end
update_inbound_rules(sg, vpc)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 66 def update_inbound_rules(sg, vpc) # # Get desired rules # desired_rules = {} case new_resource.inbound_rules when Hash new_resource.inbound_rules.each do |sources_spec, port_spec| add_rule(desired_rules, get_port_ranges(port_spec), get_actors(vpc, sources_spec)) end when Array # [ { port: X, protocol: Y, sources: [ ... ]}] new_resource.inbound_rules.each do |rule| port_ranges = get_port_ranges(rule) add_rule(desired_rules, port_ranges, get_actors(vpc, rule[:sources])) end else raise ArgumentError, "inbound_rules must be a Hash or Array (was #{new_resource.inbound_rules.inspect})" end # # Actually update the rules (remove, add) # update_rules(desired_rules, sg.ip_permissions, authorize: proc do |port_range, protocol, actors| names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a } converge_by "authorize #{names.join(', ')} to send traffic to group #{new_resource.name} (#{sg.id}) on port_range #{port_range.inspect} with protocol #{protocol || 'nil'}" do names.each do |iprange| begin if iprange.include?("-") # user_id_group_pairs allows to add inbound rules for source security group sg.authorize_ingress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, user_id_group_pairs: actors }] ) # sg.authorize_ingress({ # group # ip_permissions: [{ # ip_protocol: protocol, # from_port: port_range.first, # to_port: port_range.last, # prefix_list_ids: [{ # prefix_list_id: iprange # }] # }] # }) else sg.authorize_ingress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, ip_ranges: [{ cidr_ip: iprange }] }] ) end rescue ::Aws::EC2::Errors::InvalidPermissionDuplicate => e Chef::Log.debug("Ignoring duplicate permission") end end end end, revoke: proc do |port_range, protocol, actors| names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a } converge_by "revoke the ability of #{names.join(', ')} to send traffic to group #{new_resource.name} (#{sg.id}) on port_range #{port_range.inspect} with protocol #{protocol || 'nil'}" do names.each do |iprange| begin if iprange.include?("-") # user_id_group_pairs allows to revoke inbound rules for source security group sg.revoke_ingress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, user_id_group_pairs: actors }] ) # sg.revoke_ingress({ # group # ip_permissions: [{ # ip_protocol: protocol, # from_port: port_range.first, # to_port: port_range.last, # prefix_list_ids: [{ # prefix_list_id: iprange # }] # }] # }) else sg.revoke_ingress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, ip_ranges: [{ cidr_ip: iprange }] }] ) end rescue ::Aws::EC2::Errors::InvalidPermissionNotFound => e Chef::Log.debug("Ignoring missing permission") end end end end) end
update_outbound_rules(sg, vpc)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 184 def update_outbound_rules(sg, vpc) # # Get desired rules # desired_rules = {} case new_resource.outbound_rules when Hash new_resource.outbound_rules.each do |port_spec, sources_spec| add_rule(desired_rules, get_port_ranges(port_spec), get_actors(vpc, sources_spec)) end when Array # [ { port: X, protocol: Y, sources: [ ... ]}] new_resource.outbound_rules.each do |rule| add_rule(desired_rules, get_port_ranges(rule), get_actors(vpc, rule[:destinations])) end else raise ArgumentError, "outbound_rules must be a Hash or Array (was #{new_resource.outbound_rules.inspect})" end # # Actually update the rules (remove, add) # Chef::Log.info("dr: #{desired_rules}") update_rules(desired_rules, sg.ip_permissions_egress, authorize: proc do |port_range, protocol, actors| Chef::Log.info("proto: #{protocol.inspect}") Chef::Log.info("port_range: #{port_range.inspect}") names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a } converge_by "authorize group #{new_resource.name} (#{sg.id}) to send traffic to #{names.join(', ')} on port_range #{port_range.inspect} with protocol #{protocol || 'nil'}" do names.each do |iprange| begin if iprange.include?("-") sg.authorize_egress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, user_id_group_pairs: actors }] ) # sg.authorize_egress({ # group # ip_permissions: [{ # ip_protocol: protocol, # from_port: port_range.first, # to_port: port_range.last, # prefix_list_ids: [{ # prefix_list_id: iprange # }] # }] # }) else sg.authorize_egress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, ip_ranges: [{ cidr_ip: iprange }] }] ) end rescue ::Aws::EC2::Errors::InvalidPermissionDuplicate => e Chef::Log.debug("Ignoring duplicate permission") end end end end, revoke: proc do |port_range, protocol, actors| names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a } converge_by "revoke the ability of group #{new_resource.name} (#{sg.id}) to send traffic to #{names.join(', ')} on port_range #{port_range.inspect} with protocol #{protocol || 'nil'}" do names.each do |iprange| begin if iprange.include?("-") sg.revoke_egress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, user_id_group_pairs: actors }] ) # sg.revoke_egress({ # group # ip_permissions: [{ # ip_protocol: protocol, # from_port: port_range.first, # to_port: port_range.last, # prefix_list_ids: [{ # prefix_list_id: iprange # }] # }] # }) else sg.revoke_egress( ip_permissions: [{ ip_protocol: protocol, from_port: port_range.first, to_port: port_range.last, ip_ranges: [{ cidr_ip: iprange }] }] ) end rescue ::Aws::EC2::Errors::InvalidPermissionNotFound => e Chef::Log.debug("Ignoring missing permission") end end end end) end
update_rules(desired_rules, actual_rules_list, authorize: nil, revoke: nil)
click to toggle source
# File lib/chef/provider/aws_security_group.rb, line 302 def update_rules(desired_rules, actual_rules_list, authorize: nil, revoke: nil) actual_rules = {} actual_rules_list.each do |rule| rule = rule.to_h port_range = { port_range: rule[:from_port] ? rule[:from_port]..rule[:to_port] : -1..-1, protocol: rule[:ip_protocol].to_s.to_sym } rule[:user_id_group_pairs].map! { |h| h.reject { |x| x == :group_name } } add_rule(actual_rules, [port_range], rule[:user_id_group_pairs]) if rule[:user_id_group_pairs] add_rule(actual_rules, [port_range], rule[:ip_ranges].map { |r| r[:cidr_ip] }) if rule[:ip_ranges] end # # Get the list of permissions to add and remove # actual_rules.each do |port_range, actors| next unless desired_rules[port_range] intersection = actors & desired_rules[port_range] # Anything unhandled in desired_rules will be added desired_rules[port_range] -= intersection # Anything unhandled in actual_rules will be removed actual_rules[port_range] -= intersection end # # Add any new rules # desired_rules.each do |port_range, actors| unless actors.empty? authorize.call(port_range[:port_range], port_range[:protocol], actors) end end # # Remove any rules no longer in effect # actual_rules.each do |port_range, actors| unless actors.empty? revoke.call(port_range[:port_range], port_range[:protocol], actors) end end end