ec2-security-czar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Ec2SecurityCzar
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'ec2-security-czar/base'
2
+ require_relative 'ec2-security-czar/security_group'
3
+ require_relative 'ec2-security-czar/rule'
4
+ require_relative 'ec2-security-czar/version'
@@ -0,0 +1,94 @@
1
+ require 'spec_helper.rb'
2
+ require 'ec2-security-czar/base'
3
+
4
+ module Ec2SecurityCzar
5
+ describe Base do
6
+ let(:access_key){ 'aws-key' }
7
+ let(:secret_access_key){ 'aws-secret-key' }
8
+ let(:region) { 'us-east-1' }
9
+ let(:aws_conf) { { access_key: access_key, secret_key: secret_access_key } }
10
+ let(:ec2) { double }
11
+ let(:environment) {double}
12
+
13
+ before do
14
+ allow(File).to receive(:exists?).with('config/aws_keys.yml').and_return(true)
15
+ allow(YAML).to receive(:load_file).and_return(aws_conf)
16
+ stub_const("AWS", double("AWS const"))
17
+ stub_const("SecurityGroup", double("Security Group"))
18
+ allow(AWS).to receive(:ec2).and_return(ec2)
19
+ allow(AWS).to receive(:config)
20
+ allow(SecurityGroup).to receive(:update_security_groups) {[]}
21
+ end
22
+
23
+ context ".new" do
24
+ subject { Base }
25
+
26
+ context "without mfa" do
27
+ it "configures the AWS sdk" do
28
+ expect(AWS).to receive(:config).with(
29
+ hash_including(access_key_id: access_key, secret_access_key: secret_access_key, region: region)
30
+ )
31
+ allow(YAML).to receive(:load_file).with('config/aws_keys.yml').and_return(aws_conf)
32
+ subject.new
33
+ end
34
+ end
35
+
36
+ context "with mfa" do
37
+ let(:mfa_token) { '12345' }
38
+ let(:mfa_serial_number) { 'aws-mfa-serial' }
39
+ let(:aws_conf) { { access_key: access_key, secret_key: secret_access_key, mfa_serial_number: mfa_serial_number } }
40
+
41
+ it "configures the AWS sdk" do
42
+ allow_any_instance_of(Base).to receive(:mfa_auth)
43
+ expect(AWS).to receive(:config).with(
44
+ hash_including(access_key_id: access_key, secret_access_key: secret_access_key, region: region)
45
+ )
46
+ subject.new
47
+ end
48
+ it "runs mfa auth" do
49
+ expect_any_instance_of(Base).to receive(:mfa_auth).with(mfa_token)
50
+ subject.new(nil, token: mfa_token)
51
+ end
52
+ end
53
+ end
54
+
55
+ context "#load_config" do
56
+ subject { Base }
57
+ context "no environment is set" do
58
+ it "loads the config for the default environment" do
59
+ expect(aws_conf).to_not receive(:[]).with(nil)
60
+ subject.new
61
+ end
62
+ end
63
+ context "environment is set" do
64
+ let(:environment) { 'environment' }
65
+ let(:environment_conf) { { environment => aws_conf } }
66
+
67
+ before do
68
+ allow(YAML).to receive(:load_file).with("config/aws_keys.yml").and_return(environment_conf)
69
+ end
70
+
71
+ it "loads the config for the passed environment" do
72
+ expect(AwsConfig).to receive(:[]).with(environment_conf).and_return(environment_conf)
73
+ expect(environment_conf).to receive(:[]).with(environment).and_return(aws_conf)
74
+ subject.new(environment)
75
+ end
76
+ end
77
+ end
78
+
79
+ context "#update_security_groups" do
80
+ let(:environment) { 'environment' }
81
+ let(:environment_conf) { { environment => aws_conf } }
82
+
83
+ before do
84
+ subject.instance_variable_set("@environment", environment)
85
+ allow(YAML).to receive(:load_file).with("config/aws_keys.yml").and_return(environment_conf)
86
+ end
87
+
88
+ it "delegates to the SecurityGroup class" do
89
+ expect(SecurityGroup).to receive(:update_security_groups).with(ec2, environment, region)
90
+ subject.update_security_groups
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper.rb'
2
+ require 'ec2-security-czar/rule'
3
+ require 'ec2-security-czar/security_group'
4
+
5
+ module Ec2SecurityCzar
6
+ describe Rule do
7
+ let(:direction) { :outbound }
8
+ let(:ip_range) { '0.0.0.0/0' }
9
+ let(:group_id) { 'sec-group' }
10
+ let(:group) { {group_id: group_id} }
11
+ let(:protocol) { :tcp }
12
+ let(:port_range) { '666' }
13
+ let(:api_object) { double }
14
+ let(:options) {
15
+ {
16
+ direction: direction,
17
+ ip_range: ip_range,
18
+ protocol: protocol,
19
+ group: group,
20
+ port_range: port_range,
21
+ api_object: api_object,
22
+ }
23
+ }
24
+
25
+ before do
26
+ allow(subject).to receive(:say)
27
+ allow(subject).to receive(:pretty_print)
28
+ end
29
+
30
+ subject { Rule.new(options) }
31
+
32
+ context "#equal?" do
33
+ it "returns true if all options are equal" do
34
+ expect(subject.equal?(subject.dup)).to be_truthy
35
+ end
36
+
37
+ it "returns false if all options are not equal" do
38
+ bogus_rule = Rule.new(options.merge(ip_range: '1.1.1.1/32'))
39
+ expect(subject.equal?(bogus_rule)).to be_falsey
40
+ end
41
+
42
+ context "rule with group name to a group id" do
43
+ let(:group_name) { 'sec-group-name' }
44
+
45
+ it "returns true if the group ids are the same" do
46
+ allow(SecurityGroup).to receive(:lookup).with(group_name).and_return(group_id)
47
+ allow(group_id).to receive(:id).and_return(group_id)
48
+ equivalent_rule = Rule.new(options.merge(group: { group_name: group_name }))
49
+ expect(subject.equal?(equivalent_rule)).to be_truthy
50
+ end
51
+ end
52
+ end
53
+
54
+ context "#authorize!" do
55
+ let(:security_group_api) { double("Security Group API") }
56
+ context "outbound rule" do
57
+ let(:direction) { :outbound }
58
+ it "authorizes an egress rule for aws" do
59
+ expect(security_group_api).to receive(:authorize_egress).with(ip_range, hash_including(protocol: protocol, ports: port_range))
60
+ subject.authorize!(security_group_api)
61
+ end
62
+
63
+ it "does not authorize an ingress rule for aws" do
64
+ allow(security_group_api).to receive(:authorize_egress)
65
+ expect(security_group_api).to_not receive(:authorize_ingress)
66
+ subject.authorize!(security_group_api)
67
+ end
68
+ end
69
+
70
+ context "inbound rule" do
71
+ let(:direction) { :inbound }
72
+ it "authorizes an ingress rule for aws" do
73
+ expect(security_group_api).to receive(:authorize_ingress).with(protocol, port_range, ip_range)
74
+ subject.authorize!(security_group_api)
75
+ end
76
+
77
+ it "does not authorize an egress rule for aws" do
78
+ allow(security_group_api).to receive(:authorize_ingress)
79
+ expect(security_group_api).to_not receive(:authorize_egress)
80
+ subject.authorize!(security_group_api)
81
+ end
82
+ end
83
+
84
+ context "group rule" do
85
+ let(:direction) { :inbound }
86
+ let(:group_id) { 'sec-group' }
87
+ let(:options) {
88
+ {
89
+ direction: direction,
90
+ group: { group_id: group_id },
91
+ protocol: protocol,
92
+ port_range: port_range,
93
+ api_object: api_object,
94
+ }
95
+ }
96
+
97
+ it "passes the group_id as a hash" do
98
+ expect(security_group_api).to receive(:authorize_ingress).with(
99
+ protocol, port_range, { group_id: group_id }
100
+ )
101
+ subject.authorize!(security_group_api)
102
+ end
103
+ end
104
+
105
+ it "rescues an api error" do
106
+ allow(security_group_api).to receive(:authorize_egress).and_raise(StandardError)
107
+ expect(subject).to receive(:say).twice
108
+ expect { subject.authorize!(security_group_api) }.to_not raise_error
109
+ end
110
+ end
111
+
112
+ context "#revoke!" do
113
+ it "calls revoke on it's api object" do
114
+ expect(api_object).to receive(:revoke)
115
+ subject.revoke!
116
+ end
117
+
118
+ it "rescues an api error" do
119
+ allow(api_object).to receive(:revoke).and_raise(StandardError)
120
+ expect(subject).to receive(:say).twice
121
+ expect { subject.revoke! }.to_not raise_error
122
+ end
123
+ end
124
+
125
+ context "#group_id" do
126
+ context "given a string" do
127
+ let(:group) { "sec-group" }
128
+ it "returns the passed in string as the security group id" do
129
+ expect(subject.group_id(group)).to equal(group)
130
+ end
131
+ end
132
+
133
+ context "given a hash with group_id" do
134
+ let(:group) { { group_id: "sec-group" } }
135
+ it "returns the group id" do
136
+ expect(subject.group_id(group)).to equal(group[:group_id])
137
+ end
138
+ end
139
+
140
+ context "given a hash with group_name" do
141
+ let(:group_name) { 'sec-group' }
142
+ let(:group) { {group_id: group_id, group_name: group_name} }
143
+
144
+ it "returns the matching group id" do
145
+ allow(SecurityGroup).to receive(:lookup).with(group[:group_name]).and_return(group_id)
146
+ expect(subject.group_id(group)).to equal(group_id)
147
+ end
148
+ end
149
+ end
150
+
151
+ context ".rules_from_api" do
152
+ subject { Rule }
153
+ let(:rules) { [double(port_range: 123, protocol: :tcp, ip_ranges: ['0.0.0.0/0'], groups: [double(id: 'sec-group')])] }
154
+ let(:direction) { :outbound }
155
+
156
+ it "returns an array of rules" do
157
+ expect(subject.rules_from_api(rules, direction)).to be_an_array_of(Rule)
158
+ end
159
+ end
160
+
161
+ context ".rules_from_api" do
162
+ subject { Rule }
163
+ let(:api_rule) { double(port_range: 123, protocol: :tcp, ip_ranges: ['0.0.0.0/0'], groups: [double(id: 'sec-group')]) }
164
+ let(:rules) { [api_rule] }
165
+ let(:direction) { :outbound }
166
+
167
+ it "returns an array of rules" do
168
+ expect(subject.rules_from_api(rules, direction)).to be_an_array_of(Rule)
169
+ end
170
+ end
171
+
172
+ context ".rules_from_config" do
173
+ subject { Rule }
174
+ let(:config_rule) { { port_range: 123, protocol: :tcp, ip_ranges: ['0.0.0.0/0'], groups: [double(id: 'sec-group')] } }
175
+ let(:direction) { :outbound }
176
+ let(:rules) { {outbound: [config_rule]} }
177
+
178
+ it "returns an array of rules" do
179
+ expect(subject.rules_from_config(rules, direction)).to be_an_array_of(Rule)
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,240 @@
1
+ require 'spec_helper.rb'
2
+ require 'ec2-security-czar/security_group'
3
+
4
+ module Ec2SecurityCzar
5
+ describe SecurityGroup do
6
+ let(:outbound_rule) {
7
+ {
8
+ zone: "Test Outbound",
9
+ justification: "Test outbound justification",
10
+ groups: [ 'test-outbound-group-id' ],
11
+ protocol: :udp,
12
+ port_range: '666'
13
+ }
14
+ }
15
+ let(:inbound_rule) {
16
+ {
17
+ zone: "Test Inbound",
18
+ justification: "Test inbound justification",
19
+ groups: [ 'test-inbound-group-id' ],
20
+ protocol: :tcp,
21
+ port_range: '999'
22
+ }
23
+ }
24
+ let(:api) { double("API", name: 'test') }
25
+ let(:config) { { outbound: [outbound_rule], inbound: [inbound_rule] } }
26
+ let(:filename) { 'the/config/file' }
27
+ let(:file) { "Raw File" }
28
+ let(:parsed_file) { { derp: :herp } }
29
+ let(:environment) { 'environment' }
30
+ let(:region) { 'us-east-1'}
31
+ let(:ec2) { double }
32
+
33
+
34
+ before do
35
+ allow(SecurityGroup).to receive(:ec2) {ec2}
36
+ allow(SecurityGroup).to receive(:region) {region}
37
+ allow(SecurityGroup).to receive(:environment) {environment}
38
+ allow(File).to receive(:read).and_return(file)
39
+ allow_any_instance_of(SecurityGroup).to receive(:config_filename).and_return(filename)
40
+ allow(File).to receive(:exists?).with(filename).and_return(true)
41
+ end
42
+
43
+ context "#update_rules" do
44
+ let(:delete_inbound) { double("Inbound rule to be deleted") }
45
+ let(:delete_outbound) { double("Outbound rule to be deleted") }
46
+ let(:addition_inbound) { double("Inbound rule to be added") }
47
+ let(:addition_outbound) { double("Outbound rule to be added") }
48
+
49
+ before do
50
+ allow(ERB).to receive(:new).and_return(double(:result => parsed_file))
51
+ allow(YAML).to receive(:load).and_return(parsed_file)
52
+ allow(subject).to receive(:new_rules).with(:outbound).and_return([addition_outbound])
53
+ allow(subject).to receive(:new_rules).with(:inbound).and_return([addition_inbound])
54
+ allow(subject).to receive(:current_rules).with(:outbound).and_return([delete_outbound])
55
+ allow(subject).to receive(:current_rules).with(:inbound).and_return([delete_inbound])
56
+ allow(subject).to receive(:say)
57
+ allow(SecurityGroup).to receive(:lookup) {api}
58
+ end
59
+
60
+ subject { SecurityGroup.new(api, environment) }
61
+
62
+ it "revokes rules that have been deleted" do
63
+ allow(addition_outbound).to receive(:authorize!)
64
+ allow(addition_inbound).to receive(:authorize!)
65
+ expect(delete_inbound).to receive(:revoke!)
66
+ expect(delete_outbound).to receive(:revoke!)
67
+ subject.update_rules
68
+ end
69
+
70
+ it "authorizes rules that have been added" do
71
+ allow(delete_inbound).to receive(:revoke!)
72
+ allow(delete_outbound).to receive(:revoke!)
73
+ expect(addition_outbound).to receive(:authorize!).with(api)
74
+ expect(addition_inbound).to receive(:authorize!).with(api)
75
+ subject.update_rules
76
+ end
77
+ end
78
+
79
+ context "#load_rules" do
80
+ let(:environment) { 'parsed' }
81
+ let(:erb_file) { "--- \nenvironment: <%= environment %> \n" }
82
+
83
+ before do
84
+ allow(File).to receive(:read).with(filename).and_return(erb_file)
85
+ end
86
+
87
+ subject { SecurityGroup.new(api, environment) }
88
+
89
+ it "passes the environment into the erb rendering" do
90
+ expect(SecurityGroupConfig).to receive(:[]).at_least(:once).with(hash_including('environment' => 'parsed'))
91
+ subject.send(:load_rules)
92
+ end
93
+
94
+ end
95
+
96
+ context "#security_group_definition_files" do
97
+ let(:environment) { 'parsed' }
98
+ let(:erb_file) { "--- \nenvironment: <%= environment %> \n" }
99
+
100
+ before do
101
+ allow(File).to receive(:read).with(filename).and_return(erb_file)
102
+ end
103
+
104
+ it "returns an array of file names with out the extension" do
105
+ allow(Dir).to receive(:[]).and_return(["config/aws_keys.yml", "config/foo.yml", "config/bar.yml"])
106
+ expect(SecurityGroup.send(:security_group_definition_files)).to eq(["config/foo.yml","config/bar.yml"])
107
+ end
108
+ end
109
+
110
+ context "#config_security_groups" do
111
+
112
+ let(:file_region_1) {'us-east-1'}
113
+ let(:file_region_2) {'us-west-2'}
114
+ let(:erb_file_1) { "--- \nenvironment: <%= environment %> \n region: <%= file_region_1 %>\n" }
115
+ let(:erb_file_2) { "--- \nenvironment: <%= environment %> \n region: <%= file_region_2 %>\n" }
116
+
117
+ before do
118
+ allow(SecurityGroup).to receive(:get_security_group_region).and_return(file_region_1,file_region_2)
119
+ allow(File).to receive(:read).with(filename).and_return([erb_file_1, erb_file_2])
120
+ end
121
+
122
+ context "with no region specified" do
123
+ it "retrusn groups in the default region" do
124
+ allow(Dir).to receive(:[]).and_return(["config/aws_keys.yml", "config/foo.yml", "config/bar.yml"])
125
+ expect(SecurityGroup.send(:config_security_groups)).to eq(["foo"])
126
+ end
127
+ end
128
+
129
+ context "with a region specified" do
130
+ let(:region) { 'us-west-2' }
131
+
132
+ it "returns groups in the specified region" do
133
+ allow(Dir).to receive(:[]).and_return(["config/aws_keys.yml", "config/foo.yml", "config/bar.yml"])
134
+ expect(SecurityGroup.send(:config_security_groups)).to eq(["bar"])
135
+ end
136
+ end
137
+ end
138
+
139
+ context "#missing_security_groups" do
140
+ let(:environment) { 'parsed' }
141
+ let(:erb_file) { "--- \nenvironment: <%= environment %> \n" }
142
+ let(:security_group_1) { double }
143
+ let(:security_group_2) { double }
144
+
145
+ before do
146
+ allow(File).to receive(:read).with(filename).and_return(erb_file)
147
+ allow(SecurityGroup).to receive(:config_security_groups).and_return(["foo","bar"])
148
+ allow(security_group_1).to receive(:name).and_return("foo")
149
+ allow(security_group_2).to receive(:name).and_return("bar")
150
+ end
151
+
152
+ it "returns empty if config_security_groups is the same as security_groups" do
153
+ allow(SecurityGroup).to receive(:from_aws).and_return([security_group_1, security_group_2])
154
+ expect(SecurityGroup.send(:missing_security_groups)).to eq([])
155
+ end
156
+
157
+ it "returns groups in config_security_groups not in security_groups" do
158
+ allow(SecurityGroup).to receive(:from_aws).and_return([security_group_2])
159
+ expect(SecurityGroup.send(:missing_security_groups)).to eq(["foo"])
160
+ end
161
+ end
162
+
163
+ context ".lookup" do
164
+ before do
165
+ allow(security_group).to receive(:name) {security_group_name}
166
+ allow(security_group).to receive(:id) {security_group_id}
167
+ allow(SecurityGroup).to receive(:security_groups).and_return([security_group])
168
+ SecurityGroup.instance_variable_set(:@security_group_hash, nil)
169
+ end
170
+
171
+ let(:security_group_name) { 'sec-group-name' }
172
+ let(:security_group_id) { 'sec-group' }
173
+ let(:security_group) { double }
174
+
175
+ it "returns the group corresponding to the group name" do
176
+ expect(SecurityGroup.lookup(security_group_name)).to eq(security_group)
177
+ end
178
+
179
+ it "returns the group name corresponding to the group id" do
180
+ expect(SecurityGroup.lookup(security_group_id)).to eq(security_group)
181
+ end
182
+ end
183
+
184
+ context ".from_aws" do
185
+ before do
186
+ allow(SecurityGroup).to receive(:security_groups) {nil}
187
+ end
188
+ it "delegates to the ec2 object" do
189
+ expect(ec2).to receive(:security_groups)
190
+ SecurityGroup.from_aws
191
+ end
192
+ end
193
+
194
+ context ".create_missing_security_groups" do
195
+ let(:security_groups) { double }
196
+ let(:environment) { 'parsed' }
197
+ let(:security_group_name) {'sec-group-name'}
198
+ let(:security_group) { double }
199
+ let(:configs) {{vpc: "vpc", description: "description"}}
200
+
201
+ before do
202
+ allow(ec2).to receive(:security_groups) {security_groups}
203
+ allow(SecurityGroup).to receive(:missing_security_groups).and_return(['sec-group-name'])
204
+ allow(SecurityGroup).to receive(:new).with(security_group_name,environment).and_return(security_group)
205
+ allow(security_group).to receive(:config).and_return(configs)
206
+ allow(SecurityGroup).to receive(:say)
207
+ end
208
+
209
+ it "create missing security groups" do
210
+ expect(security_groups).to receive(:create).with('sec-group-name', {vpc: "vpc", description: "description"})
211
+ SecurityGroup.send(:create_missing_security_groups, environment)
212
+ end
213
+ end
214
+
215
+ context ".update_rules" do
216
+ let(:security_group) { double }
217
+ let(:security_groups) { [security_group] }
218
+
219
+ before do
220
+ SecurityGroup.instance_variable_set(:@environment, "environment")
221
+ allow(SecurityGroup).to receive(:security_groups) {security_groups}
222
+ allow(SecurityGroup).to receive(:new).with('sec-group-name','environment').and_return(security_group)
223
+ allow(security_group).to receive(:name) {'sec-group-name'}
224
+ end
225
+
226
+ it "calls #update_rules" do
227
+ expect(security_group).to receive(:update_rules)
228
+ SecurityGroup.send(:update_rules)
229
+ end
230
+ end
231
+
232
+ context ".update_security_groups" do
233
+ it "calls everything it's supposed to" do
234
+ expect(SecurityGroup).to receive(:create_missing_security_groups)
235
+ expect(SecurityGroup).to receive(:update_rules)
236
+ SecurityGroup.send(:update_security_groups, ec2, environment, region)
237
+ end
238
+ end
239
+ end
240
+ end