subiam 1.2.1

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,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