Class: Ironfan::Provider::Ec2::SecurityGroup

Inherits:
Resource show all
Defined in:
lib/ironfan/headers.rb,
lib/ironfan/provider/ec2/security_group.rb

Constant Summary collapse

WIDE_OPEN =
(-1..-1)

Instance Attribute Summary

Attributes inherited from Resource

#owner, #users

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Resource

aggregate!, #bogus?, create!, destroy!, forget, forget!, handle, known, #on_correlate, patiently, recall, recall?, receive, register, remember, save!, validate_computer!, validate_resources!

Methods inherited from Builder

ui, #ui

Class Method Details

.ensure_groups(computer) ⇒ Object

Utility


201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ironfan/provider/ec2/security_group.rb', line 201

def self.ensure_groups computer
  return unless Ec2.applicable computer  # Ensure the security_groups include those for cluster & facet
  # FIXME: This violates the DSL's immutability; it should be
  #   something calculated from within the DSL construction

  Ironfan.todo("CODE SMELL: violation of DSL immutability: #{caller}")
  server = computer.server
  cluster_name = "#{computer.server.realm_name}-#{computer.server.cluster_name}"
  server.security_group computer.server.realm_name
  realm_group = server.security_group cluster_name
  realm_group.authorized_by_group realm_group.name
  facet_name = "#{computer.server.realm_name}-#{computer.server.cluster_name}-#{computer.server.facet_name}"
  server.security_group facet_name
end

.expected_ids(computer) ⇒ Object


21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ironfan/provider/ec2/security_group.rb', line 21

def self.expected_ids(computer)
  return unless computer.server
  ec2 = computer.server.cloud(:ec2)

  server_groups = computer.server.security_groups
  cloud_groups = ec2.security_groups

  result = []
  [server_groups, cloud_groups].each do |container|
    container.keys.each { |name| result.push( group_name_with_vpc(name,ec2.vpc) )}
  end
  return result.uniq

end

.group_name_with_vpc(name, vpc_id = nil) ⇒ Object


190
191
192
# File 'lib/ironfan/provider/ec2/security_group.rb', line 190

def self.group_name_with_vpc(name,vpc_id=nil)
  vpc_id.nil? ? name.to_s : "#{vpc_id}:#{name.to_s}"
end

.load!(cluster = nil) ⇒ Object

Discovery


43
44
45
46
47
48
49
# File 'lib/ironfan/provider/ec2/security_group.rb', line 43

def self.load!(cluster=nil)
  Ec2.connection.security_groups.reject { |raw| raw.blank? }.each do |raw|
    sg =  SecurityGroup.new(:adaptee => raw)
    remember sg
    remember(sg, :id => sg.name.gsub( /^vpc-[^:]+:/, '') )
  end
end

.most_appropriate_group_name(group, vpc_id) ⇒ Object


194
195
196
# File 'lib/ironfan/provider/ec2/security_group.rb', line 194

def self.most_appropriate_group_name(group, vpc_id)
  vpc_id.present? ? group_name_with_vpc(group, vpc_id) : group
end

.multiple?Boolean

Returns:

  • (Boolean)

19
# File 'lib/ironfan/provider/ec2/security_group.rb', line 19

def self.multiple?()    true;   end

.prepare!(computers) ⇒ Object

Manipulation


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ironfan/provider/ec2/security_group.rb', line 75

