himeko 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ require 'aws-sdk-dynamodb'
2
+ require 'himeko/user_mimicking_role'
3
+
4
+ module Himeko
5
+ class RoleManager
6
+ def initialize(iam:, prefix:, path:, ttl: 86400, dynamodb_table:)
7
+ @iam = iam
8
+ @prefix = prefix
9
+ @path = path
10
+ @table = dynamodb_table
11
+ @ttl = ttl
12
+ end
13
+
14
+ attr_reader :iam, :prefix, :path, :table, :ttl
15
+
16
+ def fetch(username, recreate: false)
17
+ item = table.query(
18
+ limit: 1,
19
+ select: 'ALL_ATTRIBUTES',
20
+ key_condition_expression: 'username = :username',
21
+ expression_attribute_values: {":username" => username},
22
+ ).items.first
23
+
24
+ if recreate || item.nil?
25
+ begin
26
+ return create(username)
27
+ rescue Aws::IAM::Errors::EntityAlreadyExists
28
+ remove(username, delete_record: false)
29
+ retry
30
+ end
31
+ end
32
+
33
+ table.update_item(
34
+ key: {
35
+ 'username' => username,
36
+ },
37
+ update_expression: 'SET expires_at = :expires_at',
38
+ expression_attribute_values: {
39
+ ':expires_at' => (Time.now + ttl).to_i,
40
+ },
41
+ )
42
+
43
+ item.fetch('role_arn')
44
+ end
45
+
46
+ def remove(username, role_name: nil, delete_record: true)
47
+ role_name ||= role_name_for_username(username)
48
+
49
+ iam.list_attached_role_policies(role_name: role_name).each.flat_map(&:attached_policies).map(&:policy_arn).each do |policy_arn|
50
+ iam.detach_role_policy(role_name: role_name, policy_arn: policy_arn)
51
+ end
52
+ iam.list_role_policies(role_name: role_name).policy_names.each do |policy_name|
53
+ iam.delete_role_policy(role_name: role_name, policy_name: policy_name)
54
+ end
55
+ iam.delete_role(role_name: role_name)
56
+
57
+ if delete_record
58
+ table.delete_item(
59
+ key: {
60
+ 'username' => username,
61
+ },
62
+ )
63
+ end
64
+ rescue Aws::IAM::Errors::NoSuchEntity
65
+ # do nothing
66
+ end
67
+
68
+ def create(username)
69
+ role_arn = UserMimickingRole.new(iam, username, role_name_for_username(username), path).create
70
+
71
+ table.update_item(
72
+ key: {
73
+ 'username' => username,
74
+ },
75
+ update_expression: 'SET expires_at = :expires_at, role_arn = :role_arn',
76
+ expression_attribute_values: {
77
+ ':expires_at' => (Time.now + ttl).to_i,
78
+ ':role_arn' => role_arn,
79
+ },
80
+ )
81
+
82
+ role_arn
83
+ end
84
+
85
+ def prune
86
+ table.client.scan(
87
+ table_name: table.table_name,
88
+ select: 'ALL_ATTRIBUTES',
89
+ filter_expression: 'expires_at < :now',
90
+ expression_attribute_values: {
91
+ ':now' => Time.now.to_i,
92
+ },
93
+ ).each do |page|
94
+ page.items.each do |item|
95
+ puts "==> #{item['username']} (#{item['role_arn']})"
96
+ remove(item['username'], role_name: item['role_arn'].split(?/)[-1])
97
+ table.delete_item(key: {'username' => item['username']})
98
+ end
99
+ end
100
+ end
101
+
102
+ def role_name_for_username(username)
103
+ "#{prefix}#{username}"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,148 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'aws-sdk-iam'
4
+
5
+ module Himeko
6
+ class UserMimickingRole
7
+ def initialize(iam, username, role_name, path = nil, driver: nil)
8
+ @driver = driver || Driver.new(iam)
9
+ @username = username
10
+ @role_name = role_name
11
+ @path = path
12
+ end
13
+
14
+ attr_reader :driver, :username, :role_name, :path
15
+
16
+ # @return [String] role arn
17
+ def create
18
+ arn = driver.create_role(
19
+ path: path,
20
+ role_name: role_name,
21
+ assume_role_policy_document: assume_role_policy_document,
22
+ )
23
+
24
+ managed_policies.each do |policy_arn|
25
+ driver.attach_role_policy(role_name, policy_arn)
26
+ end
27
+
28
+ policies.each do |policy_name, policy|
29
+ driver.put_role_policy(role_name, policy_name, policy)
30
+ end
31
+
32
+ return arn
33
+ end
34
+
35
+ def user
36
+ @user ||= driver.get_user(username)
37
+ end
38
+
39
+ def account_id
40
+ user.arn.split(?:)[4]
41
+ end
42
+
43
+ def assume_role_policy_document
44
+ {
45
+ "Version"=>"2012-10-17",
46
+ "Statement"=>[
47
+ {
48
+ "Effect"=>"Allow",
49
+ "Principal"=>{
50
+ "AWS"=>[
51
+ "arn:aws:iam::#{account_id}:root",
52
+ ]
53
+ },
54
+ "Action"=>"sts:AssumeRole",
55
+ "Condition"=>{},
56
+ },
57
+ ],
58
+ }
59
+ end
60
+
61
+ def groups
62
+ @groups ||= driver.list_groups_for_user(username)
63
+ end
64
+
65
+ def managed_policies
66
+ @managed_policies ||= [
67
+ *driver.list_attached_user_policies(username),
68
+ *groups.flat_map do |group_name|
69
+ driver.list_attached_group_policies(group_name)
70
+ end,
71
+ ].sort.uniq
72
+ end
73
+
74
+ def policies
75
+ @policies ||= [
76
+ *driver.list_user_policies(username).map do |policy_name|
77
+ [policy_name, driver.get_user_policy(username, policy_name)]
78
+ end,
79
+ *groups.flat_map do |group_name|
80
+ driver.list_group_policies(group_name).map do |policy_name|
81
+ ["#{group_name}_#{policy_name}"[0..127], driver.get_group_policy(group_name, policy_name)]
82
+ end
83
+ end,
84
+ ].to_h
85
+ end
86
+
87
+ class Driver
88
+ def initialize(iam)
89
+ @iam = iam
90
+ end
91
+
92
+ attr_reader :iam
93
+
94
+ def get_user(username)
95
+ iam.get_user(user_name: username).user
96
+ end
97
+
98
+ def list_attached_user_policies(username)
99
+ iam.list_attached_user_policies(user_name: username).each.flat_map(&:attached_policies).map(&:policy_arn)
100
+ end
101
+
102
+ def list_user_policies(username)
103
+ iam.list_user_policies(user_name: username).policy_names
104
+ end
105
+
106
+ def get_user_policy(username, policy_name)
107
+ URI.decode_www_form_component(iam.get_user_policy(user_name: username, policy_name: policy_name).policy_document)
108
+ end
109
+
110
+ def list_groups_for_user(username)
111
+ iam.list_groups_for_user(user_name: username).groups.map(&:group_name)
112
+ end
113
+
114
+ def list_group_policies(group_name)
115
+ iam.list_group_policies(group_name: group_name).policy_names
116
+ end
117
+
118
+ def get_group_policy(group_name, policy_name)
119
+ URI.decode_www_form_component(iam.get_group_policy(group_name: group_name, policy_name: policy_name).policy_document)
120
+ end
121
+
122
+ def list_attached_group_policies(group_name)
123
+ iam.list_attached_group_policies(group_name: group_name).each.flat_map(&:attached_policies).map(&:policy_arn)
124
+ end
125
+
126
+ def create_role(path:, role_name:, assume_role_policy_document:, max_session_duration: 43200)
127
+ iam.create_role(
128
+ path: path,
129
+ role_name: role_name,
130
+ assume_role_policy_document: assume_role_policy_document.to_json,
131
+ max_session_duration: max_session_duration
132
+ ).role.arn
133
+ end
134
+
135
+ def attach_role_policy(role_name, policy_arn)
136
+ iam.attach_role_policy(role_name: role_name, policy_arn: policy_arn)
137
+ end
138
+
139
+ def put_role_policy(role_name, policy_name, policy)
140
+ iam.put_role_policy(
141
+ role_name: role_name,
142
+ policy_name: policy_name,
143
+ policy_document: policy,
144
+ )
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,3 @@
1
+ module Himeko
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: himeko
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-iam
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk-dynamodb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-protection
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: erubi
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ description:
140
+ email:
141
+ - sorah@cookpad.com
142
+ executables:
143
+ - himeko-clean-roles
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".travis.yml"
150
+ - Gemfile
151
+ - Gemfile.lock
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - app/public/style.css
156
+ - app/views/iam_limit_exceeded_error.erb
157
+ - app/views/index.erb
158
+ - app/views/keys.erb
159
+ - app/views/layout.erb
160
+ - app/views/new_key.erb
161
+ - app/views/no_user_error.erb
162
+ - bin/console
163
+ - bin/setup
164
+ - config.ru
165
+ - exe/himeko-clean-roles
166
+ - himeko.gemspec
167
+ - lib/himeko.rb
168
+ - lib/himeko/app.rb
169
+ - lib/himeko/role_manager.rb
170
+ - lib/himeko/user_mimicking_role.rb
171
+ - lib/himeko/version.rb
172
+ homepage: https://github.com/sorah/himeko
173
+ licenses:
174
+ - MIT
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.7.7
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: AWS IAM access key self service & management console federated login
196
+ test_files: []