Skip to content
This repository was archived by the owner on Jun 5, 2020. It is now read-only.

Commit 2d34b16

Browse files
authored
Merge pull request #273 from daveseff/securitygroup_egress
Adding egress rules for VPC security groups.
2 parents f1e30db + 9c29347 commit 2d34b16

File tree

5 files changed

+114
-33
lines changed

5 files changed

+114
-33
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,11 @@ Rules for ingress traffic.
879879
880880
Accepts an array.
881881
882+
##### `egress`
883+
Optional.
884+
885+
Rules for egress traffic. Accepts an array. If no egress rules are specified, the security group will default to ALL outbound traffic allowed.
886+
882887
##### `id`
883888
884889
Read-only.

lib/puppet/provider/ec2_securitygroup/v2.rb

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def self.prefetch(resources)
4343
end
4444
end
4545

46-
def self.prepare_ingress_rule_for_puppet(region, rule, groups, group = nil, cidr = nil)
46+
def self.prepare_rule_for_puppet(region, rule, groups, group = nil, cidr = nil)
4747
config = {
4848
'protocol' => rule.ip_protocol,
4949
'from_port' => rule.from_port.to_i,
@@ -60,17 +60,22 @@ def self.prepare_ingress_rule_for_puppet(region, rule, groups, group = nil, cidr
6060
config
6161
end
6262

63-
def self.format_ingress_rules(region, group, groups)
63+
def self.format_rules(region, direction, group, groups)
6464
rules = []
65-
group[:ip_permissions].each do |rule|
65+
if direction == :ingress
66+
group_rules = group[:ip_permissions]
67+
elsif direction == :egress
68+
group_rules = group[:ip_permissions_egress]
69+
end
70+
group_rules.each do |rule|
6671
addition = []
6772
rule.user_id_group_pairs.each do |security_group|
68-
addition << prepare_ingress_rule_for_puppet(region, rule, groups, security_group)
73+
addition << prepare_rule_for_puppet(region, rule, groups, security_group)
6974
end
7075
rule.ip_ranges.each do |cidr|
71-
addition << prepare_ingress_rule_for_puppet(region, rule, groups, nil, cidr)
76+
addition << prepare_rule_for_puppet(region, rule, groups, nil, cidr)
7277
end
73-
addition << prepare_ingress_rule_for_puppet(region, rule, groups) if addition.empty?
78+
addition << prepare_rule_for_puppet(region, rule, groups) if addition.empty?
7479
rules << addition
7580
end
7681
rules.flatten.uniq.compact
@@ -82,7 +87,8 @@ def self.security_group_to_hash(region, group, groups, vpcs)
8287
name: group.group_name,
8388
description: group.description,
8489
ensure: :present,
85-
ingress: format_ingress_rules(region, group, groups),
90+
ingress: format_rules(region, :ingress, group, groups),
91+
egress: format_rules(region, :egress, group, groups),
8692
vpc: vpcs[group.vpc_id],
8793
vpc_id: group.vpc_id,
8894
region: region,
@@ -127,12 +133,14 @@ def create
127133
) if resource[:tags]
128134

129135
@property_hash[:id] = response.group_id
130-
rules = resource[:ingress]
131-
authorize_ingress(rules)
136+
ingress_rules = resource[:ingress]
137+
egress_rules = resource[:egress]
138+
authorize_rules(ingress_rules, :ingress)
139+
authorize_rules(egress_rules, :egress)
132140
@property_hash[:ensure] = :present
133141
end
134142

135-
def prepare_ingress_for_api(rule)
143+
def prepare_rule_for_api(rule)
136144
ec2 = ec2_client(resource[:region])
137145
from_port ||= rule['from_port'] || rule['port'] || 1
138146
to_port ||= rule['to_port'] || rule['port'] || 65535
@@ -185,27 +193,37 @@ def prepare_ingress_for_api(rule)
185193
rule_hash[:ip_permissions].any? ? rule_hash : nil
186194
end
187195

188-
def authorize_ingress(new_rules, existing_rules=[])
196+
def authorize_rules(new_rules, direction, existing_rules=[])
189197
ec2 = ec2_client(resource[:region])
190198
new_rules = [new_rules] unless new_rules.is_a?(Array)
191199

192200
parser = PuppetX::Puppetlabs::AwsIngressRulesParser.new(new_rules)
193201
to_create = parser.rules_to_create(existing_rules)
194202
to_delete = parser.rules_to_delete(existing_rules)
195203

196-
to_delete.compact.each do |rule|
197-
prepared_rule = prepare_ingress_for_api(rule) and
198-
ec2.revoke_security_group_ingress(prepared_rule)
204+
to_delete.reject(&:nil?).each do |rule|
205+
if direction == :ingress
206+
ec2.revoke_security_group_ingress(prepare_rule_for_api(rule))
207+
elsif direction == :egress
208+
ec2.revoke_security_group_egress(prepare_rule_for_api(rule))
209+
end
199210
end
200211

201-
to_create.compact.each do |rule|
202-
prepared_rule = prepare_ingress_for_api(rule) and
203-
ec2.authorize_security_group_ingress(prepared_rule)
212+
to_create.each do |rule|
213+
if direction == :ingress
214+
ec2.authorize_security_group_ingress(prepare_rule_for_api(rule))
215+
elsif direction == :egress
216+
ec2.authorize_security_group_egress(prepare_rule_for_api(rule))
217+
end
204218
end
205219
end
206220

207221
def ingress=(value)
208-
authorize_ingress(value, @property_hash[:ingress])
222+
authorize_rules(value, :ingress, @property_hash[:ingress])
223+
end
224+
225+
def egress=(value)
226+
authorize_rules(value, :egress, @property_hash[:egress])
209227
end
210228

211229
def destroy

lib/puppet/type/ec2_securitygroup.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ def insync?(is)
3434
end
3535
end
3636

37+
newproperty(:egress, :array_matching => :all) do
38+
desc 'rules for egress traffic'
39+
def insync?(is)
40+
for_comparison = Marshal.load(Marshal.dump(should))
41+
parser = PuppetX::Puppetlabs::AwsIngressRulesParser.new(for_comparison)
42+
to_create = parser.rules_to_create(is)
43+
to_delete = parser.rules_to_delete(is)
44+
to_create.empty? && to_delete.empty?
45+
end
46+
47+
validate do |value|
48+
fail 'egress should be a Hash' unless value.is_a?(Hash)
49+
end
50+
end
51+
3752
newproperty(:tags, :parent => PuppetX::Property::AwsTag) do
3853
desc 'the tags for the security group'
3954
end

spec/acceptance/securitygroup_spec.rb

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def get_group_permission(ip_permissions, group_name, protocol, from_port, to_por
3333
end
3434

3535
# a fairly naive matching algorithm, since the shape of ip_permissions is
36-
# quite different than the shape of our ingress rules
37-
def check_ingress_rule(rule, ip_permissions)
36+
# quite different than the shape of our rules
37+
def check_rule(rule, ip_permissions)
3838
if (rule.has_key? :security_group)
3939
group_name = rule[:security_group]
4040
protocols = rule[:protocol] || ['tcp', 'udp', 'icmp']
@@ -43,7 +43,7 @@ def check_ingress_rule(rule, ip_permissions)
4343
to_port = rule[:port] || rule[:to_port] || (protocol == 'icmp' ? -1 : 65535)
4444
get_group_permission(ip_permissions, group_name, protocol, from_port, to_port)
4545
end
46-
msg = "Could not find ingress rule for #{group_name}"
46+
msg = "Could not find rule for #{group_name}"
4747
else
4848
protocol = rule[:protocol] || 'tcp'
4949
from_port = rule[:port] || rule[:from_port] || (protocol == 'icmp' ? -1 : 1)
@@ -55,18 +55,18 @@ def check_ingress_rule(rule, ip_permissions)
5555
perm[:ip_ranges].any? { |ip| ip[:cidr_ip] == rule[:cidr] }
5656
end
5757

58-
msg = "Could not find ingress rule for #{protocol} from port #{from_port} to #{to_port} with CIDR #{rule[:cidr]}"
58+
msg = "Could not find rule for #{protocol} from port #{from_port} to #{to_port} with CIDR #{rule[:cidr]}"
5959
end
6060
[match, msg]
6161
end
6262

63-
def has_ingress_rule(rule, ip_permissions)
64-
match, msg = check_ingress_rule(rule, ip_permissions)
63+
def has_rule(rule, ip_permissions)
64+
match, msg = check_rule(rule, ip_permissions)
6565
expect(match).to eq(true), msg
6666
end
6767

68-
def doesnt_have_ingress_rule(rule, ip_permissions)
69-
match, msg = check_ingress_rule(rule, ip_permissions)
68+
def doesnt_have_rule(rule, ip_permissions)
69+
match, msg = check_rule(rule, ip_permissions)
7070
expect(match).to eq(false), msg
7171
end
7272

@@ -89,6 +89,15 @@ def doesnt_have_ingress_rule(rule, ip_permissions)
8989
:cidr => '0.0.0.0/0'
9090
}
9191
],
92+
:egress => [
93+
{
94+
:security_group => @name,
95+
},{
96+
:protocol => 'tcp',
97+
:port => 8080,
98+
:cidr => '0.0.0.0/0'
99+
}
100+
],
92101
:tags => {
93102
:department => 'engineering',
94103
:project => 'cloud',
@@ -125,13 +134,18 @@ def doesnt_have_ingress_rule(rule, ip_permissions)
125134

126135
it "with the specified ingress rules" do
127136
# perform a naive match
128-
@config[:ingress].all? { |rule| has_ingress_rule(rule, @group.ip_permissions)}
137+
@config[:ingress].all? { |rule| has_rule(rule, @group.ip_permissions)}
138+
end
139+
140+
it "with the specified egress rules" do
141+
# perform a naive match
142+
@config[:egress].all? { |rule| has_rule(rule, @group.ip_permissions)}
129143
end
130144

131145
it 'should be able to modify the ingress rules and recreate the security group' do
132146
new_rules = [{
133147
:protocol => 'tcp',
134-
:port => 80,
148+
:port => 8080,
135149
:cidr => '0.0.0.0/0'
136150
}]
137151
new_config = @config.dup.update({:ingress => new_rules})
@@ -141,8 +155,25 @@ def doesnt_have_ingress_rule(rule, ip_permissions)
141155
# should still have the original rules
142156
@group = get_group(@config[:name])
143157

144-
new_rules.all? { |rule| has_ingress_rule(rule, @group.ip_permissions)}
145-
@config[:ingress].all? { |rule| doesnt_have_ingress_rule(rule, @group.ip_permissions)}
158+
new_rules.all? { |rule| has_rule(rule, @group.ip_permissions)}
159+
@config[:ingress].all? { |rule| doesnt_have_rule(rule, @group.ip_permissions)}
160+
end
161+
162+
it 'should be able to modify the egress rules and recreate the security group' do
163+
new_rules = [{
164+
:protocol => 'tcp',
165+
:port => 80,
166+
:cidr => '0.0.0.0/0'
167+
}]
168+
new_config = @config.dup.update({:egress => new_rules})
169+
result = PuppetManifest.new(@template, new_config).apply
170+
expect(result.exit_code).to eq(2)
171+
172+
# should still have the original rules
173+
@group = get_group(@config[:name])
174+
175+
new_rules.all? { |rule| has_rule(rule, @group.ip_permissions_egress)}
176+
@config[:egress].all? { |rule| doesnt_have_rule(rule, @group.ip_permissions_egress)}
146177
end
147178

148179
describe 'that another group depends on in a secondary manifest' do
@@ -204,6 +235,13 @@ def doesnt_have_ingress_rule(rule, ip_permissions)
204235
:cidr => '0.0.0.0/0'
205236
},
206237
],
238+
:egress => [
239+
{
240+
:protocol => 'tcp',
241+
:port => 8080,
242+
:cidr => '0.0.0.0/0'
243+
},
244+
],
207245
:tags => {
208246
:department => 'engineering',
209247
:project => 'cloud',
@@ -425,7 +463,7 @@ def expect_rule_matches(ingress_rule, ip_permission)
425463
end
426464

427465
it "with the specified ingress rules" do
428-
@config[:ingress].all? { |rule| has_ingress_rule(rule, @group.ip_permissions)}
466+
@config[:ingress].all? { |rule| has_rule(rule, @group.ip_permissions)}
429467
end
430468

431469
rules_to_test = [
@@ -474,8 +512,8 @@ def expect_rule_matches(ingress_rule, ip_permission)
474512

475513
@group = get_group(@config[:name])
476514

477-
new_rules.all? { |rule| has_ingress_rule(rule, @group.ip_permissions)}
478-
@config[:ingress].all? { |rule| doesnt_have_ingress_rule(rule, @group.ip_permissions)}
515+
new_rules.all? { |rule| has_rule(rule, @group.ip_permissions)}
516+
@config[:ingress].all? { |rule| doesnt_have_rule(rule, @group.ip_permissions)}
479517
end
480518
end
481519

spec/unit/type/ec2_securitygroup_spec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
:description,
1717
:region,
1818
:ingress,
19+
:egress,
1920
:tags,
2021
:id,
2122
]
@@ -60,6 +61,10 @@
6061
expect(type_class).to require_hash_for('ingress')
6162
end
6263

64+
it "should require egress to be a hash" do
65+
expect(type_class).to require_hash_for('egress')
66+
end
67+
6368
it "should require tags to be a hash" do
6469
expect(type_class).to require_hash_for('tags')
6570
end

0 commit comments

Comments
 (0)