himeko 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/Rakefile +6 -0
- data/app/public/style.css +257 -0
- data/app/views/iam_limit_exceeded_error.erb +7 -0
- data/app/views/index.erb +52 -0
- data/app/views/keys.erb +44 -0
- data/app/views/layout.erb +45 -0
- data/app/views/new_key.erb +35 -0
- data/app/views/no_user_error.erb +5 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +96 -0
- data/exe/himeko-clean-roles +18 -0
- data/himeko.gemspec +34 -0
- data/lib/himeko.rb +2 -0
- data/lib/himeko/app.rb +177 -0
- data/lib/himeko/role_manager.rb +106 -0
- data/lib/himeko/user_mimicking_role.rb +148 -0
- data/lib/himeko/version.rb +3 -0
- metadata +196 -0
data/app/views/index.erb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
<div class='access-console-form'>
|
3
|
+
<form action='/console' method='POST'>
|
4
|
+
<div class='access-console-form-auto access-console-form-auto-enabled'>
|
5
|
+
<p>Taking you to console in <span class='access-console-form-auto-sec'>4</span> seconds... <a class='access-console-form-auto-cancel' href='#'>Stop</a></p>
|
6
|
+
</div>
|
7
|
+
<p><button type='submit' class='btn-primary'>Access to Console</button></p>
|
8
|
+
<p><input type='checkbox' name='recreate' value='1' id='recreate'><label for='recreate'>Reinitiailze</label></p>
|
9
|
+
<p><small>Choose "Reinitiailze" when you have a change in your IAM permissions</small></p>
|
10
|
+
</form>
|
11
|
+
<div class='access-console-form-loading'>
|
12
|
+
<p>Logging into console... (This could take up to 10-20 seconds)</p>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<%== conf.dig(:custom_html, :index) %>
|
17
|
+
|
18
|
+
<script>
|
19
|
+
"use strict";
|
20
|
+
document.addEventListener("DOMContentLoaded", () => {
|
21
|
+
document.body.addEventListener('click', () => {
|
22
|
+
document.body.querySelectorAll('.access-console-form-auto-enabled').forEach((elem) => {
|
23
|
+
elem.classList.remove('access-console-form-auto-enabled');
|
24
|
+
});
|
25
|
+
});
|
26
|
+
document.querySelectorAll('.access-console-form').forEach((elem) => {
|
27
|
+
elem.querySelector('form').addEventListener('submit', (e) => {
|
28
|
+
elem.classList.add('access-console-form-submitted');
|
29
|
+
});
|
30
|
+
elem.querySelector('.access-console-form-auto-cancel').addEventListener('click', (e) => {
|
31
|
+
elem.querySelector('.access-console-form-auto').classList.remove('access-console-form-auto-enabled');
|
32
|
+
e.preventDefault();
|
33
|
+
});
|
34
|
+
|
35
|
+
const countDown = function() {
|
36
|
+
const secElem = elem.querySelector('.access-console-form-auto-sec');
|
37
|
+
var sec = parseInt(secElem.innerHTML, 10);
|
38
|
+
sec -= 1;
|
39
|
+
secElem.innerHTML = `${sec}`;
|
40
|
+
if (sec < 1) {
|
41
|
+
if (elem.querySelector('.access-console-form-auto-enabled')) {
|
42
|
+
elem.querySelector('form').submit();
|
43
|
+
elem.classList.add('access-console-form-submitted');
|
44
|
+
}
|
45
|
+
} else {
|
46
|
+
setTimeout(countDown, 1000);
|
47
|
+
}
|
48
|
+
};
|
49
|
+
setTimeout(countDown, 1000);
|
50
|
+
});
|
51
|
+
});
|
52
|
+
</script>
|
data/app/views/keys.erb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
<h2>Keys</h2>
|
2
|
+
|
3
|
+
<p>List of IAM access key for <code><%= current_username %></code>:</p>
|
4
|
+
|
5
|
+
<section class='iam-keys'>
|
6
|
+
<%- @keys.each do |key, last_used| -%>
|
7
|
+
<div class='iam-key'>
|
8
|
+
<div class='iam-key-info'>
|
9
|
+
<h3><%= key.access_key_id %></h3>
|
10
|
+
Status: <%= key.status %><br>
|
11
|
+
Since: <%= key.create_date %><br>
|
12
|
+
Last Used: <%= last_used.service_name %> @ <%= last_used.region %> / <%= last_used.last_used_date %>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class='iam-key-actions'>
|
16
|
+
<form action='/keys/<%= key.access_key_id %>' method='POST' onsubmit="return confirm('Are you sure? This cannot be undone.')">
|
17
|
+
<input type='hidden' name='_method' value='DELETE'>
|
18
|
+
<button type='submit' class='btn-danger'>Delete</button>
|
19
|
+
</form>
|
20
|
+
|
21
|
+
<%- if key.status == 'Active' -%>
|
22
|
+
<form action='/keys/<%= key.access_key_id %>/active' method='POST' onsubmit="return confirm('Are you sure?')">
|
23
|
+
<input type='hidden' name='_method' value='DELETE'>
|
24
|
+
<button type='submit' class='btn'>Disable</button>
|
25
|
+
</form>
|
26
|
+
<%- else -%>
|
27
|
+
<form action='/keys/<%= key.access_key_id %>/active' method='POST'>
|
28
|
+
<button type='submit' class='btn'>Activate</button>
|
29
|
+
</form>
|
30
|
+
<%- end -%>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
<%- end -%>
|
34
|
+
</section>
|
35
|
+
|
36
|
+
<%- if @keys.size < 2 -%>
|
37
|
+
<form action='/keys' method='POST'>
|
38
|
+
<button type='submit' class='btn-primary'>Create</button>
|
39
|
+
</form>
|
40
|
+
<%- else -%>
|
41
|
+
<p>IAM user cannot have more than 2 access keys at once. Delete an existing key to create new one; <a href='https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html#reference_iam-limits-entities'>AWS docs</a><p>
|
42
|
+
<%- end -%>
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset='utf-8'>
|
5
|
+
<meta name='viewport' content='width=device-width, minimum-scale=1'>
|
6
|
+
<title>AWS self-service portal | Himeko</title>
|
7
|
+
<link rel='stylesheet' href='/style.css' type="text/css">
|
8
|
+
</head>
|
9
|
+
|
10
|
+
<body>
|
11
|
+
<div class="container">
|
12
|
+
<header class='header'>
|
13
|
+
<h1 class='logo'>
|
14
|
+
<a href='/'>AWS self-service</a>
|
15
|
+
</h1>
|
16
|
+
|
17
|
+
<nav>
|
18
|
+
<a href='/'>Console</a>
|
19
|
+
<a href='/keys'>Access Keys</a>
|
20
|
+
</nav>
|
21
|
+
</header>
|
22
|
+
|
23
|
+
<% notice ||= session.delete(:notice); error ||= session.delete(:error) %>
|
24
|
+
<% if notice %>
|
25
|
+
<div class="notice"><%= notice %></div>
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
<% if error %>
|
29
|
+
<div class="error"><%= error %></div>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<div class="box">
|
33
|
+
<%== yield %>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<footer>
|
37
|
+
<p>Logged in as <%== current_username %></p>
|
38
|
+
<div class="credit">
|
39
|
+
Powered by <a href="https://github.com/sorah/himeko">sorah/himeko</a>
|
40
|
+
</div>
|
41
|
+
</footer>
|
42
|
+
</div>
|
43
|
+
</body>
|
44
|
+
</html>
|
45
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<h2>New IAM Access Key Created</h2>
|
2
|
+
|
3
|
+
<p>New IAM access key has been created for <code><%= @key.user_name %></code>. Note that you won't be able to see the secret key again!</p>
|
4
|
+
|
5
|
+
<%== conf.dig(:custom_html, :new_key_guidance) %>
|
6
|
+
|
7
|
+
<section class='iam-secret-key'>
|
8
|
+
<div>
|
9
|
+
<h4>AWS_ACCESS_KEY_ID</code></h4>
|
10
|
+
<pre class='iam-copyable'><code><%= @key.access_key_id %></code></pre>
|
11
|
+
</div>
|
12
|
+
<div>
|
13
|
+
<h4>AWS_SECRET_ACCESS_KEY</code></h4>
|
14
|
+
<pre class='iam-copyable'><code><%= @key.secret_access_key %></code></pre>
|
15
|
+
</div>
|
16
|
+
</section>
|
17
|
+
|
18
|
+
<script>
|
19
|
+
"use strict";
|
20
|
+
document.addEventListener('DOMContentLoaded', () => {
|
21
|
+
document.querySelectorAll('.iam-copyable > code').forEach((e) => {
|
22
|
+
e.addEventListener('mouseenter', (e) => {
|
23
|
+
const range = document.createRange();
|
24
|
+
range.selectNodeContents(e.target);
|
25
|
+
const selection = window.getSelection();
|
26
|
+
selection.removeAllRanges();
|
27
|
+
selection.addRange(range);
|
28
|
+
});
|
29
|
+
e.addEventListener('mouseleave', (e) => {
|
30
|
+
const selection = window.getSelection();
|
31
|
+
selection.removeAllRanges();
|
32
|
+
});
|
33
|
+
});
|
34
|
+
});
|
35
|
+
</script>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "himeko"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config.ru
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require 'omniauth'
|
5
|
+
|
6
|
+
require 'himeko'
|
7
|
+
|
8
|
+
if ENV['RACK_ENV'] == 'production'
|
9
|
+
raise 'Set $SECRET_KEY_BASE' unless ENV['SECRET_KEY_BASE']
|
10
|
+
end
|
11
|
+
|
12
|
+
dev = ENV.fetch('RACK_ENV', 'development') == 'development'
|
13
|
+
|
14
|
+
use(
|
15
|
+
Rack::Session::Cookie,
|
16
|
+
key: 'himekosess',
|
17
|
+
expire_after: 3600,
|
18
|
+
secure: ENV.fetch('HIMEKO_SECURE_SESSION', ENV['RACK_ENV'] == 'production' ? '1' : nil) == '1',
|
19
|
+
secret: ENV.fetch('SECRET_KEY_BASE', SecureRandom.base64(256)),
|
20
|
+
)
|
21
|
+
|
22
|
+
provider = nil
|
23
|
+
case
|
24
|
+
when ENV['HIMEKO_GITHUB_KEY'] && ENV['HIMEKO_GITHUB_SECRET']
|
25
|
+
require 'omniauth-github'
|
26
|
+
gh_client_options = {}
|
27
|
+
if ENV['HIMEKO_GITHUB_HOST']
|
28
|
+
gh_client_options[:site] = "#{ENV['HIMEKO_GITHUB_HOST']}/api/v3"
|
29
|
+
gh_client_options[:authorize_url] = "#{ENV['HIMEKO_GITHUB_HOST']}/login/oauth/authorize"
|
30
|
+
gh_client_options[:token_url] = "#{ENV['HIMEKO_GITHUB_HOST']}/login/oauth/access_token"
|
31
|
+
end
|
32
|
+
|
33
|
+
gh_scope = ''
|
34
|
+
if ENV['HIMEKO_GITHUB_TEAMS']
|
35
|
+
gh_scope = 'read:org'
|
36
|
+
end
|
37
|
+
|
38
|
+
use OmniAuth::Builder do
|
39
|
+
provider(:github, ENV['HIMEKO_GITHUB_KEY'], ENV['HIMEKO_GITHUB_SECRET'], client_options: gh_client_options, scope: gh_scope)
|
40
|
+
end
|
41
|
+
provider = :github
|
42
|
+
when ENV['HIMEKO_GOOGLE_KEY'] && ENV['HIMEKO_GOOGLE_SECRET']
|
43
|
+
require 'omniauth-google-oauth2'
|
44
|
+
use OmniAuth::Builder do
|
45
|
+
provider(:google_oauth2, ENV['HIMEKO_GOOGLE_KEY'], ENV['HIMEKO_GOOGLE_SECRET'], hd: ENV['HIMEKO_GOOGLE_HD'])
|
46
|
+
end
|
47
|
+
provider = :google_oauth2
|
48
|
+
when dev
|
49
|
+
use OmniAuth::Builder do
|
50
|
+
provider(:developer, fields: %i(uid), uid_field: :uid)
|
51
|
+
end
|
52
|
+
provider = :developer
|
53
|
+
end
|
54
|
+
|
55
|
+
use(Class.new do
|
56
|
+
def initialize(app, provider)
|
57
|
+
@app = app
|
58
|
+
@provider = provider
|
59
|
+
end
|
60
|
+
|
61
|
+
def call(env)
|
62
|
+
return process_callback(env) if env['omniauth.auth']
|
63
|
+
session = env.fetch('rack.session')
|
64
|
+
|
65
|
+
user = env['himeko.user'] = session[:user]
|
66
|
+
unless user
|
67
|
+
session[:back_to] ||= env['PATH_INFO']
|
68
|
+
return [302, {'Location' => "/auth/#{@provider}"}, []]
|
69
|
+
end
|
70
|
+
|
71
|
+
@app.call env
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_callback(env)
|
75
|
+
session = env.fetch('rack.session')
|
76
|
+
auth = env.fetch('omniauth.auth')
|
77
|
+
case auth.fetch(:provider)
|
78
|
+
when 'github'
|
79
|
+
session[:user] = auth.fetch(:info).fetch(:nickname)
|
80
|
+
when 'google_oauth2'
|
81
|
+
session[:user] = auth.fetch(:info).fetch(:email).split(?@,2)[0]
|
82
|
+
when 'developer'
|
83
|
+
session[:user] = auth.fetch(:uid)
|
84
|
+
end
|
85
|
+
return [302, {'Location' => session.delete(:back_to) || '/'}, []]
|
86
|
+
end
|
87
|
+
end, provider)
|
88
|
+
|
89
|
+
config = {
|
90
|
+
role_path: ENV.fetch('HIMEKO_ROLE_PATH', '/user-role/'),
|
91
|
+
role_prefix: ENV.fetch('HIMEKO_ROLE_PREFIX', 'user_'),
|
92
|
+
dynamodb_table_name: ENV.fetch('HIMEKO_DYNAMODB_TABLE'),
|
93
|
+
session_duration: ENV.fetch('HIMEKO_SESSION_DURATION', 3600).to_i,
|
94
|
+
}
|
95
|
+
|
96
|
+
run Himeko.app(config)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'himeko'
|
3
|
+
require 'logger'
|
4
|
+
require 'aws-sdk-dynamodb'
|
5
|
+
require 'aws-sdk-iam'
|
6
|
+
|
7
|
+
config = {
|
8
|
+
role_path: ENV.fetch('HIMEKO_ROLE_PATH', '/user-role/'),
|
9
|
+
role_prefix: ENV.fetch('HIMEKO_ROLE_PREFIX', 'user_'),
|
10
|
+
dynamodb_table_name: ENV.fetch('HIMEKO_DYNAMODB_TABLE', 'himeko-staging'),
|
11
|
+
}
|
12
|
+
|
13
|
+
Himeko::RoleManager.new(
|
14
|
+
iam: Aws::IAM::Client.new(logger: Logger.new($stdout)),
|
15
|
+
path: config[:role_path],
|
16
|
+
prefix: config[:role_prefix],
|
17
|
+
dynamodb_table: Aws::DynamoDB::Resource.new(logger: Logger.new($stdout)).table(config[:dynamodb_table_name]),
|
18
|
+
).prune
|
data/himeko.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "himeko/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "himeko"
|
8
|
+
spec.version = Himeko::VERSION
|
9
|
+
spec.authors = ["Sorah Fukumori"]
|
10
|
+
spec.email = ["sorah@cookpad.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{AWS IAM access key self service & management console federated login}
|
13
|
+
spec.homepage = "https://github.com/sorah/himeko"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "aws-sdk-core" # aws-sdk-sts
|
24
|
+
spec.add_dependency "aws-sdk-iam"
|
25
|
+
spec.add_dependency "aws-sdk-dynamodb"
|
26
|
+
|
27
|
+
spec.add_dependency "sinatra"
|
28
|
+
spec.add_dependency "rack-protection"
|
29
|
+
spec.add_dependency "erubi"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler"
|
32
|
+
spec.add_development_dependency "rake"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
end
|
data/lib/himeko.rb
ADDED
data/lib/himeko/app.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
require 'erubi'
|
5
|
+
require 'sinatra/base'
|
6
|
+
require 'rack/protection'
|
7
|
+
|
8
|
+
require 'aws-sdk-core' # sts
|
9
|
+
require 'aws-sdk-iam'
|
10
|
+
require 'aws-sdk-dynamodb'
|
11
|
+
|
12
|
+
require 'himeko/role_manager'
|
13
|
+
|
14
|
+
module Himeko
|
15
|
+
def self.app(*args)
|
16
|
+
App.rack(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
class App < Sinatra::Base
|
20
|
+
CONTEXT_RACK_ENV_NAME = 'himeko.ctx'
|
21
|
+
USER_RACK_ENV_NAME = 'himeko.user'
|
22
|
+
|
23
|
+
def self.initialize_context(config)
|
24
|
+
{
|
25
|
+
config: config,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.rack(config={})
|
30
|
+
klass = App
|
31
|
+
|
32
|
+
context = initialize_context(config)
|
33
|
+
lambda { |env|
|
34
|
+
env[CONTEXT_RACK_ENV_NAME] = context
|
35
|
+
klass.call(env)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
configure do
|
40
|
+
enable :logging
|
41
|
+
end
|
42
|
+
|
43
|
+
set :root, File.expand_path(File.join(__dir__, '..', '..', 'app'))
|
44
|
+
set :erb, escape_html: true
|
45
|
+
|
46
|
+
use Rack::MethodOverride
|
47
|
+
use Rack::Protection
|
48
|
+
|
49
|
+
helpers do
|
50
|
+
def context
|
51
|
+
request.env[CONTEXT_RACK_ENV_NAME]
|
52
|
+
end
|
53
|
+
|
54
|
+
def conf
|
55
|
+
context[:config]
|
56
|
+
end
|
57
|
+
|
58
|
+
def current_username
|
59
|
+
name = request.env[USER_RACK_ENV_NAME]
|
60
|
+
halt 401, "request.env[#{USER_RACK_ENV_NAME}] is missing (maybe a configuration bug!)" unless name
|
61
|
+
name
|
62
|
+
end
|
63
|
+
|
64
|
+
def sts
|
65
|
+
@sts ||= context[:sts] ||= conf[:sts] || Aws::STS::Client.new(logger: env['rack.logger'])
|
66
|
+
end
|
67
|
+
|
68
|
+
def iam
|
69
|
+
@iam ||= context[:iam] ||= conf[:iam] || Aws::IAM::Client.new(logger: env['rack.logger'])
|
70
|
+
end
|
71
|
+
|
72
|
+
def dynamodb_table
|
73
|
+
@dynamodb_table ||= context[:dynamodb_table] ||= conf[:dynamodb_table] ||= Aws::DynamoDB::Resource.new().table(conf.fetch(:dynamodb_table_name))
|
74
|
+
end
|
75
|
+
|
76
|
+
def role_manager
|
77
|
+
@role_manager ||= context[:role_manager] ||= RoleManager.new(
|
78
|
+
iam: iam,
|
79
|
+
prefix: conf.fetch(:role_prefix),
|
80
|
+
path: conf.fetch(:role_path),
|
81
|
+
ttl: conf.fetch(:role_ttl, 86400),
|
82
|
+
dynamodb_table: dynamodb_table,
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def console_session_duration
|
87
|
+
conf.fetch(:session_duration, 3600)
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_no_user_error
|
91
|
+
status 403
|
92
|
+
erb :no_user_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
get '/' do
|
97
|
+
erb :index
|
98
|
+
end
|
99
|
+
|
100
|
+
post '/console' do
|
101
|
+
recreate = params[:recreate] == '1'
|
102
|
+
begin
|
103
|
+
arn = role_manager.fetch(current_username, recreate: recreate)
|
104
|
+
rescue Aws::IAM::Errors::LimitExceeded => e
|
105
|
+
@iam_error = e
|
106
|
+
status 400
|
107
|
+
return erb :iam_limit_exceeded_error
|
108
|
+
end
|
109
|
+
|
110
|
+
retries = 0
|
111
|
+
resp = nil
|
112
|
+
begin
|
113
|
+
resp = sts.assume_role(
|
114
|
+
duration_seconds: console_session_duration,
|
115
|
+
role_arn: arn,
|
116
|
+
role_session_name: current_username,
|
117
|
+
)
|
118
|
+
rescue Aws::STS::Errors::AccessDenied
|
119
|
+
raise if retries > 5
|
120
|
+
sleep 1 + (1.1**retries)
|
121
|
+
retries += 1
|
122
|
+
retry
|
123
|
+
end
|
124
|
+
json = {sessionId: resp.credentials.access_key_id, sessionKey: resp.credentials.secret_access_key, sessionToken: resp.credentials.session_token}.to_json
|
125
|
+
signin_token = JSON.parse(open("https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=#{URI.encode_www_form_component(json)}", 'r', &:read))
|
126
|
+
|
127
|
+
url = "https://signin.aws.amazon.com/federation?Action=login&Issuer=#{URI.encode_www_form_component(request.base_url)}&Destination=#{URI.encode_www_form_component(params[:relay] || 'https://console.aws.amazon.com/console/home')}&SigninToken=#{signin_token.fetch("SigninToken")}"
|
128
|
+
|
129
|
+
redirect url
|
130
|
+
end
|
131
|
+
|
132
|
+
get '/keys' do
|
133
|
+
@keys = iam.list_access_keys(user_name: current_username)
|
134
|
+
.access_key_metadata.map do |key_data|
|
135
|
+
[
|
136
|
+
key_data,
|
137
|
+
iam.get_access_key_last_used(access_key_id: key_data.access_key_id).access_key_last_used,
|
138
|
+
]
|
139
|
+
end
|
140
|
+
erb :keys
|
141
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
142
|
+
return render_no_user_error()
|
143
|
+
end
|
144
|
+
|
145
|
+
post '/keys' do
|
146
|
+
#\@key = Struct.new(:user_name, :access_key_id, :secret_access_key).new('foobar', 'DUMMY123', 'secret+secret')
|
147
|
+
@key = iam.create_access_key(user_name: current_username).access_key
|
148
|
+
erb :new_key
|
149
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
150
|
+
return render_no_user_error()
|
151
|
+
end
|
152
|
+
|
153
|
+
delete '/keys/:id' do
|
154
|
+
iam.delete_access_key(access_key_id: params[:id], user_name: current_username)
|
155
|
+
session[:notice] = "Access key #{params[:id]} has been deleted."
|
156
|
+
redirect '/keys'
|
157
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
158
|
+
halt 404, 'NoSuchEntity'
|
159
|
+
end
|
160
|
+
|
161
|
+
post '/keys/:id/active' do
|
162
|
+
iam.update_access_key(access_key_id: params[:id], user_name: current_username, status: 'Active')
|
163
|
+
session[:notice] = "Access key #{params[:id]} has been activated."
|
164
|
+
redirect '/keys'
|
165
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
166
|
+
halt 404, 'NoSuchEntity'
|
167
|
+
end
|
168
|
+
|
169
|
+
delete '/keys/:id/active' do
|
170
|
+
iam.update_access_key(access_key_id: params[:id], user_name: current_username, status: 'Inactive')
|
171
|
+
session[:notice] = "Access key #{params[:id]} has been deactivated."
|
172
|
+
redirect '/keys'
|
173
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
174
|
+
halt 404, 'NoSuchEntity'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|