miam 0.2.3 → 0.2.5.beta1
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.
- checksums.yaml +5 -5
- data/.rspec +2 -1
- data/.travis.yml +6 -4
- data/README.md +8 -1
- data/bin/miam +10 -5
- data/lib/miam/client.rb +54 -13
- data/lib/miam/driver.rb +44 -14
- data/lib/miam/dsl/context/role.rb +5 -1
- data/lib/miam/dsl/context.rb +5 -1
- data/lib/miam/dsl/converter.rb +17 -3
- data/lib/miam/exporter.rb +8 -4
- data/lib/miam/password_manager.rb +23 -4
- data/lib/miam/template_helper.rb +11 -0
- data/lib/miam/version.rb +1 -1
- data/lib/miam.rb +1 -1
- data/miam.gemspec +3 -4
- data/spec/miam/attach_detach_policy_spec.rb +4 -4
- data/spec/miam/create_spec.rb +3 -3
- data/spec/miam/delete_spec.rb +6 -6
- data/spec/miam/exclude_spec.rb +252 -0
- data/spec/miam/hash_ext_spec.rb +2 -2
- data/spec/miam/ignore_login_profile_spec.rb +3 -3
- data/spec/miam/rename_spec.rb +6 -6
- data/spec/miam/target_spec.rb +185 -0
- data/spec/miam/update_spec.rb +97 -11
- data/spec/spec_helper.rb +5 -2
- metadata +29 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b0a35750c70a9a9f30ff1a1482a3da3a66c994e691fa3802b2c54992408415df
|
|
4
|
+
data.tar.gz: c33e5e326f59da9810b8735620cea049cc619b013b1e3525d86416e44dd91af1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce11c734155e09c454e913ec7dc2d7ea8eb68cf60b0eb7803f0e01ae5b2469c865172e4a0a87f2e0ebb71d72fe9653b0b009d21a23913a4cd763b0ebbd1e6b72
|
|
7
|
+
data.tar.gz: e6001cb1c8c03903846f4d936e130f198fec72a695e086ab5ed36a8795be9891bd6f5143f4876b123d7cc5c27e50a846dc20f8a2ce521c66c61ed60546cea6d3
|
data/.rspec
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
language: ruby
|
|
3
|
+
cache: bundler
|
|
3
4
|
rvm:
|
|
4
|
-
- 2.
|
|
5
|
+
- 2.3.4
|
|
5
6
|
script:
|
|
6
7
|
- bundle install
|
|
7
|
-
- bundle exec rake
|
|
8
|
+
- travis_wait 60 bundle exec rake
|
|
8
9
|
env:
|
|
9
10
|
global:
|
|
10
|
-
- secure:
|
|
11
|
-
- secure:
|
|
11
|
+
- secure: c5kyaYSGrKMg3bsVMCnIYe2w/3jJ4rxkoIwM06YGLmw5lPW0twWgpwpuBsGf6WKvDLv0eQjGq3B0I1DqHmdYHq4dQ+PdsvWg0kQUpzWpKD2ccVXevyPeDs5pC1UcLtDUpRk0Rv1Hpo4v5kiT2zsmLx29Z6F4n7agtzqK5Q2vdPs=
|
|
12
|
+
- secure: ipdvAzZ5EwTQui8CyINqRYLFNNe4ZyZP617xEmUS3YalhRMOEZ+NSq7BlqliE4IJPg8YlQjT5q35xFtREIr/KYS1tmU1/nFA68GC/9T+uJAsc3LSmUoYMn8Ot3654u1SjlZ86benyToZ2MgHglmHigS46SOoxBpqHrKMUW9RUz0=
|
|
12
13
|
- AWS_REGION=ap-northeast-1
|
|
14
|
+
- APPLY_WAIT=10
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Miam is a tool to manage IAM.
|
|
|
5
5
|
It defines the state of IAM using DSL, and updates IAM according to DSL.
|
|
6
6
|
|
|
7
7
|
[](http://badge.fury.io/rb/miam)
|
|
8
|
-
[](https://travis-ci.org/codenize-tools/miam)
|
|
9
9
|
[](https://coveralls.io/github/winebarrel/miam?branch=master)
|
|
10
10
|
|
|
11
11
|
**Notice**
|
|
@@ -22,6 +22,12 @@ It defines the state of IAM using DSL, and updates IAM according to DSL.
|
|
|
22
22
|
* Sort policy array
|
|
23
23
|
* `>= 0.2.3`
|
|
24
24
|
* Support Custom Managed Policy
|
|
25
|
+
* `>= 0.2.4`
|
|
26
|
+
* Fix for Password Policy ([RP#22](https://github.com/winebarrel/miam/pull/22))
|
|
27
|
+
* Fix `--target` option for Policies ([RP#21](https://github.com/winebarrel/miam/pull/21))
|
|
28
|
+
* Fix for `Rate exceeded` ([PR#23](https://github.com/winebarrel/miam/pull/23))
|
|
29
|
+
* Fix for non-User credentials ([PR#17](https://github.com/winebarrel/miam/pull/17))
|
|
30
|
+
* Add `--exclude` option
|
|
25
31
|
|
|
26
32
|
## Installation
|
|
27
33
|
|
|
@@ -71,6 +77,7 @@ Usage: miam [options]
|
|
|
71
77
|
--format=FORMAT
|
|
72
78
|
--export-concurrency N
|
|
73
79
|
--target REGEXP
|
|
80
|
+
--exclude REGEXP
|
|
74
81
|
--ignore-login-profile
|
|
75
82
|
--no-color
|
|
76
83
|
--no-progress
|
data/bin/miam
CHANGED
|
@@ -19,10 +19,11 @@ MAGIC_COMMENT = <<-EOS
|
|
|
19
19
|
EOS
|
|
20
20
|
|
|
21
21
|
options = {
|
|
22
|
-
:dry_run
|
|
23
|
-
:format
|
|
24
|
-
:color
|
|
25
|
-
:debug
|
|
22
|
+
:dry_run => false,
|
|
23
|
+
:format => :ruby,
|
|
24
|
+
:color => true,
|
|
25
|
+
:debug => false,
|
|
26
|
+
:show_backtrace => false
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
options[:password_manager] = Miam::PasswordManager.new(account_output, options)
|
|
@@ -51,11 +52,14 @@ ARGV.options do |opt|
|
|
|
51
52
|
opt.on('' , '--split-more') { split = :more }
|
|
52
53
|
opt.on('', '--format=FORMAT', [:ruby, :json]) {|v| format_passed = true; options[:format] = v }
|
|
53
54
|
opt.on('' , '--export-concurrency N', Integer) {|v| options[:export_concurrency] = v }
|
|
54
|
-
opt.on('' , '--target REGEXP') {|v| options[:target]
|
|
55
|
+
opt.on('' , '--target REGEXP') {|v| (options[:target] ||= []) << Regexp.new(v) }
|
|
56
|
+
opt.on('' , '--exclude REGEXP') {|v| (options[:exclude] ||= []) << Regexp.new(v) }
|
|
55
57
|
opt.on('' , '--ignore-login-profile') { options[:ignore_login_profile] = true }
|
|
58
|
+
opt.on('' , '--no-access-key') { options[:no_access_key] = true }
|
|
56
59
|
opt.on('' , '--no-color') { options[:color] = false }
|
|
57
60
|
opt.on('' , '--no-progress') { options[:no_progress] = true }
|
|
58
61
|
opt.on('' , '--debug') { options[:debug] = true }
|
|
62
|
+
opt.on('' , '--show-backtrace') { options[:show_backtrace] = true }
|
|
59
63
|
opt.parse!
|
|
60
64
|
|
|
61
65
|
aws_opts = {}
|
|
@@ -184,6 +188,7 @@ rescue => e
|
|
|
184
188
|
raise e
|
|
185
189
|
else
|
|
186
190
|
$stderr.puts("[ERROR] #{e.message}".red)
|
|
191
|
+
$stderr.puts(e.backtrace.map(&:yellow)) if options[:show_backtrace]
|
|
187
192
|
exit 1
|
|
188
193
|
end
|
|
189
194
|
end
|
data/lib/miam/client.rb
CHANGED
|
@@ -2,10 +2,14 @@ class Miam::Client
|
|
|
2
2
|
include Miam::Logger::Helper
|
|
3
3
|
|
|
4
4
|
def initialize(options = {})
|
|
5
|
-
@options = {
|
|
5
|
+
@options = {
|
|
6
|
+
format: :ruby,
|
|
7
|
+
exclude: []
|
|
8
|
+
}.merge(options)
|
|
6
9
|
aws_config = options.delete(:aws_config) || {}
|
|
7
10
|
@iam = Aws::IAM::Client.new(aws_config)
|
|
8
|
-
@
|
|
11
|
+
@sts = Aws::STS::Client.new(aws_config)
|
|
12
|
+
@driver = Miam::Driver.new(@iam, @sts, options)
|
|
9
13
|
@password_manager = options[:password_manager] || Miam::PasswordManager.new('-', options)
|
|
10
14
|
end
|
|
11
15
|
|
|
@@ -57,6 +61,7 @@ class Miam::Client
|
|
|
57
61
|
|
|
58
62
|
def walk(file)
|
|
59
63
|
expected = load_file(file)
|
|
64
|
+
@options[:exclude] += expected[:exclude]
|
|
60
65
|
|
|
61
66
|
actual, group_users, instance_profile_roles = Miam::Exporter.export(@iam, @options)
|
|
62
67
|
updated = pre_walk_managed_policies(expected[:policies], actual[:policies])
|
|
@@ -86,7 +91,7 @@ class Miam::Client
|
|
|
86
91
|
updated = walk_user(user_name, expected_attrs, actual_attrs) || updated
|
|
87
92
|
else
|
|
88
93
|
actual_attrs = @driver.create_user(user_name, expected_attrs)
|
|
89
|
-
access_key = @driver.create_access_key(user_name)
|
|
94
|
+
access_key = @driver.create_access_key(user_name) unless @options[:no_access_key]
|
|
90
95
|
|
|
91
96
|
if access_key
|
|
92
97
|
@password_manager.puts_password(user_name, access_key[:access_key_id], access_key[:secret_access_key])
|
|
@@ -129,7 +134,7 @@ class Miam::Client
|
|
|
129
134
|
end
|
|
130
135
|
|
|
131
136
|
if expected_login_profile and not actual_login_profile
|
|
132
|
-
expected_login_profile[:password] ||= @password_manager.identify(user_name, :login_profile)
|
|
137
|
+
expected_login_profile[:password] ||= @password_manager.identify(user_name, :login_profile, @driver.password_policy)
|
|
133
138
|
@driver.create_login_profile(user_name, expected_login_profile)
|
|
134
139
|
updated = true
|
|
135
140
|
elsif not expected_login_profile and actual_login_profile
|
|
@@ -137,7 +142,7 @@ class Miam::Client
|
|
|
137
142
|
updated = true
|
|
138
143
|
elsif expected_login_profile != actual_login_profile
|
|
139
144
|
if @options[:ignore_login_profile]
|
|
140
|
-
log(:warn, "User `#{user_name}`: difference of
|
|
145
|
+
log(:warn, "User `#{user_name}`: difference of login profile has been ignored: expected=#{expected_login_profile.inspect}, actual=#{actual_login_profile.inspect}", :color => :yellow)
|
|
141
146
|
else
|
|
142
147
|
@driver.update_login_profile(user_name, expected_login_profile, actual_login_profile)
|
|
143
148
|
updated = true
|
|
@@ -254,17 +259,45 @@ class Miam::Client
|
|
|
254
259
|
log(:warn, "Role `#{role_name}`: 'path' cannot be updated", :color => :yellow)
|
|
255
260
|
end
|
|
256
261
|
|
|
257
|
-
updated =
|
|
262
|
+
updated = walk_role_settings(role_name, {max_session_duration: expected_attrs[:max_session_duration]}, {max_session_duration: actual_attrs[:max_session_duration]})
|
|
263
|
+
updated = walk_assume_role_policy(role_name, expected_attrs[:assume_role_policy_document], actual_attrs[:assume_role_policy_document]) || updated
|
|
258
264
|
updated = walk_role_instance_profiles(role_name, expected_attrs[:instance_profiles], actual_attrs[:instance_profiles]) || updated
|
|
259
265
|
updated = walk_attached_managed_policies(:role, role_name, expected_attrs[:attached_managed_policies], actual_attrs[:attached_managed_policies]) || updated
|
|
260
266
|
walk_policies(:role, role_name, expected_attrs[:policies], actual_attrs[:policies]) || updated
|
|
261
267
|
end
|
|
262
268
|
|
|
269
|
+
def walk_role_settings(role_name, expected_settings, actual_settings)
|
|
270
|
+
updated = false
|
|
271
|
+
|
|
272
|
+
if expected_settings != actual_settings
|
|
273
|
+
@driver.update_role_settings(role_name, expected_settings, actual_settings)
|
|
274
|
+
updated = true
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
updated
|
|
278
|
+
end
|
|
279
|
+
|
|
263
280
|
def walk_assume_role_policy(role_name, expected_assume_role_policy, actual_assume_role_policy)
|
|
264
281
|
updated = false
|
|
265
282
|
expected_assume_role_policy.sort_array!
|
|
266
283
|
actual_assume_role_policy.sort_array!
|
|
267
284
|
|
|
285
|
+
# With only one entity granted
|
|
286
|
+
# On IAM
|
|
287
|
+
# (1) Statement => [ { Principal => AWS => arn } ]
|
|
288
|
+
# Should be able to specify like:
|
|
289
|
+
# (2) Statement => [ { Principal => AWS => [arn] } ]
|
|
290
|
+
# Actually (1) is reflected when config (2) is applied
|
|
291
|
+
expected_arp_stmt = expected_assume_role_policy.fetch('Statement', [])
|
|
292
|
+
expected_arp_stmt = expected_arp_stmt.select {|i| i.key?('Principal') }
|
|
293
|
+
|
|
294
|
+
expected_arp_stmt.each do |stmt|
|
|
295
|
+
stmt['Principal'].each do |k, v|
|
|
296
|
+
entities = Array(v)
|
|
297
|
+
stmt['Principal'][k] = entities.first if entities.length < 2
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
268
301
|
if expected_assume_role_policy != actual_assume_role_policy
|
|
269
302
|
@driver.update_assume_role_policy(role_name, expected_assume_role_policy, actual_assume_role_policy)
|
|
270
303
|
updated = true
|
|
@@ -445,6 +478,7 @@ class Miam::Client
|
|
|
445
478
|
updated = false
|
|
446
479
|
|
|
447
480
|
expected.each do |policy_name, expected_attrs|
|
|
481
|
+
next unless target_matched?(policy_name)
|
|
448
482
|
actual_attrs = actual.delete(policy_name)
|
|
449
483
|
|
|
450
484
|
if actual_attrs
|
|
@@ -452,7 +486,7 @@ class Miam::Client
|
|
|
452
486
|
log(:warn, "ManagedPolicy `#{policy_name}`: 'path' cannot be updated", :color => :yellow)
|
|
453
487
|
end
|
|
454
488
|
|
|
455
|
-
updated = walk_managed_policy(policy_name, expected_attrs[:document], actual_attrs[:document]) || updated
|
|
489
|
+
updated = walk_managed_policy(policy_name, actual_attrs[:path], expected_attrs[:document], actual_attrs[:document]) || updated
|
|
456
490
|
else
|
|
457
491
|
@driver.create_managed_policy(policy_name, expected_attrs)
|
|
458
492
|
updated = true
|
|
@@ -462,13 +496,13 @@ class Miam::Client
|
|
|
462
496
|
updated
|
|
463
497
|
end
|
|
464
498
|
|
|
465
|
-
def walk_managed_policy(policy_name, expected_document, actual_document)
|
|
499
|
+
def walk_managed_policy(policy_name, policy_path, expected_document, actual_document)
|
|
466
500
|
updated = false
|
|
467
501
|
expected_document.sort_array!
|
|
468
502
|
actual_document.sort_array!
|
|
469
503
|
|
|
470
504
|
if expected_document != actual_document
|
|
471
|
-
@driver.update_managed_policy(policy_name, expected_document, actual_document)
|
|
505
|
+
@driver.update_managed_policy(policy_name, policy_path, expected_document, actual_document)
|
|
472
506
|
updated = true
|
|
473
507
|
end
|
|
474
508
|
|
|
@@ -479,7 +513,8 @@ class Miam::Client
|
|
|
479
513
|
updated = false
|
|
480
514
|
|
|
481
515
|
actual.each do |policy_name, actual_attrs|
|
|
482
|
-
|
|
516
|
+
next unless target_matched?(policy_name)
|
|
517
|
+
@driver.delete_managed_policy(policy_name, actual_attrs[:path])
|
|
483
518
|
updated = true
|
|
484
519
|
end
|
|
485
520
|
|
|
@@ -505,11 +540,17 @@ class Miam::Client
|
|
|
505
540
|
end
|
|
506
541
|
|
|
507
542
|
def target_matched?(name)
|
|
543
|
+
result = true
|
|
544
|
+
|
|
545
|
+
if @options[:exclude]
|
|
546
|
+
result &&= @options[:exclude].all? {|r| name !~ r }
|
|
547
|
+
end
|
|
548
|
+
|
|
508
549
|
if @options[:target]
|
|
509
|
-
|
|
510
|
-
else
|
|
511
|
-
true
|
|
550
|
+
result &&= @options[:target].any? {|r| name =~ r}
|
|
512
551
|
end
|
|
552
|
+
|
|
553
|
+
result
|
|
513
554
|
end
|
|
514
555
|
|
|
515
556
|
def exec_by_format(proc_by_format)
|
data/lib/miam/driver.rb
CHANGED
|
@@ -4,9 +4,11 @@ class Miam::Driver
|
|
|
4
4
|
MAX_POLICY_SIZE = 2048
|
|
5
5
|
MAX_POLICY_VERSIONS = 5
|
|
6
6
|
|
|
7
|
-
def initialize(iam, options = {})
|
|
7
|
+
def initialize(iam, sts, options = {})
|
|
8
8
|
@iam = iam
|
|
9
|
+
@sts = sts
|
|
9
10
|
@options = options
|
|
11
|
+
@account_id = nil
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def create_user(user_name, attrs)
|
|
@@ -67,6 +69,14 @@ class Miam::Driver
|
|
|
67
69
|
@iam.delete_signing_certificate(:user_name => user_name, :certificate_id => certificate_id)
|
|
68
70
|
end
|
|
69
71
|
|
|
72
|
+
mfa_devices = @iam.list_mfa_devices(:user_name => user_name).map {|resp|
|
|
73
|
+
resp.mfa_devices
|
|
74
|
+
}.flatten
|
|
75
|
+
|
|
76
|
+
mfa_devices.each do |md|
|
|
77
|
+
@iam.deactivate_mfa_device(:user_name => user_name, :serial_number => md.serial_number)
|
|
78
|
+
end
|
|
79
|
+
|
|
70
80
|
@iam.delete_user(:user_name => user_name)
|
|
71
81
|
end
|
|
72
82
|
end
|
|
@@ -168,6 +178,7 @@ class Miam::Driver
|
|
|
168
178
|
params = {
|
|
169
179
|
:role_name => role_name,
|
|
170
180
|
:assume_role_policy_document => encode_document(assume_role_policy_document),
|
|
181
|
+
:max_session_duration => attrs.fetch(:max_session_duration)
|
|
171
182
|
}
|
|
172
183
|
|
|
173
184
|
params[:path] = attrs[:path] if attrs[:path]
|
|
@@ -179,6 +190,7 @@ class Miam::Driver
|
|
|
179
190
|
:assume_role_policy_document => assume_role_policy_document,
|
|
180
191
|
:policies => {},
|
|
181
192
|
:attached_managed_policies => [],
|
|
193
|
+
:max_session_duration => attrs.fetch(:max_session_duration),
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
new_role_attrs[:path] = attrs[:path] if attrs[:path]
|
|
@@ -227,6 +239,14 @@ class Miam::Driver
|
|
|
227
239
|
end
|
|
228
240
|
end
|
|
229
241
|
|
|
242
|
+
def update_role_settings(role_name, new_settings, old_settings)
|
|
243
|
+
log(:info, "Update Role `#{role_name}` > Settings", :color => :green)
|
|
244
|
+
log(:info, Miam::Utils.diff(old_settings, new_settings, :color => @options[:color]), :color => false)
|
|
245
|
+
unless_dry_run do
|
|
246
|
+
@iam.update_role(new_settings.merge(role_name: role_name))
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
230
250
|
def update_assume_role_policy(role_name, policy_document, old_policy_document)
|
|
231
251
|
log(:info, "Update Role `#{role_name}` > AssumeRolePolicy", :color => :green)
|
|
232
252
|
log(:info, Miam::Utils.diff(old_policy_document, policy_document, :color => @options[:color]), :color => false)
|
|
@@ -338,7 +358,7 @@ class Miam::Driver
|
|
|
338
358
|
type_s = type.slice(0, 1).upcase + type.slice(1..-1)
|
|
339
359
|
|
|
340
360
|
log(:info, "Update #{type_s} `#{name}`", :color => :green)
|
|
341
|
-
log(:info, " detach policies=#{policies.join(',')}", :color => :
|
|
361
|
+
log(:info, " detach policies=#{policies.join(',')}", :color => :red)
|
|
342
362
|
|
|
343
363
|
unless_dry_run do
|
|
344
364
|
policies.each do |arn|
|
|
@@ -377,12 +397,12 @@ class Miam::Driver
|
|
|
377
397
|
end
|
|
378
398
|
end
|
|
379
399
|
|
|
380
|
-
def delete_managed_policy(policy_name)
|
|
400
|
+
def delete_managed_policy(policy_name, policy_path)
|
|
381
401
|
log(:info, "Delete ManagedPolicy `#{policy_name}`", :color => :red)
|
|
382
402
|
|
|
383
403
|
unless_dry_run do
|
|
384
404
|
policy_versions = @iam.list_policy_versions(
|
|
385
|
-
:policy_arn => policy_arn(policy_name),
|
|
405
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
386
406
|
:max_items => MAX_POLICY_VERSIONS
|
|
387
407
|
)
|
|
388
408
|
|
|
@@ -390,24 +410,24 @@ class Miam::Driver
|
|
|
390
410
|
pv.is_default_version
|
|
391
411
|
}.each {|pv|
|
|
392
412
|
@iam.delete_policy_version(
|
|
393
|
-
:policy_arn => policy_arn(policy_name),
|
|
413
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
394
414
|
:version_id => pv.version_id
|
|
395
415
|
)
|
|
396
416
|
}
|
|
397
417
|
|
|
398
418
|
@iam.delete_policy(
|
|
399
|
-
:policy_arn => policy_arn(policy_name)
|
|
419
|
+
:policy_arn => policy_arn(policy_name, policy_path)
|
|
400
420
|
)
|
|
401
421
|
end
|
|
402
422
|
end
|
|
403
423
|
|
|
404
|
-
def update_managed_policy(policy_name, policy_document, old_policy_document)
|
|
424
|
+
def update_managed_policy(policy_name, policy_path, policy_document, old_policy_document)
|
|
405
425
|
log(:info, "Update ManagedPolicy `#{policy_name}`", :color => :green)
|
|
406
426
|
log(:info, Miam::Utils.diff(old_policy_document, policy_document, :color => @options[:color]), :color => false)
|
|
407
427
|
|
|
408
428
|
unless_dry_run do
|
|
409
429
|
policy_versions = @iam.list_policy_versions(
|
|
410
|
-
:policy_arn => policy_arn(policy_name),
|
|
430
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
411
431
|
:max_items => MAX_POLICY_VERSIONS
|
|
412
432
|
)
|
|
413
433
|
|
|
@@ -417,19 +437,27 @@ class Miam::Driver
|
|
|
417
437
|
}.sort_by {|pv| pv.version_id[1..-1].to_i }.first
|
|
418
438
|
|
|
419
439
|
@iam.delete_policy_version(
|
|
420
|
-
:policy_arn => policy_arn(policy_name),
|
|
440
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
421
441
|
:version_id => delete_policy_version.version_id
|
|
422
442
|
)
|
|
423
443
|
end
|
|
424
444
|
|
|
425
445
|
@iam.create_policy_version(
|
|
426
|
-
:policy_arn => policy_arn(policy_name),
|
|
446
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
427
447
|
:policy_document => encode_document(policy_document),
|
|
428
448
|
set_as_default: true
|
|
429
449
|
)
|
|
430
450
|
end
|
|
431
451
|
end
|
|
432
452
|
|
|
453
|
+
def password_policy
|
|
454
|
+
return @password_policy if instance_variable_defined?(:@password_policy)
|
|
455
|
+
|
|
456
|
+
@password_policy = @iam.get_account_password_policy.password_policy
|
|
457
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
458
|
+
@password_policy = nil
|
|
459
|
+
end
|
|
460
|
+
|
|
433
461
|
private
|
|
434
462
|
|
|
435
463
|
def encode_document(policy_document)
|
|
@@ -455,11 +483,13 @@ class Miam::Driver
|
|
|
455
483
|
yield unless @options[:dry_run]
|
|
456
484
|
end
|
|
457
485
|
|
|
458
|
-
def
|
|
459
|
-
|
|
486
|
+
def account_id
|
|
487
|
+
# https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
|
|
488
|
+
# http://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
|
|
489
|
+
@account_id ||= @sts.get_caller_identity.account
|
|
460
490
|
end
|
|
461
491
|
|
|
462
|
-
def policy_arn(policy_name)
|
|
463
|
-
"arn:aws:iam::#{
|
|
492
|
+
def policy_arn(policy_name, policy_path)
|
|
493
|
+
File.join("arn:aws:iam::#{account_id}:policy", policy_path, policy_name)
|
|
464
494
|
end
|
|
465
495
|
end
|
|
@@ -4,7 +4,7 @@ class Miam::DSL::Context::Role
|
|
|
4
4
|
def initialize(context, name, &block)
|
|
5
5
|
@role_name = name
|
|
6
6
|
@context = context.merge(:role_name => name)
|
|
7
|
-
@result = {:instance_profiles => [], :policies => {}, :attached_managed_policies => []}
|
|
7
|
+
@result = {:instance_profiles => [], :max_session_duration => 3600, :policies => {}, :attached_managed_policies => []}
|
|
8
8
|
instance_eval(&block)
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -22,6 +22,10 @@ class Miam::DSL::Context::Role
|
|
|
22
22
|
@result[:instance_profiles].concat(profiles.map(&:to_s))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def max_session_duration(duration)
|
|
26
|
+
@result[:max_session_duration] = duration
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
def assume_role_policy_document
|
|
26
30
|
if @result[:assume_role_policy_document]
|
|
27
31
|
raise "Role `#{@role_name}` > AssumeRolePolicyDocument: already defined"
|
data/lib/miam/dsl/context.rb
CHANGED
|
@@ -12,7 +12,7 @@ class Miam::DSL::Context
|
|
|
12
12
|
def initialize(path, options = {}, &block)
|
|
13
13
|
@path = path
|
|
14
14
|
@options = options
|
|
15
|
-
@result = {:users => {}, :groups => {}, :roles => {}, :instance_profiles => {}, :policies => {}}
|
|
15
|
+
@result = {:users => {}, :groups => {}, :roles => {}, :instance_profiles => {}, :policies => {}, :exclude => []}
|
|
16
16
|
|
|
17
17
|
@context = Hashie::Mash.new(
|
|
18
18
|
:path => path,
|
|
@@ -41,6 +41,10 @@ class Miam::DSL::Context
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
def exclude(pattern)
|
|
45
|
+
@result[:exclude] << pattern
|
|
46
|
+
end
|
|
47
|
+
|
|
44
48
|
def user(name, user_options = {}, &block)
|
|
45
49
|
name = name.to_s
|
|
46
50
|
|
data/lib/miam/dsl/converter.rb
CHANGED
|
@@ -95,6 +95,8 @@ end
|
|
|
95
95
|
role #{role_name.inspect}, #{Miam::Utils.unbrace(role_options.inspect)} do
|
|
96
96
|
#{output_role_instance_profiles(attrs[:instance_profiles])}
|
|
97
97
|
|
|
98
|
+
#{output_role_max_session_duration(attrs[:max_session_duration])}
|
|
99
|
+
|
|
98
100
|
#{output_assume_role_policy_document(attrs[:assume_role_policy_document])}
|
|
99
101
|
|
|
100
102
|
#{output_policies(attrs[:policies])}
|
|
@@ -122,6 +124,12 @@ end
|
|
|
122
124
|
}.select {|i| i }.join("\n")
|
|
123
125
|
end
|
|
124
126
|
|
|
127
|
+
def output_role_max_session_duration(max_session_duration)
|
|
128
|
+
<<-EOS.strip
|
|
129
|
+
max_session_duration #{max_session_duration}
|
|
130
|
+
EOS
|
|
131
|
+
end
|
|
132
|
+
|
|
125
133
|
def output_assume_role_policy_document(assume_role_policy_document)
|
|
126
134
|
assume_role_policy_document = assume_role_policy_document.pretty_inspect
|
|
127
135
|
assume_role_policy_document.gsub!("\n", "\n ").strip!
|
|
@@ -193,10 +201,16 @@ end
|
|
|
193
201
|
end
|
|
194
202
|
|
|
195
203
|
def target_matched?(name)
|
|
204
|
+
result = true
|
|
205
|
+
|
|
206
|
+
if @options[:exclude]
|
|
207
|
+
result &&= @options[:exclude].all? {|r| name !~ r}
|
|
208
|
+
end
|
|
209
|
+
|
|
196
210
|
if @options[:target]
|
|
197
|
-
|
|
198
|
-
else
|
|
199
|
-
true
|
|
211
|
+
result &&= @options[:target].any? {|r| name =~ r}
|
|
200
212
|
end
|
|
213
|
+
|
|
214
|
+
result
|
|
201
215
|
end
|
|
202
216
|
end
|
data/lib/miam/exporter.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
class Miam::Exporter
|
|
3
3
|
AWS_MANAGED_POLICY_PREFIX = 'arn:aws:iam::aws:'
|
|
4
|
+
AWS_CN_MANAGED_POLICY_PREFIX = 'arn:aws-cn:iam::aws:'
|
|
4
5
|
|
|
5
6
|
def self.export(iam, options = {})
|
|
6
7
|
self.new(iam, options).export
|
|
@@ -143,6 +144,8 @@ class Miam::Exporter
|
|
|
143
144
|
instance_profiles = role.instance_profile_list.map {|i| i.instance_profile_name }
|
|
144
145
|
policies = export_role_policies(role)
|
|
145
146
|
attached_managed_policies = role.attached_managed_policies.map(&:policy_arn)
|
|
147
|
+
role_data = @iam.get_role(role_name: role_name).role
|
|
148
|
+
max_session_duration = role_data.max_session_duration
|
|
146
149
|
|
|
147
150
|
@mutex.synchronize do
|
|
148
151
|
instance_profiles.each do |instance_profile_name|
|
|
@@ -158,6 +161,7 @@ class Miam::Exporter
|
|
|
158
161
|
:instance_profiles => instance_profiles,
|
|
159
162
|
:policies => policies,
|
|
160
163
|
:attached_managed_policies => attached_managed_policies,
|
|
164
|
+
:max_session_duration => max_session_duration,
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
progress
|
|
@@ -200,7 +204,7 @@ class Miam::Exporter
|
|
|
200
204
|
result = {}
|
|
201
205
|
|
|
202
206
|
Parallel.each(policies, :in_threads => @concurrency) do |policy|
|
|
203
|
-
if policy.arn.start_with?(AWS_MANAGED_POLICY_PREFIX)
|
|
207
|
+
if policy.arn.start_with?(AWS_MANAGED_POLICY_PREFIX) or policy.arn.start_with?(AWS_CN_MANAGED_POLICY_PREFIX)
|
|
204
208
|
next
|
|
205
209
|
end
|
|
206
210
|
|
|
@@ -262,10 +266,10 @@ class Miam::Exporter
|
|
|
262
266
|
@iam.get_account_authorization_details.each do |resp|
|
|
263
267
|
keys.each do |key|
|
|
264
268
|
account_authorization_details[key].concat(resp[key])
|
|
269
|
+
end
|
|
265
270
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
end
|
|
271
|
+
unless @options[:no_progress]
|
|
272
|
+
progressbar.increment
|
|
269
273
|
end
|
|
270
274
|
end
|
|
271
275
|
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
class Miam::PasswordManager
|
|
2
2
|
include Miam::Logger::Helper
|
|
3
3
|
|
|
4
|
+
LOWERCASES = ('a'..'z').to_a
|
|
5
|
+
UPPERCASES = ('A'..'Z').to_a
|
|
6
|
+
NUMBERS = ('0'..'9').to_a
|
|
7
|
+
SYMBOLS = "!@\#$%^&*()_+-=[]{}|'".split(//)
|
|
8
|
+
|
|
4
9
|
def initialize(output, options = {})
|
|
5
10
|
@output = output
|
|
6
11
|
@options = options
|
|
7
12
|
end
|
|
8
13
|
|
|
9
|
-
def identify(user, type)
|
|
10
|
-
password = mkpasswd
|
|
14
|
+
def identify(user, type, policy)
|
|
15
|
+
password = mkpasswd(policy)
|
|
16
|
+
log(:debug, "mkpasswd: #{password}")
|
|
11
17
|
puts_password(user, type, password)
|
|
12
18
|
password
|
|
13
19
|
end
|
|
@@ -22,8 +28,21 @@ class Miam::PasswordManager
|
|
|
22
28
|
|
|
23
29
|
private
|
|
24
30
|
|
|
25
|
-
def mkpasswd(
|
|
26
|
-
|
|
31
|
+
def mkpasswd(policy)
|
|
32
|
+
chars = []
|
|
33
|
+
len = 8
|
|
34
|
+
|
|
35
|
+
if policy
|
|
36
|
+
len = policy.minimum_password_length if policy.minimum_password_length > len
|
|
37
|
+
chars << LOWERCASES.shuffle.first if policy.require_lowercase_characters
|
|
38
|
+
chars << UPPERCASES.shuffle.first if policy.require_uppercase_characters
|
|
39
|
+
chars << NUMBERS.shuffle.first if policy.require_numbers
|
|
40
|
+
chars << SYMBOLS.shuffle.first if policy.require_symbols
|
|
41
|
+
|
|
42
|
+
len -= chars.length
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
(chars + [*1..9, *'A'..'Z', *'a'..'z'].shuffle.slice(0, len)).shuffle.join
|
|
27
46
|
end
|
|
28
47
|
|
|
29
48
|
def open_output
|
data/lib/miam/template_helper.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Miam
|
|
2
2
|
module TemplateHelper
|
|
3
3
|
def include_template(template_name, context = {})
|
|
4
|
+
@template_name = template_name
|
|
5
|
+
@caller = caller[0]
|
|
4
6
|
tmplt = @context.templates[template_name.to_s]
|
|
5
7
|
|
|
6
8
|
unless tmplt
|
|
@@ -16,5 +18,14 @@ module Miam
|
|
|
16
18
|
def context
|
|
17
19
|
@context
|
|
18
20
|
end
|
|
21
|
+
|
|
22
|
+
def required(*args)
|
|
23
|
+
missing_args = args.map(&:to_s) - @context.keys.map(&:to_s)
|
|
24
|
+
unless missing_args.empty?
|
|
25
|
+
ex = ArgumentError.new("Missing arguments: [#{missing_args.join(", ")}] in template: '#{@template_name}'")
|
|
26
|
+
ex.set_backtrace(@caller)
|
|
27
|
+
raise ex
|
|
28
|
+
end
|
|
29
|
+
end
|
|
19
30
|
end
|
|
20
31
|
end
|
data/lib/miam/version.rb
CHANGED
data/lib/miam.rb
CHANGED
data/miam.gemspec
CHANGED
|
@@ -8,11 +8,9 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.version = Miam::VERSION
|
|
9
9
|
spec.authors = ['Genki Sugawara']
|
|
10
10
|
spec.email = ['sgwr_dts@yahoo.co.jp']
|
|
11
|
-
spec.summary = %q{TODO: Write a short summary. Required.}
|
|
12
|
-
spec.description = %q{TODO: Write a longer description. Optional.}
|
|
13
11
|
spec.summary = %q{Miam is a tool to manage IAM.}
|
|
14
12
|
spec.description = %q{Miam is a tool to manage IAM. It defines the state of IAM using DSL, and updates IAM according to DSL.}
|
|
15
|
-
spec.homepage = '
|
|
13
|
+
spec.homepage = 'https://github.com/codenize-tools/miam'
|
|
16
14
|
spec.license = 'MIT'
|
|
17
15
|
|
|
18
16
|
spec.files = `git ls-files -z`.split("\x0")
|
|
@@ -20,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
|
20
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
21
19
|
spec.require_paths = ['lib']
|
|
22
20
|
|
|
23
|
-
spec.add_dependency 'aws-sdk-
|
|
21
|
+
spec.add_dependency 'aws-sdk-iam', '~> 1'
|
|
24
22
|
spec.add_dependency 'ruby-progressbar'
|
|
25
23
|
spec.add_dependency 'parallel'
|
|
26
24
|
spec.add_dependency 'term-ansicolor'
|
|
@@ -31,4 +29,5 @@ Gem::Specification.new do |spec|
|
|
|
31
29
|
spec.add_development_dependency 'rspec', '>= 3.0.0'
|
|
32
30
|
spec.add_development_dependency 'rspec-instafail'
|
|
33
31
|
spec.add_development_dependency 'coveralls'
|
|
32
|
+
spec.add_development_dependency 'nokogiri'
|
|
34
33
|
end
|