def self.prepare!(computers)

  # Create any groups that don't yet exist, and ensure any authorizations
  # that are required for those groups
  cluster_name             = nil
  groups_to_create         = [ ]
  authorizations_to_ensure = [ ]

  computers.each{|comp| ensure_groups(comp) if Ec2.applicable(comp) } # Add facet and cluster security groups for the computer

  # First, deduce the list of all groups to which at least one instance belongs
  # We'll use this later to decide whether to create groups, or authorize access,
  # using a VPC security group or an EC2 security group.
  groups_that_should_exist = computers.map{|comp| expected_ids(comp) }.flatten.compact.sort.uniq
  groups_to_create << groups_that_should_exist

  computers.select { |computer| Ec2.applicable computer }.each do |computer|
    cloud           = computer.server.cloud(:ec2)
    cluster_name    = computer.server.cluster_name

    # Iterate over all of the security group information, keeping track of
    # any groups that must exist and any authorizations that must be ensured
    [computer.server.security_groups, cloud.security_groups].each do |container|
      container.values.each do |dsl_group|
        
        groups_to_create << dsl_group.name

        groups_to_create << dsl_group.group_authorized.map do |other_group|
          most_appropriate_group_name(other_group, cloud.vpc)
        end
        
        groups_to_create << dsl_group.group_authorized_by.map do |other_group|
          most_appropriate_group_name(other_group, cloud.vpc)
        end
        
        authorizations_to_ensure << dsl_group.group_authorized.map do |other_group|
          {
            :grantor      => most_appropriate_group_name(dsl_group.name, cloud.vpc),
            :grantee      => most_appropriate_group_name(other_group, cloud.vpc),
            :grantee_type => :group,
            :range        => WIDE_OPEN,
          }
        end
        
        authorizations_to_ensure << dsl_group.group_authorized_by.map do |other_group|
          {
            :grantor      => most_appropriate_group_name(other_group, cloud.vpc),
            :grantee      => most_appropriate_group_name(dsl_group.name, cloud.vpc),
            :grantee_type => :group,
            :range        => WIDE_OPEN,
          }
        end
        
        authorizations_to_ensure << dsl_group.range_authorizations.map do |range_auth|
          range, cidr, protocol = range_auth
          {
            :grantor      => group_name_with_vpc(dsl_group.name, cloud.vpc),
            :grantee      => { :cidr_ip => cidr, :ip_protocol => protocol },
            :grantee_type => :cidr,
            :range        => range,
          }
        end
      end
    end
  end
  groups_to_create         = groups_to_create.flatten.uniq.reject { |group| recall? group.to_s }.sort
  authorizations_to_ensure = authorizations_to_ensure.flatten.uniq.sort { |a,b| a[:grantor] <=> b[:grantor] }

  Ironfan.step(cluster_name, "creating security groups", :blue) unless groups_to_create.empty?
  groups_to_create.each do |group|
    if group =~ /\//
      Ironfan.step(group, "  assuming that owner/group pair #{group} already exists", :blue)
    else
      Ironfan.step(group, "  creating #{group} security group", :blue)
      begin
        tokens    = group.to_s.split(':')
        group_id  = tokens.pop
        vpc_id    = tokens.pop
        Ec2.connection.create_security_group(group_id,"Ironfan created group #{group_id}",vpc_id)
      rescue Fog::Compute::AWS::Error => e # InvalidPermission.Duplicate
        case e.message
        when /SecurityGroupLimitExceeded/
          raise e
        else
          Chef::Log.info("ignoring security group error: #{e}")
        end
      end
    end
  end

  # Re-load everything so that we have a @@known list of security groups to manipulate
  load! unless groups_to_create.empty?

  # Now make sure that all required authorizations are present
  Ironfan.step(cluster_name, "ensuring security group permissions", :blue) unless authorizations_to_ensure.empty?
  authorizations_to_ensure.each do |auth|
    grantor_fog = recall(auth[:grantor])
    if :group == auth[:grantee_type]
      if fog_grantee = recall(auth[:grantee])
        options = { :group => fog_grantee.group_id }
      elsif auth[:grantee] =~ /\//
        options = { :group_alias => auth[:grantee] }
      else
        raise "Don't know what to do with authorization grantee #{auth[:grantee]}"
      end
      message = "  ensuring access from #{auth[:grantee]} to #{auth[:grantor]}"
    else
      options = auth[:grantee]
      message = "  ensuring #{auth[:grantee][:ip_protocol]} access from #{auth[:grantee][:cidr_ip]} to #{auth[:range]}"
    end
    Ironfan.step(auth[:grantor], message, :blue)
    safely_authorize(grantor_fog, auth[:range], options)
  end
