subiam 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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