miam 0.2.3 → 0.2.4.beta11
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 +4 -4
- data/.rspec +2 -1
- data/.travis.yml +2 -2
- data/README.md +7 -0
- data/bin/miam +1 -0
- data/lib/miam/client.rb +18 -9
- data/lib/miam/driver.rb +33 -13
- data/lib/miam/dsl/converter.rb +9 -3
- data/lib/miam/exporter.rb +5 -4
- data/lib/miam/password_manager.rb +23 -4
- data/lib/miam/version.rb +1 -1
- data/miam.gemspec +1 -2
- metadata +19 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9138483028ecd9f31271ee0104cc8315c3c2761d
|
|
4
|
+
data.tar.gz: 3d4561d7d711aa87e8476889638d2ecfb3f209f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83cc09da8add9757a5069ffe4b157faef21426e25b9573af06ad97dbe439aaa6bc10f04ae43dc5150bfd1651b9b5181ef0a6856c9f40001ad085ff393029f126
|
|
7
|
+
data.tar.gz: 44b226fc05224222876d99fe7f767c929f4e33d089e2ec7a8d69fe0d2b86c54f4c7200e0778e779565f0ae98251fa9bfe72765cf158ec80d0dbc073bd2e74777
|
data/.rspec
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
language: ruby
|
|
3
3
|
rvm:
|
|
4
|
-
- 2.
|
|
4
|
+
- 2.3.3
|
|
5
5
|
script:
|
|
6
6
|
- bundle install
|
|
7
|
-
- bundle exec rake
|
|
7
|
+
- travis_wait bundle exec rake
|
|
8
8
|
env:
|
|
9
9
|
global:
|
|
10
10
|
- secure: "Ec8bwSfp06anSzLJpGhkKjPz9EocMwl7H8t2LKoI3bKV/cz9JU0GhhUdUUmg3mCFH9+8/YGLzcbFKcxZIqnDK7ukdgTGsnGDevnctis0QwM7/nKHdkaK8JWhCx41TUXOqoiCKkNEnm1EbDtrILt4rJwb0BGXGnPjv6nLAyvdE2Y="
|
data/README.md
CHANGED
|
@@ -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
|
@@ -52,6 +52,7 @@ ARGV.options do |opt|
|
|
|
52
52
|
opt.on('', '--format=FORMAT', [:ruby, :json]) {|v| format_passed = true; options[:format] = v }
|
|
53
53
|
opt.on('' , '--export-concurrency N', Integer) {|v| options[:export_concurrency] = v }
|
|
54
54
|
opt.on('' , '--target REGEXP') {|v| options[:target] = Regexp.new(v) }
|
|
55
|
+
opt.on('' , '--exclude REGEXP') {|v| options[:exclude] = Regexp.new(v) }
|
|
55
56
|
opt.on('' , '--ignore-login-profile') { options[:ignore_login_profile] = true }
|
|
56
57
|
opt.on('' , '--no-color') { options[:color] = false }
|
|
57
58
|
opt.on('' , '--no-progress') { options[:no_progress] = true }
|
data/lib/miam/client.rb
CHANGED
|
@@ -5,7 +5,8 @@ class Miam::Client
|
|
|
5
5
|
@options = {:format => :ruby}.merge(options)
|
|
6
6
|
aws_config = options.delete(:aws_config) || {}
|
|
7
7
|
@iam = Aws::IAM::Client.new(aws_config)
|
|
8
|
-
@
|
|
8
|
+
@sts = Aws::STS::Client.new(aws_config)
|
|
9
|
+
@driver = Miam::Driver.new(@iam, @sts, options)
|
|
9
10
|
@password_manager = options[:password_manager] || Miam::PasswordManager.new('-', options)
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -129,7 +130,7 @@ class Miam::Client
|
|
|
129
130
|
end
|
|
130
131
|
|
|
131
132
|
if expected_login_profile and not actual_login_profile
|
|
132
|
-
expected_login_profile[:password] ||= @password_manager.identify(user_name, :login_profile)
|
|
133
|
+
expected_login_profile[:password] ||= @password_manager.identify(user_name, :login_profile, @driver.password_policy)
|
|
133
134
|
@driver.create_login_profile(user_name, expected_login_profile)
|
|
134
135
|
updated = true
|
|
135
136
|
elsif not expected_login_profile and actual_login_profile
|
|
@@ -445,6 +446,7 @@ class Miam::Client
|
|
|
445
446
|
updated = false
|
|
446
447
|
|
|
447
448
|
expected.each do |policy_name, expected_attrs|
|
|
449
|
+
next unless target_matched?(policy_name)
|
|
448
450
|
actual_attrs = actual.delete(policy_name)
|
|
449
451
|
|
|
450
452
|
if actual_attrs
|
|
@@ -452,7 +454,7 @@ class Miam::Client
|
|
|
452
454
|
log(:warn, "ManagedPolicy `#{policy_name}`: 'path' cannot be updated", :color => :yellow)
|
|
453
455
|
end
|
|
454
456
|
|
|
455
|
-
updated = walk_managed_policy(policy_name, expected_attrs[:document], actual_attrs[:document]) || updated
|
|
457
|
+
updated = walk_managed_policy(policy_name, actual_attrs[:path], expected_attrs[:document], actual_attrs[:document]) || updated
|
|
456
458
|
else
|
|
457
459
|
@driver.create_managed_policy(policy_name, expected_attrs)
|
|
458
460
|
updated = true
|
|
@@ -462,13 +464,13 @@ class Miam::Client
|
|
|
462
464
|
updated
|
|
463
465
|
end
|
|
464
466
|
|
|
465
|
-
def walk_managed_policy(policy_name, expected_document, actual_document)
|
|
467
|
+
def walk_managed_policy(policy_name, policy_path, expected_document, actual_document)
|
|
466
468
|
updated = false
|
|
467
469
|
expected_document.sort_array!
|
|
468
470
|
actual_document.sort_array!
|
|
469
471
|
|
|
470
472
|
if expected_document != actual_document
|
|
471
|
-
@driver.update_managed_policy(policy_name, expected_document, actual_document)
|
|
473
|
+
@driver.update_managed_policy(policy_name, policy_path, expected_document, actual_document)
|
|
472
474
|
updated = true
|
|
473
475
|
end
|
|
474
476
|
|
|
@@ -479,7 +481,8 @@ class Miam::Client
|
|
|
479
481
|
updated = false
|
|
480
482
|
|
|
481
483
|
actual.each do |policy_name, actual_attrs|
|
|
482
|
-
|
|
484
|
+
next unless target_matched?(policy_name)
|
|
485
|
+
@driver.delete_managed_policy(policy_name, actual_attrs[:path])
|
|
483
486
|
updated = true
|
|
484
487
|
end
|
|
485
488
|
|
|
@@ -505,11 +508,17 @@ class Miam::Client
|
|
|
505
508
|
end
|
|
506
509
|
|
|
507
510
|
def target_matched?(name)
|
|
511
|
+
result = true
|
|
512
|
+
|
|
513
|
+
if @options[:exclude]
|
|
514
|
+
result &&= name !~ @options[:exclude]
|
|
515
|
+
end
|
|
516
|
+
|
|
508
517
|
if @options[:target]
|
|
509
|
-
name =~ @options[:target]
|
|
510
|
-
else
|
|
511
|
-
true
|
|
518
|
+
result &&= name =~ @options[:target]
|
|
512
519
|
end
|
|
520
|
+
|
|
521
|
+
result
|
|
513
522
|
end
|
|
514
523
|
|
|
515
524
|
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
|
|
@@ -377,12 +387,12 @@ class Miam::Driver
|
|
|
377
387
|
end
|
|
378
388
|
end
|
|
379
389
|
|
|
380
|
-
def delete_managed_policy(policy_name)
|
|
390
|
+
def delete_managed_policy(policy_name, policy_path)
|
|
381
391
|
log(:info, "Delete ManagedPolicy `#{policy_name}`", :color => :red)
|
|
382
392
|
|
|
383
393
|
unless_dry_run do
|
|
384
394
|
policy_versions = @iam.list_policy_versions(
|
|
385
|
-
:policy_arn => policy_arn(policy_name),
|
|
395
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
386
396
|
:max_items => MAX_POLICY_VERSIONS
|
|
387
397
|
)
|
|
388
398
|
|
|
@@ -390,24 +400,24 @@ class Miam::Driver
|
|
|
390
400
|
pv.is_default_version
|
|
391
401
|
}.each {|pv|
|
|
392
402
|
@iam.delete_policy_version(
|
|
393
|
-
:policy_arn => policy_arn(policy_name),
|
|
403
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
394
404
|
:version_id => pv.version_id
|
|
395
405
|
)
|
|
396
406
|
}
|
|
397
407
|
|
|
398
408
|
@iam.delete_policy(
|
|
399
|
-
:policy_arn => policy_arn(policy_name)
|
|
409
|
+
:policy_arn => policy_arn(policy_name, policy_path)
|
|
400
410
|
)
|
|
401
411
|
end
|
|
402
412
|
end
|
|
403
413
|
|
|
404
|
-
def update_managed_policy(policy_name, policy_document, old_policy_document)
|
|
414
|
+
def update_managed_policy(policy_name, policy_path, policy_document, old_policy_document)
|
|
405
415
|
log(:info, "Update ManagedPolicy `#{policy_name}`", :color => :green)
|
|
406
416
|
log(:info, Miam::Utils.diff(old_policy_document, policy_document, :color => @options[:color]), :color => false)
|
|
407
417
|
|
|
408
418
|
unless_dry_run do
|
|
409
419
|
policy_versions = @iam.list_policy_versions(
|
|
410
|
-
:policy_arn => policy_arn(policy_name),
|
|
420
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
411
421
|
:max_items => MAX_POLICY_VERSIONS
|
|
412
422
|
)
|
|
413
423
|
|
|
@@ -417,19 +427,27 @@ class Miam::Driver
|
|
|
417
427
|
}.sort_by {|pv| pv.version_id[1..-1].to_i }.first
|
|
418
428
|
|
|
419
429
|
@iam.delete_policy_version(
|
|
420
|
-
:policy_arn => policy_arn(policy_name),
|
|
430
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
421
431
|
:version_id => delete_policy_version.version_id
|
|
422
432
|
)
|
|
423
433
|
end
|
|
424
434
|
|
|
425
435
|
@iam.create_policy_version(
|
|
426
|
-
:policy_arn => policy_arn(policy_name),
|
|
436
|
+
:policy_arn => policy_arn(policy_name, policy_path),
|
|
427
437
|
:policy_document => encode_document(policy_document),
|
|
428
438
|
set_as_default: true
|
|
429
439
|
)
|
|
430
440
|
end
|
|
431
441
|
end
|
|
432
442
|
|
|
443
|
+
def password_policy
|
|
444
|
+
return @password_policy if instance_variable_defined?(:@password_policy)
|
|
445
|
+
|
|
446
|
+
@password_policy = @iam.get_account_password_policy.password_policy
|
|
447
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
448
|
+
@password_policy = nil
|
|
449
|
+
end
|
|
450
|
+
|
|
433
451
|
private
|
|
434
452
|
|
|
435
453
|
def encode_document(policy_document)
|
|
@@ -455,11 +473,13 @@ class Miam::Driver
|
|
|
455
473
|
yield unless @options[:dry_run]
|
|
456
474
|
end
|
|
457
475
|
|
|
458
|
-
def
|
|
459
|
-
|
|
476
|
+
def account_id
|
|
477
|
+
# https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
|
|
478
|
+
# http://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
|
|
479
|
+
@account_id ||= @sts.get_caller_identity.account
|
|
460
480
|
end
|
|
461
481
|
|
|
462
|
-
def policy_arn(policy_name)
|
|
463
|
-
"arn:aws:iam::#{
|
|
482
|
+
def policy_arn(policy_name, policy_path)
|
|
483
|
+
File.join("arn:aws:iam::#{account_id}:policy", policy_path, policy_name)
|
|
464
484
|
end
|
|
465
485
|
end
|
data/lib/miam/dsl/converter.rb
CHANGED
|
@@ -193,10 +193,16 @@ end
|
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
def target_matched?(name)
|
|
196
|
+
result = true
|
|
197
|
+
|
|
198
|
+
if @options[:exclude]
|
|
199
|
+
result &&= name !~ @options[:exclude]
|
|
200
|
+
end
|
|
201
|
+
|
|
196
202
|
if @options[:target]
|
|
197
|
-
name =~ @options[:target]
|
|
198
|
-
else
|
|
199
|
-
true
|
|
203
|
+
result &&= name =~ @options[:target]
|
|
200
204
|
end
|
|
205
|
+
|
|
206
|
+
result
|
|
201
207
|
end
|
|
202
208
|
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
|
|
@@ -200,7 +201,7 @@ class Miam::Exporter
|
|
|
200
201
|
result = {}
|
|
201
202
|
|
|
202
203
|
Parallel.each(policies, :in_threads => @concurrency) do |policy|
|
|
203
|
-
if policy.arn.start_with?(AWS_MANAGED_POLICY_PREFIX)
|
|
204
|
+
if policy.arn.start_with?(AWS_MANAGED_POLICY_PREFIX) or policy.arn.start_with?(AWS_CN_MANAGED_POLICY_PREFIX)
|
|
204
205
|
next
|
|
205
206
|
end
|
|
206
207
|
|
|
@@ -262,10 +263,10 @@ class Miam::Exporter
|
|
|
262
263
|
@iam.get_account_authorization_details.each do |resp|
|
|
263
264
|
keys.each do |key|
|
|
264
265
|
account_authorization_details[key].concat(resp[key])
|
|
266
|
+
end
|
|
265
267
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
end
|
|
268
|
+
unless @options[:no_progress]
|
|
269
|
+
progressbar.increment
|
|
269
270
|
end
|
|
270
271
|
end
|
|
271
272
|
|
|
@@ -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(:info, "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/version.rb
CHANGED
data/miam.gemspec
CHANGED
|
@@ -8,8 +8,6 @@ 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
13
|
spec.homepage = 'http://miam.codenize.tools/'
|
|
@@ -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
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: miam
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4.beta11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Genki Sugawara
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2017-02-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aws-sdk-core
|
|
@@ -164,6 +164,20 @@ dependencies:
|
|
|
164
164
|
- - ">="
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: nokogiri
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
167
181
|
description: Miam is a tool to manage IAM. It defines the state of IAM using DSL,
|
|
168
182
|
and updates IAM according to DSL.
|
|
169
183
|
email:
|
|
@@ -224,12 +238,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
224
238
|
version: '0'
|
|
225
239
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
226
240
|
requirements:
|
|
227
|
-
- - "
|
|
241
|
+
- - ">"
|
|
228
242
|
- !ruby/object:Gem::Version
|
|
229
|
-
version:
|
|
243
|
+
version: 1.3.1
|
|
230
244
|
requirements: []
|
|
231
245
|
rubyforge_project:
|
|
232
|
-
rubygems_version: 2.
|
|
246
|
+
rubygems_version: 2.5.2
|
|
233
247
|
signing_key:
|
|
234
248
|
specification_version: 4
|
|
235
249
|
summary: Miam is a tool to manage IAM.
|