himeko 0.1.0
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 +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
|