subiam 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ class Subiam::Logger < ::Logger
2
+ include Singleton
3
+
4
+ def initialize
5
+ super($stdout)
6
+
7
+ self.formatter = proc do |severity, datetime, progname, msg|
8
+ "#{msg}\n"
9
+ end
10
+
11
+ self.level = Logger::INFO
12
+ end
13
+
14
+ def set_debug(value)
15
+ self.level = value ? Logger::DEBUG : Logger::INFO
16
+ end
17
+
18
+ module Helper
19
+ def log(level, message, log_options = {})
20
+ message = "[#{level.to_s.upcase}] #{message}" unless level == :info
21
+ message << ' (dry-run)' if @options[:dry_run]
22
+ message = message.send(log_options[:color]) if log_options[:color]
23
+ logger = @options[:logger] || Subiam::Logger.instance
24
+ logger.send(level, message)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ class Subiam::PasswordManager
2
+ include Subiam::Logger::Helper
3
+
4
+ def initialize(output, options = {})
5
+ @output = output
6
+ @options = options
7
+ end
8
+
9
+ def identify(user, type)
10
+ password = mkpasswd
11
+ puts_password(user, type, password)
12
+ password
13
+ end
14
+
15
+ def puts_password(user, type, password)
16
+ log(:info, "User `#{user}` > `#{type}`: put password to `#{@output}`")
17
+
18
+ open_output do |f|
19
+ f.puts("#{user},#{type},#{password}")
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def mkpasswd(len = 8)
26
+ [*1..9, *'A'..'Z', *'a'..'z'].shuffle.slice(0, len).join
27
+ end
28
+
29
+ def open_output
30
+ return if @options[:dry_run]
31
+
32
+ if @output == '-'
33
+ yield($stdout)
34
+ $stdout.flush
35
+ else
36
+ open(@output, 'a') do |f|
37
+ yield(f)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module Subiam
2
+ module TemplateHelper
3
+ def include_template(template_name, context = {})
4
+ tmplt = @context.templates[template_name.to_s]
5
+
6
+ unless tmplt
7
+ raise "Template `#{template_name}` is not defined"
8
+ end
9
+
10
+ context_orig = @context
11
+ @context = @context.merge(context)
12
+ instance_eval(&tmplt)
13
+ @context = context_orig
14
+ end
15
+
16
+ def context
17
+ @context
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ class Subiam::Utils
2
+ class << self
3
+ def unbrace(str)
4
+ str.sub(/\A\s*\{/, '').sub(/\}\s*\z/, '')
5
+ end
6
+
7
+ def camelize(str)
8
+ str.slice(0, 1).upcase + str.slice(1..-1).downcase
9
+ end
10
+
11
+ def bytesize(str)
12
+ if str.respond_to?(:bytesize)
13
+ str.bytesize
14
+ else
15
+ str.length
16
+ end
17
+ end
18
+
19
+ def diff(obj1, obj2, options = {})
20
+ diffy = Diffy::Diff.new(
21
+ obj1.pretty_inspect,
22
+ obj2.pretty_inspect,
23
+ :diff => '-u'
24
+ )
25
+
26
+ out = diffy.to_s(options[:color] ? :color : :text).gsub(/\s+\z/m, '')
27
+ out.gsub!(/^/, options[:indent]) if options[:indent]
28
+ out
29
+ end
30
+ end # of class methods
31
+ end
@@ -0,0 +1,3 @@
1
+ module Subiam
2
+ VERSION = '1.2.1'
3
+ end
data/lib/subiam.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'cgi'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'pp'
5
+ require 'singleton'
6
+ require 'thread'
7
+
8
+ require 'aws-sdk-core'
9
+ Aws.use_bundled_cert!
10
+
11
+ require 'ruby-progressbar'
12
+ require 'parallel'
13
+ require 'term/ansicolor'
14
+ require 'diffy'
15
+ require 'hashie'
16
+
17
+ module Subiam ; end
18
+ require 'subiam/ext/array_ext'
19
+ require 'subiam/ext/hash_ext'
20
+ require 'subiam/ext/string_ext'
21
+ require 'subiam/logger'
22
+ require 'subiam/template_helper'
23
+ require 'subiam/client'
24
+ require 'subiam/driver'
25
+ require 'subiam/dsl'
26
+ require 'subiam/dsl/helper/arn'
27
+ require 'subiam/dsl/context'
28
+ require 'subiam/dsl/context/group'
29
+ require 'subiam/dsl/context/managed_policy'
30
+ require 'subiam/dsl/context/role'
31
+ require 'subiam/dsl/context/user'
32
+ require 'subiam/dsl/converter'
33
+ require 'subiam/exporter'
34
+ require 'subiam/password_manager'
35
+ require 'subiam/utils'
36
+ require 'subiam/version'
@@ -0,0 +1,95 @@
1
+ if ENV['TRAVIS']
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
6
+ SimpleCov.start do
7
+ add_filter "spec/"
8
+ end
9
+ end
10
+
11
+ require 'tempfile'
12
+ require 'subiam'
13
+
14
+ Aws.config.update(
15
+ access_key_id: ENV['MIAM_TEST_ACCESS_KEY_ID'] || 'scott',
16
+ secret_access_key: ENV['MIAM_TEST_SECRET_ACCESS_KEY'] || 'tiger'
17
+ )
18
+
19
+ MIAM_TEST_ACCOUNT_ID = Aws::IAM::Client.new.get_user.user.user_id
20
+
21
+ RSpec.configure do |config|
22
+ config.before(:each) do
23
+ apply { 'target /.*/' }
24
+ end
25
+
26
+ config.after(:all) do
27
+ apply { 'target /.*/' }
28
+ end
29
+ end
30
+
31
+ def client(user_options = {})
32
+ options = {
33
+ logger: Logger.new('/dev/null'),
34
+ no_progress: true,
35
+ enable_delete: true,
36
+ }
37
+
38
+ options[:password_manager] = Subiam::PasswordManager.new('/dev/null', options)
39
+
40
+ if_debug do
41
+ logger = Subiam::Logger.instance
42
+ logger.set_debug(true)
43
+
44
+ options.update(
45
+ debug: true,
46
+ logger: logger,
47
+ aws_config: {
48
+ http_wire_trace: true,
49
+ logger: logger
50
+ },
51
+ )
52
+ end
53
+
54
+ options = options.merge(user_options)
55
+ Subiam::Client.new(options)
56
+ end
57
+
58
+ def tempfile(content, options = {})
59
+ basename = "#{File.basename __FILE__}.#{$$}"
60
+ basename = [basename, options[:ext]] if options[:ext]
61
+
62
+ Tempfile.open(basename) do |f|
63
+ f.puts(content)
64
+ f.flush
65
+ f.rewind
66
+ yield(f)
67
+ end
68
+ end
69
+
70
+ def apply(cli = client)
71
+ tempfile(yield) do |f|
72
+ begin
73
+ cli.apply(f.path)
74
+ rescue Aws::IAM::Errors::EntityTemporarilyUnmodifiable
75
+ sleep 3
76
+ retry
77
+ end
78
+ end
79
+ end
80
+
81
+ def parse(cli = client)
82
+ tempfile(yield) do |f|
83
+ cli.__send__(:load_file, f.path)
84
+ end
85
+ end
86
+
87
+ def export(options = {})
88
+ options = {no_progress: true}.merge(options)
89
+ cli = options.delete(:client) || Aws::IAM::Client.new
90
+ Subiam::Exporter.export(cli, options)[0]
91
+ end
92
+
93
+ def if_debug
94
+ yield if ENV['DEBUG'] == '1'
95
+ end
@@ -0,0 +1,389 @@
1
+ describe 'attach/detach policy' do
2
+ let(:dsl) do
3
+ <<-RUBY
4
+ target /^iam-test-/
5
+
6
+ user "iam-test-bob", :path=>"/devloper/" do
7
+ login_profile :password_reset_required=>true
8
+
9
+ groups(
10
+ "iam-test-Admin",
11
+ "iam-test-SES"
12
+ )
13
+
14
+ policy "S3" do
15
+ {"Statement"=>
16
+ [{"Action"=>
17
+ ["s3:Get*",
18
+ "s3:List*"],
19
+ "Effect"=>"Allow",
20
+ "Resource"=>"*"}]}
21
+ end
22
+
23
+ attached_managed_policies(
24
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
25
+ )
26
+ end
27
+
28
+ user "iam-test-mary", :path=>"/staff/" do
29
+ policy "S3" do
30
+ {"Statement"=>
31
+ [{"Action"=>
32
+ ["s3:Get*",
33
+ "s3:List*"],
34
+ "Effect"=>"Allow",
35
+ "Resource"=>"*"}]}
36
+ end
37
+
38
+ attached_managed_policies(
39
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
40
+ )
41
+ end
42
+
43
+ group "iam-test-Admin", :path=>"/admin/" do
44
+ policy "Admin" do
45
+ {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]}
46
+ end
47
+
48
+ attached_managed_policies(
49
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
50
+ )
51
+ end
52
+
53
+ group "iam-test-SES", :path=>"/ses/" do
54
+ policy "ses-policy" do
55
+ {"Statement"=>
56
+ [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]}
57
+ end
58
+
59
+ attached_managed_policies(
60
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
61
+ )
62
+ end
63
+
64
+ role "iam-test-my-role", :path=>"/any/" do
65
+ instance_profiles(
66
+ "iam-test-my-instance-profile"
67
+ )
68
+
69
+ assume_role_policy_document do
70
+ {"Version"=>"2012-10-17",
71
+ "Statement"=>
72
+ [{"Sid"=>"",
73
+ "Effect"=>"Allow",
74
+ "Principal"=>{"Service"=>"ec2.amazonaws.com"},
75
+ "Action"=>"sts:AssumeRole"}]}
76
+ end
77
+
78
+ policy "role-policy" do
79
+ {"Statement"=>
80
+ [{"Action"=>
81
+ ["s3:Get*",
82
+ "s3:List*"],
83
+ "Effect"=>"Allow",
84
+ "Resource"=>"*"}]}
85
+ end
86
+
87
+ attached_managed_policies(
88
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
89
+ )
90
+ end
91
+
92
+ instance_profile "iam-test-my-instance-profile", :path=>"/profile/"
93
+ RUBY
94
+ end
95
+
96
+ let(:expected) do
97
+ {:users=>
98
+ {"iam-test-bob"=>
99
+ {:path=>"/devloper/",
100
+ :groups=>["iam-test-Admin", "iam-test-SES"],
101
+ :attached_managed_policies=>[
102
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"],
103
+ :policies=>
104
+ {"S3"=>
105
+ {"Statement"=>
106
+ [{"Action"=>["s3:Get*", "s3:List*"],
107
+ "Effect"=>"Allow",
108
+ "Resource"=>"*"}]}},
109
+ :login_profile=>{:password_reset_required=>true}},
110
+ "iam-test-mary"=>
111
+ {:path=>"/staff/",
112
+ :groups=>[],
113
+ :attached_managed_policies=>[
114
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"],
115
+ :policies=>
116
+ {"S3"=>
117
+ {"Statement"=>
118
+ [{"Action"=>["s3:Get*", "s3:List*"],
119
+ "Effect"=>"Allow",
120
+ "Resource"=>"*"}]}}}},
121
+ :groups=>
122
+ {"iam-test-Admin"=>
123
+ {:path=>"/admin/",
124
+ :attached_managed_policies=>[
125
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"],
126
+ :policies=>
127
+ {"Admin"=>
128
+ {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]}}},
129
+ "iam-test-SES"=>
130
+ {:path=>"/ses/",
131
+ :attached_managed_policies=>[
132
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"],
133
+ :policies=>
134
+ {"ses-policy"=>
135
+ {"Statement"=>
136
+ [{"Effect"=>"Allow",
137
+ "Action"=>"ses:SendRawEmail",
138
+ "Resource"=>"*"}]}}}},
139
+ :policies=>{},
140
+ :roles=>
141
+ {"iam-test-my-role"=>
142
+ {:path=>"/any/",
143
+ :assume_role_policy_document=>
144
+ {"Version"=>"2012-10-17",
145
+ "Statement"=>
146
+ [{"Sid"=>"",
147
+ "Effect"=>"Allow",
148
+ "Principal"=>{"Service"=>"ec2.amazonaws.com"},
149
+ "Action"=>"sts:AssumeRole"}]},
150
+ :instance_profiles=>["iam-test-my-instance-profile"],
151
+ :attached_managed_policies=>[
152
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"],
153
+ :policies=>
154
+ {"role-policy"=>
155
+ {"Statement"=>
156
+ [{"Action"=>["s3:Get*", "s3:List*"],
157
+ "Effect"=>"Allow",
158
+ "Resource"=>"*"}]}}}},
159
+ :instance_profiles=>{"iam-test-my-instance-profile"=>{:path=>"/profile/"}}}
160
+ end
161
+
162
+ before(:each) do
163
+ apply { dsl }
164
+ end
165
+
166
+ context 'when no change' do
167
+ subject { client }
168
+
169
+ it do
170
+ updated = apply(subject) { dsl }
171
+ expect(updated).to be_falsey
172
+ expect(export).to eq expected
173
+ end
174
+ end
175
+
176
+ context 'when attach policy' do
177
+ let(:update_policy_dsl) do
178
+ <<-RUBY
179
+ target /^iam-test-/
180
+
181
+ user "iam-test-bob", :path=>"/devloper/" do
182
+ login_profile :password_reset_required=>true
183
+
184
+ groups(
185
+ "iam-test-Admin",
186
+ "iam-test-SES"
187
+ )
188
+
189
+ policy "S3" do
190
+ {"Statement"=>
191
+ [{"Action"=>
192
+ ["s3:Get*",
193
+ "s3:List*"],
194
+ "Effect"=>"Allow",
195
+ "Resource"=>"*"}]}
196
+ end
197
+
198
+ attached_managed_policies(
199
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
200
+ )
201
+ end
202
+
203
+ user "iam-test-mary", :path=>"/staff/" do
204
+ policy "S3" do
205
+ {"Statement"=>
206
+ [{"Action"=>
207
+ ["s3:Get*",
208
+ "s3:List*"],
209
+ "Effect"=>"Allow",
210
+ "Resource"=>"*"}]}
211
+ end
212
+
213
+ attached_managed_policies(
214
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess",
215
+ "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
216
+ )
217
+ end
218
+
219
+ group "iam-test-Admin", :path=>"/admin/" do
220
+ policy "Admin" do
221
+ {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]}
222
+ end
223
+
224
+ attached_managed_policies(
225
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
226
+ )
227
+ end
228
+
229
+ group "iam-test-SES", :path=>"/ses/" do
230
+ policy "ses-policy" do
231
+ {"Statement"=>
232
+ [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]}
233
+ end
234
+
235
+ attached_managed_policies(
236
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess",
237
+ "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
238
+ )
239
+ end
240
+
241
+ role "iam-test-my-role", :path=>"/any/" do
242
+ instance_profiles(
243
+ "iam-test-my-instance-profile"
244
+ )
245
+
246
+ assume_role_policy_document do
247
+ {"Version"=>"2012-10-17",
248
+ "Statement"=>
249
+ [{"Sid"=>"",
250
+ "Effect"=>"Allow",
251
+ "Principal"=>{"Service"=>"ec2.amazonaws.com"},
252
+ "Action"=>"sts:AssumeRole"}]}
253
+ end
254
+
255
+ policy "role-policy" do
256
+ {"Statement"=>
257
+ [{"Action"=>
258
+ ["s3:Get*",
259
+ "s3:List*"],
260
+ "Effect"=>"Allow",
261
+ "Resource"=>"*"}]}
262
+ end
263
+
264
+ attached_managed_policies(
265
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess",
266
+ "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
267
+ )
268
+ end
269
+
270
+ instance_profile "iam-test-my-instance-profile", :path=>"/profile/"
271
+ RUBY
272
+ end
273
+
274
+ subject { client }
275
+
276
+ it do
277
+ updated = apply(subject) { update_policy_dsl }
278
+ expect(updated).to be_truthy
279
+ expected[:users]["iam-test-mary"][:attached_managed_policies] << "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
280
+ expected[:groups]["iam-test-SES"][:attached_managed_policies] << "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
281
+ expected[:roles]["iam-test-my-role"][:attached_managed_policies] << "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess"
282
+ expect(export).to eq expected
283
+ end
284
+ end
285
+
286
+ context 'when detach policy' do
287
+ let(:update_policy_dsl) do
288
+ <<-RUBY
289
+ target /^iam-test-/
290
+
291
+ user "iam-test-bob", :path=>"/devloper/" do
292
+ login_profile :password_reset_required=>true
293
+
294
+ groups(
295
+ "iam-test-Admin",
296
+ "iam-test-SES"
297
+ )
298
+
299
+ policy "S3" do
300
+ {"Statement"=>
301
+ [{"Action"=>
302
+ ["s3:Get*",
303
+ "s3:List*"],
304
+ "Effect"=>"Allow",
305
+ "Resource"=>"*"}]}
306
+ end
307
+
308
+ attached_managed_policies(
309
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
310
+ )
311
+ end
312
+
313
+ user "iam-test-mary", :path=>"/staff/" do
314
+ policy "S3" do
315
+ {"Statement"=>
316
+ [{"Action"=>
317
+ ["s3:Get*",
318
+ "s3:List*"],
319
+ "Effect"=>"Allow",
320
+ "Resource"=>"*"}]}
321
+ end
322
+
323
+ attached_managed_policies(
324
+ )
325
+ end
326
+
327
+ group "iam-test-Admin", :path=>"/admin/" do
328
+ policy "Admin" do
329
+ {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]}
330
+ end
331
+
332
+ attached_managed_policies(
333
+ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"
334
+ )
335
+ end
336
+
337
+ group "iam-test-SES", :path=>"/ses/" do
338
+ policy "ses-policy" do
339
+ {"Statement"=>
340
+ [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]}
341
+ end
342
+
343
+ attached_managed_policies(
344
+ )
345
+ end
346
+
347
+ role "iam-test-my-role", :path=>"/any/" do
348
+ instance_profiles(
349
+ "iam-test-my-instance-profile"
350
+ )
351
+
352
+ assume_role_policy_document do
353
+ {"Version"=>"2012-10-17",
354
+ "Statement"=>
355
+ [{"Sid"=>"",
356
+ "Effect"=>"Allow",
357
+ "Principal"=>{"Service"=>"ec2.amazonaws.com"},
358
+ "Action"=>"sts:AssumeRole"}]}
359
+ end
360
+
361
+ policy "role-policy" do
362
+ {"Statement"=>
363
+ [{"Action"=>
364
+ ["s3:Get*",
365
+ "s3:List*"],
366
+ "Effect"=>"Allow",
367
+ "Resource"=>"*"}]}
368
+ end
369
+
370
+ attached_managed_policies(
371
+ )
372
+ end
373
+
374
+ instance_profile "iam-test-my-instance-profile", :path=>"/profile/"
375
+ RUBY
376
+ end
377
+
378
+ subject { client }
379
+
380
+ it do
381
+ updated = apply(subject) { update_policy_dsl }
382
+ expect(updated).to be_truthy
383
+ expected[:users]["iam-test-mary"][:attached_managed_policies].clear
384
+ expected[:groups]["iam-test-SES"][:attached_managed_policies].clear
385
+ expected[:roles]["iam-test-my-role"][:attached_managed_policies].clear
386
+ expect(export).to eq expected
387
+ end
388
+ end
389
+ end