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,101 @@
1
+ class Subiam::DSL::Context
2
+ include Subiam::TemplateHelper
3
+
4
+ def self.eval(dsl, path, options = {})
5
+ self.new(path, options) {
6
+ eval(dsl, binding, path)
7
+ }
8
+ end
9
+
10
+ attr_reader :result
11
+
12
+ def initialize(path, options = {}, &block)
13
+ @path = path
14
+ @options = options
15
+ @result = {:users => {}, :groups => {}, :roles => {}, :instance_profiles => {}, :policies => {}, :target => nil}
16
+
17
+ @context = Hashie::Mash.new(
18
+ :path => path,
19
+ :options => options,
20
+ :templates => {}
21
+ )
22
+
23
+ instance_eval(&block)
24
+ end
25
+
26
+ def template(name, &block)
27
+ @context.templates[name.to_s] = block
28
+ end
29
+
30
+ private
31
+
32
+ def import(file)
33
+ iamfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file))
34
+
35
+ if File.exist?(iamfile)
36
+ instance_eval(File.read(iamfile), iamfile)
37
+ elsif File.exist?(iamfile + '.rb')
38
+ instance_eval(File.read(iamfile + '.rb'), iamfile + '.rb')
39
+ else
40
+ raise("File: #{iamfile} or #{iamfile + '.rb'} not found.")
41
+ end
42
+ end
43
+
44
+ def target(regexp)
45
+ @result[:target] = regexp
46
+ end
47
+
48
+ def user(name, user_options = {}, &block)
49
+ name = name.to_s
50
+
51
+ if @result[:users][name]
52
+ raise "User `#{name}` is already defined"
53
+ end
54
+
55
+ attrs = Subiam::DSL::Context::User.new(@context, name, &block).result
56
+ @result[:users][name] = user_options.merge(attrs)
57
+ end
58
+
59
+ def group(name, group_options = {}, &block)
60
+ name = name.to_s
61
+
62
+ if @result[:groups][name]
63
+ raise "Group `#{name}` is already defined"
64
+ end
65
+
66
+ attrs = Subiam::DSL::Context::Group.new(@context, name, &block).result
67
+ @result[:groups][name] = group_options.merge(attrs)
68
+ end
69
+
70
+ def role(name, role_options = {}, &block)
71
+ name = name.to_s
72
+
73
+ if @result[:roles][name]
74
+ raise "Role `#{name}` is already defined"
75
+ end
76
+
77
+ attrs = Subiam::DSL::Context::Role.new(@context, name, &block).result
78
+ @result[:roles][name] = role_options.merge(attrs)
79
+ end
80
+
81
+ def instance_profile(name, instance_profile_options = {}, &block)
82
+ name = name.to_s
83
+
84
+ if @result[:instance_profiles][name]
85
+ raise "instance_profile `#{name}` is already defined"
86
+ end
87
+
88
+ @result[:instance_profiles][name] = instance_profile_options
89
+ end
90
+
91
+ def managed_policy(name, policy_options = {}, &block)
92
+ name = name.to_s
93
+
94
+ if @result[:policies][name]
95
+ raise "ManagedPolicy `#{name}` is already defined"
96
+ end
97
+
98
+ attrs = Subiam::DSL::Context::ManagedPolicy.new(@context, name, &block).result
99
+ @result[:policies][name] = policy_options.merge(attrs)
100
+ end
101
+ end
@@ -0,0 +1,202 @@
1
+ class Subiam::DSL::Converter
2
+ def self.convert(exported, options = {})
3
+ self.new(exported, options).convert
4
+ end
5
+
6
+ def initialize(exported, options = {})
7
+ @exported = exported
8
+ @options = options
9
+ end
10
+
11
+ def convert
12
+ [
13
+ output_users(@exported[:users]),
14
+ output_groups(@exported[:groups]),
15
+ output_roles(@exported[:roles]),
16
+ output_instance_profiles(@exported[:instance_profiles]),
17
+ output_managed_policies(@exported[:policies]),
18
+ ].join("\n")
19
+ end
20
+
21
+ private
22
+
23
+ def output_users(users)
24
+ users.each.sort_by {|k, v| k }.map {|user_name, attrs|
25
+ next unless target_matched?(user_name)
26
+ output_user(user_name, attrs)
27
+ }.select {|i| i }.join("\n")
28
+ end
29
+
30
+ def output_user(user_name, attrs)
31
+ user_options = {:path => attrs[:path]}
32
+
33
+ <<-EOS
34
+ user #{user_name.inspect}, #{Subiam::Utils.unbrace(user_options.inspect)} do
35
+ #{output_login_profile(attrs[:login_profile])}
36
+
37
+ #{output_user_groups(attrs[:groups])}
38
+
39
+ #{output_policies(attrs[:policies])}
40
+
41
+ #{output_attached_managed_policies(attrs[:attached_managed_policies])}
42
+ end
43
+ EOS
44
+ end
45
+
46
+ def output_user_groups(groups)
47
+ if groups.empty?
48
+ groups = ['# no group']
49
+ else
50
+ groups = groups.map {|i| i.inspect }
51
+ end
52
+
53
+ groups = "\n " + groups.join(",\n ") + "\n "
54
+ "groups(#{groups})"
55
+ end
56
+
57
+ def output_login_profile(login_profile)
58
+ if login_profile
59
+ "login_profile #{Subiam::Utils.unbrace(login_profile.inspect)}"
60
+ else
61
+ '# login_profile :password_reset_required=>true'
62
+ end
63
+ end
64
+
65
+ def output_groups(groups)
66
+ groups.each.sort_by {|k, v| k }.map {|group_name, attrs|
67
+ next unless target_matched?(group_name)
68
+ output_group(group_name, attrs)
69
+ }.select {|i| i }.join("\n")
70
+ end
71
+
72
+ def output_group(group_name, attrs)
73
+ group_options = {:path => attrs[:path]}
74
+
75
+ <<-EOS
76
+ group #{group_name.inspect}, #{Subiam::Utils.unbrace(group_options.inspect)} do
77
+ #{output_policies(attrs[:policies])}
78
+
79
+ #{output_attached_managed_policies(attrs[:attached_managed_policies])}
80
+ end
81
+ EOS
82
+ end
83
+
84
+ def output_roles(roles)
85
+ roles.each.sort_by {|k, v| k }.map {|role_name, attrs|
86
+ next unless target_matched?(role_name)
87
+ output_role(role_name, attrs)
88
+ }.select {|i| i }.join("\n")
89
+ end
90
+
91
+ def output_role(role_name, attrs)
92
+ role_options = {:path => attrs[:path]}
93
+
94
+ <<-EOS
95
+ role #{role_name.inspect}, #{Subiam::Utils.unbrace(role_options.inspect)} do
96
+ #{output_role_instance_profiles(attrs[:instance_profiles])}
97
+
98
+ #{output_assume_role_policy_document(attrs[:assume_role_policy_document])}
99
+
100
+ #{output_policies(attrs[:policies])}
101
+
102
+ #{output_attached_managed_policies(attrs[:attached_managed_policies])}
103
+ end
104
+ EOS
105
+ end
106
+
107
+ def output_role_instance_profiles(instance_profiles)
108
+ if instance_profiles.empty?
109
+ instance_profiles = ['# no instance_profile']
110
+ else
111
+ instance_profiles = instance_profiles.map {|i| i.inspect }
112
+ end
113
+
114
+ instance_profiles = "\n " + instance_profiles.join(",\n ") + "\n "
115
+ "instance_profiles(#{instance_profiles})"
116
+ end
117
+
118
+ def output_instance_profiles(instance_profiles)
119
+ instance_profiles.each.sort_by {|k, v| k }.map {|instance_profile_name, attrs|
120
+ next unless target_matched?(instance_profile_name)
121
+ output_instance_profile(instance_profile_name, attrs)
122
+ }.select {|i| i }.join("\n")
123
+ end
124
+
125
+ def output_assume_role_policy_document(assume_role_policy_document)
126
+ assume_role_policy_document = assume_role_policy_document.pretty_inspect
127
+ assume_role_policy_document.gsub!("\n", "\n ").strip!
128
+
129
+ <<-EOS.strip
130
+ assume_role_policy_document do
131
+ #{assume_role_policy_document}
132
+ end
133
+ EOS
134
+ end
135
+
136
+ def output_instance_profile(instance_profile_name, attrs)
137
+ instance_profile_options = {:path => attrs[:path]}
138
+
139
+ <<-EOS
140
+ instance_profile #{instance_profile_name.inspect}, #{Subiam::Utils.unbrace(instance_profile_options.inspect)}
141
+ EOS
142
+ end
143
+
144
+ def output_policies(policies)
145
+ if policies.empty?
146
+ "# no policy"
147
+ else
148
+ policies.map {|policy_name, policy_document|
149
+ output_policy(policy_name, policy_document)
150
+ }.join("\n\n ").strip
151
+ end
152
+ end
153
+
154
+ def output_policy(policy_name, policy_document)
155
+ policy_document = policy_document.pretty_inspect
156
+ policy_document.gsub!("\n", "\n ").strip!
157
+
158
+ <<-EOS.strip
159
+ policy #{policy_name.inspect} do
160
+ #{policy_document}
161
+ end
162
+ EOS
163
+ end
164
+
165
+ def output_attached_managed_policies(attached_managed_policies)
166
+ if attached_managed_policies.empty?
167
+ attached_managed_policies = ['# attached_managed_policy']
168
+ else
169
+ attached_managed_policies = attached_managed_policies.map {|i| i.inspect }
170
+ end
171
+
172
+ attached_managed_policies = "\n " + attached_managed_policies.join(",\n ") + "\n "
173
+ "attached_managed_policies(#{attached_managed_policies})"
174
+ end
175
+
176
+ def output_managed_policies(policies)
177
+ policies.each.sort_by {|k, v| k }.map {|policy_name, attrs|
178
+ next unless target_matched?(policy_name)
179
+ output_managed_policy(policy_name, attrs)
180
+ }.select {|i| i }.join("\n")
181
+ end
182
+
183
+ def output_managed_policy(policy_name, attrs)
184
+ policy_options = {:path => attrs[:path]}
185
+ policy_document = attrs[:document].pretty_inspect
186
+ policy_document.gsub!("\n", "\n ").strip!
187
+
188
+ <<-EOS
189
+ managed_policy #{policy_name.inspect}, #{Subiam::Utils.unbrace(policy_options.inspect)} do
190
+ #{policy_document}
191
+ end
192
+ EOS
193
+ end
194
+
195
+ def target_matched?(name)
196
+ if @options[:target]
197
+ name =~ @options[:target]
198
+ else
199
+ true
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,22 @@
1
+ module Subiam::DSL::Helper
2
+ module Arn
3
+ private
4
+
5
+ def arn_policy_by_aws(name)
6
+ "arn:aws:iam::aws:policy/#{name}"
7
+ end
8
+
9
+ def arn_policy_by_current_account(name)
10
+ "arn:aws:iam::#{current_account}:policy/#{name}"
11
+ end
12
+
13
+ def current_account
14
+ if @current_account
15
+ return @current_account
16
+ end
17
+ aws_config = (@context.options && @context.options[:aws_config]) ? @context.options[:aws_config] : {}
18
+ sts = Aws::STS::Client.new(aws_config)
19
+ @current_account = sts.get_caller_identity.user_id
20
+ end
21
+ end
22
+ end
data/lib/subiam/dsl.rb ADDED
@@ -0,0 +1,9 @@
1
+ class Subiam::DSL
2
+ def self.convert(exported, options = {})
3
+ Subiam::DSL::Converter.convert(exported, options)
4
+ end
5
+
6
+ def self.parse(dsl, path, options = {})
7
+ Subiam::DSL::Context.eval(dsl, path, options).result
8
+ end
9
+ end
@@ -0,0 +1,278 @@
1
+ # coding: utf-8
2
+ class Subiam::Exporter
3
+ AWS_MANAGED_POLICY_PREFIX = 'arn:aws:iam::aws:'
4
+
5
+ def self.export(iam, options = {})
6
+ self.new(iam, options).export
7
+ end
8
+
9
+ def initialize(iam, options = {})
10
+ @iam = iam
11
+ @options = options
12
+ @mutex = Mutex.new
13
+ @concurrency = options[:export_concurrency] || 16
14
+ end
15
+
16
+ def export
17
+ account_authorization_details = get_account_authorization_details
18
+
19
+ users = account_authorization_details[:user_detail_list]
20
+ groups = account_authorization_details[:group_detail_list]
21
+ roles = account_authorization_details[:role_detail_list]
22
+ policies = account_authorization_details[:policies]
23
+ instance_profiles = list_instance_profiles
24
+ group_users = {}
25
+ instance_profile_roles = {}
26
+
27
+ unless @options[:no_progress]
28
+ progress_total = users.length + groups.length + roles.length + instance_profiles.length
29
+
30
+ @progressbar = ProgressBar.create(
31
+ :format => ' %bᗧ%i %p%%',
32
+ :progress_mark => ' ',
33
+ :remainder_mark => '・',
34
+ :total => progress_total,
35
+ :output => $stderr)
36
+ end
37
+
38
+ expected = {
39
+ :users => export_users(users, group_users),
40
+ :groups => export_groups(groups),
41
+ :roles => export_roles(roles, instance_profile_roles),
42
+ :instance_profiles => export_instance_profiles(instance_profiles),
43
+ :policies => export_policies(policies),
44
+ }
45
+
46
+ [expected, group_users, instance_profile_roles]
47
+ end
48
+
49
+ private
50
+
51
+ def export_users(users, group_users)
52
+ result = {}
53
+
54
+ Parallel.each(users, :in_threads => @concurrency) do |user|
55
+ user_name = user.user_name
56
+ groups = user.group_list
57
+ policies = export_user_policies(user)
58
+ login_profile = export_login_profile(user_name)
59
+ attached_managed_policies = user.attached_managed_policies.map(&:policy_arn)
60
+
61
+ @mutex.synchronize do
62
+ groups.each do |group_name|
63
+ group_users[group_name] ||= []
64
+ group_users[group_name] << user_name
65
+ end
66
+
67
+ result[user_name] = {
68
+ :path => user.path,
69
+ :groups => groups,
70
+ :policies => policies,
71
+ :attached_managed_policies => attached_managed_policies
72
+ }
73
+
74
+ if login_profile
75
+ result[user_name][:login_profile] = login_profile
76
+ end
77
+
78
+ progress
79
+ end
80
+ end
81
+
82
+ result
83
+ end
84
+
85
+ def export_user_policies(user)
86
+ result = {}
87
+
88
+ user.user_policy_list.each do |policy|
89
+ document = CGI.unescape(policy.policy_document)
90
+ result[policy.policy_name] = JSON.parse(document)
91
+ end
92
+
93
+ result
94
+ end
95
+
96
+ def export_login_profile(user_name)
97
+ begin
98
+ resp = @iam.get_login_profile(:user_name => user_name)
99
+ {:password_reset_required => resp.login_profile.password_reset_required}
100
+ rescue Aws::IAM::Errors::NoSuchEntity
101
+ nil
102
+ end
103
+ end
104
+
105
+ def export_groups(groups)
106
+ result = {}
107
+
108
+ Parallel.each(groups, :in_threads => @concurrency) do |group|
109
+ group_name = group.group_name
110
+ policies = export_group_policies(group)
111
+ attached_managed_policies = group.attached_managed_policies.map(&:policy_arn)
112
+
113
+ @mutex.synchronize do
114
+ result[group_name] = {
115
+ :path => group.path,
116
+ :policies => policies,
117
+ :attached_managed_policies => attached_managed_policies,
118
+ }
119
+
120
+ progress
121
+ end
122
+ end
123
+
124
+ result
125
+ end
126
+
127
+ def export_group_policies(group)
128
+ result = {}
129
+
130
+ group.group_policy_list.each do |policy|
131
+ document = CGI.unescape(policy.policy_document)
132
+ result[policy.policy_name] = JSON.parse(document)
133
+ end
134
+
135
+ result
136
+ end
137
+
138
+ def export_roles(roles, instance_profile_roles)
139
+ result = {}
140
+
141
+ Parallel.each(roles, :in_threads => @concurrency) do |role|
142
+ role_name = role.role_name
143
+ instance_profiles = role.instance_profile_list.map {|i| i.instance_profile_name }
144
+ policies = export_role_policies(role)
145
+ attached_managed_policies = role.attached_managed_policies.map(&:policy_arn)
146
+
147
+ @mutex.synchronize do
148
+ instance_profiles.each do |instance_profile_name|
149
+ instance_profile_roles[instance_profile_name] ||= []
150
+ instance_profile_roles[instance_profile_name] << role_name
151
+ end
152
+
153
+ document = CGI.unescape(role.assume_role_policy_document)
154
+
155
+ result[role_name] = {
156
+ :path => role.path,
157
+ :assume_role_policy_document => JSON.parse(document),
158
+ :instance_profiles => instance_profiles,
159
+ :policies => policies,
160
+ :attached_managed_policies => attached_managed_policies,
161
+ }
162
+
163
+ progress
164
+ end
165
+ end
166
+
167
+ result
168
+ end
169
+
170
+ def export_role_policies(role)
171
+ result = {}
172
+
173
+ role.role_policy_list.each do |policy|
174
+ document = CGI.unescape(policy.policy_document)
175
+ result[policy.policy_name] = JSON.parse(document)
176
+ end
177
+
178
+ result
179
+ end
180
+
181
+ def export_instance_profiles(instance_profiles)
182
+ result = {}
183
+
184
+ Parallel.each(instance_profiles, :in_threads => @concurrency) do |instance_profile|
185
+ instance_profile_name = instance_profile.instance_profile_name
186
+
187
+ @mutex.synchronize do
188
+ result[instance_profile_name] = {
189
+ :path => instance_profile.path,
190
+ }
191
+
192
+ progress
193
+ end
194
+ end
195
+
196
+ result
197
+ end
198
+
199
+ def export_policies(policies)
200
+ result = {}
201
+
202
+ Parallel.each(policies, :in_threads => @concurrency) do |policy|
203
+ if policy.arn.start_with?(AWS_MANAGED_POLICY_PREFIX)
204
+ next
205
+ end
206
+
207
+ policy_name = policy.policy_name
208
+ document = export_policy_document(policy)
209
+
210
+ result[policy_name] = {
211
+ :path => policy.path,
212
+ :document => document,
213
+ }
214
+ end
215
+
216
+ result
217
+ end
218
+
219
+ def export_policy_document(policy)
220
+ policy_version = nil
221
+
222
+ policy_version_list = policy.policy_version_list.sort_by do |pv|
223
+ pv.version_id[1..-1].to_i
224
+ end
225
+
226
+ policy_version_list.each do |pv|
227
+ policy_version = pv
228
+
229
+ if pv.is_default_version
230
+ break
231
+ end
232
+ end
233
+
234
+ document = CGI.unescape(policy_version.document)
235
+ JSON.parse(document)
236
+ end
237
+
238
+ def list_instance_profiles
239
+ @iam.list_instance_profiles.map {|resp|
240
+ resp.instance_profiles.to_a
241
+ }.flatten
242
+ end
243
+
244
+ def get_account_authorization_details
245
+ account_authorization_details = {}
246
+
247
+ unless @options[:no_progress]
248
+ progressbar = ProgressBar.create(:title => 'Loading', :total => nil, :output => $stderr)
249
+ end
250
+
251
+ keys = [
252
+ :user_detail_list,
253
+ :group_detail_list,
254
+ :role_detail_list,
255
+ :policies,
256
+ ]
257
+
258
+ keys.each do |key|
259
+ account_authorization_details[key] = []
260
+ end
261
+
262
+ @iam.get_account_authorization_details.each do |resp|
263
+ keys.each do |key|
264
+ account_authorization_details[key].concat(resp[key])
265
+
266
+ unless @options[:no_progress]
267
+ progressbar.increment
268
+ end
269
+ end
270
+ end
271
+
272
+ account_authorization_details
273
+ end
274
+
275
+ def progress
276
+ @progressbar.increment if @progressbar
277
+ end
278
+ end
@@ -0,0 +1,13 @@
1
+ class Array
2
+ def keys_to_s_recursive
3
+ new_one = []
4
+ self.each_with_index do |elem, index|
5
+ if elem.respond_to? :keys_to_s_recursive
6
+ new_one[index] = elem.keys_to_s_recursive
7
+ next
8
+ end
9
+ new_one[index] = elem
10
+ end
11
+ new_one
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ class Hash
2
+ def sort_array!
3
+ keys.each do |key|
4
+ value = self[key]
5
+ self[key] = sort_array0(value)
6
+ end
7
+
8
+ self
9
+ end
10
+
11
+ def keys_to_s_recursive
12
+ new_one = {}
13
+ self.each do |key, elem|
14
+ if elem.respond_to? :keys_to_s_recursive
15
+ new_one[key.to_s] = elem.keys_to_s_recursive
16
+ next
17
+ end
18
+ new_one[key.to_s] = elem
19
+ end
20
+ new_one
21
+ end
22
+
23
+ private
24
+
25
+ def sort_array0(value)
26
+ case value
27
+ when Hash
28
+ new_value = {}
29
+
30
+ value.each do |k, v|
31
+ new_value[k] = sort_array0(v)
32
+ end
33
+
34
+ new_value
35
+ when Array
36
+ value.map {|v| sort_array0(v) }.sort_by(&:to_s)
37
+ else
38
+ value
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ class String
2
+ @@colorize = false
3
+
4
+ class << self
5
+ def colorize=(value)
6
+ @@colorize = value
7
+ end
8
+
9
+ def colorize
10
+ @@colorize
11
+ end
12
+ end # of class methods
13
+
14
+ Term::ANSIColor::Attribute.named_attributes.map do |attribute|
15
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
16
+ def #{attribute.name}
17
+ if @@colorize
18
+ Term::ANSIColor.send(#{attribute.name.inspect}, self)
19
+ else
20
+ self
21
+ end
22
+ end
23
+ EOS
24
+ end
25
+ end