ec2-security-czar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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