end

.resource_typeObject


20
# File 'lib/ironfan/provider/ec2/security_group.rb', line 20

def self.resource_type()        :security_group;   end

.safely_authorize(fog_group, range, options) ⇒ Object

Try an authorization, ignoring duplicates (this is easier than correlating). Do so for both TCP and UDP, unless only one is specified


218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/ironfan/provider/ec2/security_group.rb', line 218

def self.safely_authorize(fog_group,range,options)

  if options[:group_alias]    # In this case, we must first extract the group name
    # before recursively calling this function with it.


    owner, group = options.delete(:group_alias).split(/\//)
    Chef::Log.debug("authorizing group alias #{options[:group_alias].inspect} to group #{fog_group.name}")
    group_id = Ec2.connection.security_groups.get(group).group_id
    safely_authorize(fog_group, range, options.merge(group: group_id))
  elsif options[:ip_protocol]    # In this case, we've received the ip_protocol. With or
    # without a group name, we have enough information to open
    # the security group.


    Chef::Log.debug("authorizing to #{fog_group.name} with options #{options.inspect}")
    self.patiently(fog_group.name, Fog::Compute::AWS::Error, :ignore => Proc.new { |e| e.message =~ /Duplicate/ }) do
      fog_group.authorize_port_range(range,options)
    end
  else
    # Without an IP protocol, we'll open all of the relevant
    # ones. On non-VPC, that means tcp, udp, and icmp. On VPC,
    # that means -1 for all protocols.

    Chef::Log.debug([
        "didn't receive ip_protocol for authorization to #{fog_group.name} ",
        "with options #{options.inspect}. assuming all protocols"
      ].join)
    if fog_group.vpc_id.nil?      # Non-VPC does not support -1 for all protocols, so
      # we'll need to do each protocol indendently. If we
      # haven't received an ip_protocol, we'll assume the user
      # meant to open everything.

      safely_authorize(fog_group, 1..65535, options.merge(:ip_protocol => 'tcp'))
      safely_authorize(fog_group, 1..65535, options.merge(:ip_protocol => 'udp'))
      safely_authorize(fog_group, -1..-1,   options.merge(:ip_protocol => 'icmp'))
    else
      # In VPC, we should use only one rule to conserve rules.
      safely_authorize(fog_group,range,options.merge(:ip_protocol => -1))
    end
  end
end

.shared?Boolean

Returns:

  • (Boolean)

18
# File 'lib/ironfan/provider/ec2/security_group.rb', line 18

def self.shared?()      true;   end

Instance Method Details

#nameObject


36
37
38
# File 'lib/ironfan/provider/ec2/security_group.rb', line 36

def name()
  self.class.group_name_with_vpc(adaptee.name, adaptee.vpc_id)
end

#receive_adaptee(obj) ⇒ Object


51
52
53
54
# File 'lib/ironfan/provider/ec2/security_group.rb', line 51

def receive_adaptee(obj)
  obj = Ec2.connection.security_groups.new(obj) if obj.is_a?(Hash)
  super
end

#to_sObject


56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ironfan/provider/ec2/security_group.rb', line 56

def to_s
  if ip_permissions.present?
    perm_str = ip_permissions.map{|perm|
      "%s:%s-%s (%s | %s)" % [
        perm['ipProtocol'], perm['fromPort'], perm['toPort'],
        perm['groups'  ].map{|el| el['groupName'] }.join(','),
        perm['ipRanges'].map{|el| el['cidrIp']    }.join(','),
      ]
    }
    return "<%-15s %-12s %-25s %s>" % [ self.class.handle, group_id, name, perm_str]
  else
    return "<%-15s %-12s %s>" % [ self.class.handle, group_id, name ]
  end
end