aws_assume_role 0.0.2
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 +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +37 -0
- data/Gemfile +18 -0
- data/LICENSE.md +19 -0
- data/README.md +143 -0
- data/Rakefile +2 -0
- data/aws_assume_role.gemspec +32 -0
- data/bin/aws-assume-role +66 -0
- data/bin/test.rb +39 -0
- data/lib/aws_assume_role.rb +3 -0
- data/lib/aws_assume_role/credentials.rb +88 -0
- data/lib/aws_assume_role/logging.rb +36 -0
- data/lib/aws_assume_role/profile.rb +183 -0
- data/lib/aws_assume_role/profile/assume_role.rb +117 -0
- data/lib/aws_assume_role/profile/basic.rb +92 -0
- data/lib/aws_assume_role/profile/list.rb +57 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 199475f5c68d701119ec803d1af1e677583341c1
|
4
|
+
data.tar.gz: 925d022c466217e38ae89c9345607963156de362
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6d898f41a279403a627b7b16b80747c76402e643af7fcd22311898e116afa96747b22345938430d4621d1d1663f3eda67e237027b354f60806d497258f0b1ebc
|
7
|
+
data.tar.gz: ca9f4c76a16552e30bbd94f66997675b99c60e1cea48fb0faca796bbf2e57973b1e533f5f07a5dd3865269a8ffa77200f25819555bd64fe3c7b5f42b19bdb1e4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
DisplayCopNames: true
|
4
|
+
|
5
|
+
Metrics/MethodLength:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Style/IndentationWidth:
|
9
|
+
Width: 4
|
10
|
+
|
11
|
+
Style/TrailingCommaInArguments:
|
12
|
+
EnforcedStyleForMultiline: comma
|
13
|
+
|
14
|
+
Style/TrailingCommaInLiteral:
|
15
|
+
EnforcedStyleForMultiline: comma
|
16
|
+
|
17
|
+
Style/EmptyLinesAroundModuleBody:
|
18
|
+
EnforcedStyle: empty_lines
|
19
|
+
|
20
|
+
Style/EmptyLinesAroundClassBody:
|
21
|
+
EnforcedStyle: empty_lines
|
22
|
+
|
23
|
+
Style/EmptyLinesAroundMethodBody:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/EmptyLinesAroundBlockBody:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/ClassLength:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Metrics/AbcSize:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Metrics/PerceivedComplexity:
|
36
|
+
Enabled: false
|
37
|
+
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
source 'https://rubygems.org'
|
3
|
+
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
case Gem::Platform.local.os
|
7
|
+
when 'linux'
|
8
|
+
gem 'gir_ffi-gnome_keyring', '~> 0.0.3'
|
9
|
+
when 'darwin'
|
10
|
+
gem 'ruby-keychain', '~> 0.3.2'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Development dependencies - didn't seem to get installed when
|
14
|
+
# referenced in the gemspec, find out why.
|
15
|
+
gem 'bundler'
|
16
|
+
gem 'rake'
|
17
|
+
gem 'rspec'
|
18
|
+
gem 'rubocop', require: false
|
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2016 The Scale Factory Limited
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
6
|
+
the Software without restriction, including without limitation the rights to
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# aws-assume-role
|
2
|
+
|
3
|
+
This will get role credentials for you, managing 2FA devices, and set those
|
4
|
+
credentials in environments. It stores the fetched credentials in Gnome Keyring
|
5
|
+
or OSX Keychain so they are not readable from disk.
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
`gem install aws_assume_role`
|
10
|
+
|
11
|
+
### Platform notes
|
12
|
+
|
13
|
+
Gnome Keyring uses the [GirFFI](https://github.com/mvz/gir_ffi) bindings, which
|
14
|
+
requires the introspection bindings to be installed (as well as gnome-keyring).
|
15
|
+
`apt-get install gnome-keyring libgirepository1.0-dev` for Debian/Ubuntu.
|
16
|
+
|
17
|
+
## Config file
|
18
|
+
|
19
|
+
Create a config file, the default is `~/.aws/assume.yaml`
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
---
|
23
|
+
default:
|
24
|
+
set_environment: false
|
25
|
+
# credentials come from .aws/credentials default profile or environment
|
26
|
+
|
27
|
+
scalefactory:
|
28
|
+
# if this profile is passed don't set the environment credentials
|
29
|
+
set_environment: false
|
30
|
+
# load credentials from sf_sso profile in .aws/credentials
|
31
|
+
profile: sf_sso
|
32
|
+
|
33
|
+
|
34
|
+
# These use the scalefactory profile above
|
35
|
+
xx_mgmt:
|
36
|
+
parent: scalefactory
|
37
|
+
set_environment: true
|
38
|
+
type: assume_role
|
39
|
+
region: eu-west-1
|
40
|
+
role_arn: arn:aws:iam::123456789012:role/RoleNameHere
|
41
|
+
|
42
|
+
xx_test:
|
43
|
+
parent: scalefactory
|
44
|
+
set_environment: true
|
45
|
+
type: assume_role
|
46
|
+
role_arn: arn:aws:iam::123456789012:role/RoleNameHere
|
47
|
+
|
48
|
+
xx:
|
49
|
+
type: list
|
50
|
+
set_environment: true
|
51
|
+
list:
|
52
|
+
- name: xx_test
|
53
|
+
env_prefix: TEST_
|
54
|
+
- name: xx_mgmt
|
55
|
+
env_prefix: MGMT_
|
56
|
+
|
57
|
+
|
58
|
+
# These use the default above
|
59
|
+
yy_mgmt:
|
60
|
+
set_environment: true
|
61
|
+
type: assume_role
|
62
|
+
role_arn: arn:aws:iam::123456789012:role/RoleNameHere
|
63
|
+
|
64
|
+
yy_test:
|
65
|
+
set_environment: true
|
66
|
+
type: assume_role
|
67
|
+
role_arn: arn:aws:iam::123456789012:role/RoleNameHere
|
68
|
+
|
69
|
+
xx:
|
70
|
+
type: list
|
71
|
+
set_environment: true
|
72
|
+
list:
|
73
|
+
- name: xx_test
|
74
|
+
env_prefix: TEST_
|
75
|
+
- name: xx_mgmt
|
76
|
+
env_prefix: MGMT_
|
77
|
+
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
## How to use?
|
85
|
+
|
86
|
+
### In Environment variable
|
87
|
+
|
88
|
+
```
|
89
|
+
export AWS_ACCESS_KEY_ID=1234567890010
|
90
|
+
export AWS_SECRET_ACCESS_KEY=abcdefghijklmnopqrstuvwzyx1
|
91
|
+
export AWS_DEFAULT_REGION=eu-west-1
|
92
|
+
```
|
93
|
+
|
94
|
+
Then run the `aws-assume-role` command.
|
95
|
+
|
96
|
+
### in credentials file
|
97
|
+
|
98
|
+
I have the following entry in `~/.aws/credentials`:
|
99
|
+
|
100
|
+
```
|
101
|
+
[sf_sso]
|
102
|
+
aws_access_key_id = 1234567890010
|
103
|
+
aws_secret_access_key = abcdefghijklmnopqrstuvwzyx1
|
104
|
+
region = eu-west-1
|
105
|
+
```
|
106
|
+
|
107
|
+
### Environment Variables Set
|
108
|
+
|
109
|
+
If `region` is defined for an `assume_role` type the environment variable
|
110
|
+
`AWS_DEFAULT_REGION` will be set with that value, otherwise it will be left
|
111
|
+
blank.
|
112
|
+
|
113
|
+
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` will all
|
114
|
+
be set.
|
115
|
+
|
116
|
+
If a type of `list` is called the environment variables will all be set with the
|
117
|
+
provided prefix, e.g.
|
118
|
+
|
119
|
+
```
|
120
|
+
MGMT_AWS_ACCESS_KEY_ID=122343534535435435
|
121
|
+
MGMT_AWS_DEFAULT_REGION=eu-west-1
|
122
|
+
MGMT_AWS_SECRET_ACCESS_KEY=+4324234234235454353535353535
|
123
|
+
MGMT_AWS_SESSION_TOKEN=F353453535345345345345345353534
|
124
|
+
TEST_AWS_ACCESS_KEY_ID=53454353453453453534
|
125
|
+
TEST_AWS_SECRET_ACCESS_KEY=3534534534534534534534
|
126
|
+
TEST_AWS_SESSION_TOKEN=5435345353453
|
127
|
+
```
|
128
|
+
|
129
|
+
If you use the `-v` or `--verbose` flag it will print out any AWS environment
|
130
|
+
variables set at the end of the action.
|
131
|
+
|
132
|
+
### Calling another application
|
133
|
+
|
134
|
+
You can call another application by passing a bare double dash followed by the
|
135
|
+
target command.
|
136
|
+
|
137
|
+
```
|
138
|
+
aws-assume-role --profile yy_mgmt -- aws ec2 describe-instances --query "Reservations[*].Instances[*].PrivateIpAddress" --output=text
|
139
|
+
10.254.4.20
|
140
|
+
10.254.4.15
|
141
|
+
10.254.0.10
|
142
|
+
10.254.4.5
|
143
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'aws_assume_role'
|
7
|
+
spec.version = '0.0.2'
|
8
|
+
spec.authors = ['Jon Topper', 'Jack Thomas']
|
9
|
+
spec.email = ['jon@scalefactory.com', 'jack@scalefactory.com']
|
10
|
+
|
11
|
+
spec.description = 'Used to fetch multiple AWS Role Credential '\
|
12
|
+
'Keys using different Session Keys '\
|
13
|
+
'and store them securely using Gnome Keyring '\
|
14
|
+
'or OSX keychain'
|
15
|
+
spec.summary = 'Manage AWS STS credentials with MFA'
|
16
|
+
spec.homepage = 'https://github.com/scalefactory/aws_assume_role'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f|
|
20
|
+
f.match(%r{^(test|spec|features)/})
|
21
|
+
}
|
22
|
+
spec.bindir = 'bin'
|
23
|
+
spec.executables = spec.files.grep(%r{^bin/aws}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'aws-sdk'
|
27
|
+
spec.add_runtime_dependency 'inifile'
|
28
|
+
spec.add_runtime_dependency 'keyring', '~> 0.4.1'
|
29
|
+
|
30
|
+
# spec.add_development_dependency 'bundler', '~> 1.12'
|
31
|
+
# spec.add_development_dependency 'rake', '~> 10.0'
|
32
|
+
end
|
data/bin/aws-assume-role
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib/', __FILE__)
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'aws_assume_role'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
optparse = OptionParser.new do |opts|
|
10
|
+
|
11
|
+
options[:config] = "#{ENV['HOME']}/.aws/assume.yaml"
|
12
|
+
opts.on('-c', '--config file', 'Config file. defaults to' \
|
13
|
+
'~/.aws/assume.yaml.') do |c|
|
14
|
+
options[:config] = c
|
15
|
+
end
|
16
|
+
|
17
|
+
options[:profile] = false
|
18
|
+
opts.on('--profile name', 'Load the credentials for a profile into AWS ' \
|
19
|
+
'environment variables. If this is not specified the ') do |c|
|
20
|
+
options[:profile] = c
|
21
|
+
end
|
22
|
+
|
23
|
+
options[:debug] = false
|
24
|
+
opts.on('-d', '--debug', 'Enable debugging') do
|
25
|
+
options[:debug] = true
|
26
|
+
end
|
27
|
+
|
28
|
+
options[:verbose] = false
|
29
|
+
opts.on('-v', '--verbose', 'Get more words') do
|
30
|
+
options[:verbose] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
optdata = optparse.parse!
|
36
|
+
|
37
|
+
if options[:debug]
|
38
|
+
AWSAssumeRole::Profile.logger.level = Logger::DEBUG
|
39
|
+
else
|
40
|
+
AWSAssumeRole::Profile.logger.level = Logger::WARN
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
begin
|
45
|
+
AWSAssumeRole::Profile.load_profiles
|
46
|
+
AWSAssumeRole::Profile.load_config_file(options[:config])
|
47
|
+
rescue Errno::ENOENT
|
48
|
+
puts "No config file at options[:config]. Please create one!"
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
if options[:profile] != false
|
54
|
+
profile = AWSAssumeRole::Profile.get_by_name(options[:profile] )
|
55
|
+
profile.use
|
56
|
+
|
57
|
+
if options[:verbose]
|
58
|
+
system('env | grep "AWS" | sort')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
unless optdata.empty?
|
63
|
+
cmd = optdata.join(' ')
|
64
|
+
AWSAssumeRole::Profile.logger.debug "Executing Command '#{cmd}'"
|
65
|
+
system(cmd)
|
66
|
+
end
|
data/bin/test.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib/', __FILE__)
|
4
|
+
|
5
|
+
require 'aws_assume_role'
|
6
|
+
|
7
|
+
test_profiles_yaml = <<EOF
|
8
|
+
---
|
9
|
+
default:
|
10
|
+
set_environment: false
|
11
|
+
# credentials come from .aws/credentials or environment
|
12
|
+
|
13
|
+
mgmt:
|
14
|
+
set_environment: true
|
15
|
+
type: assume_role
|
16
|
+
role_arn: arn:aws:iam::339253004131:role/TerraformUser
|
17
|
+
|
18
|
+
test:
|
19
|
+
set_environment: true
|
20
|
+
type: assume_role
|
21
|
+
role_arn: arn:aws:iam::542043528869:role/TerraformUser
|
22
|
+
|
23
|
+
tf_test:
|
24
|
+
type: list
|
25
|
+
list:
|
26
|
+
- name: test
|
27
|
+
env_prefix: TEST_
|
28
|
+
- name: mgmt
|
29
|
+
env_prefix: MGMT_
|
30
|
+
|
31
|
+
EOF
|
32
|
+
|
33
|
+
AWSAssumeRole::Profile.logger.level = Logger::DEBUG
|
34
|
+
AWSAssumeRole::Profile.parse_config(test_profiles_yaml)
|
35
|
+
|
36
|
+
p = AWSAssumeRole::Profile.get_by_name('tf_test')
|
37
|
+
p.use
|
38
|
+
|
39
|
+
system('env | grep "AWS" | sort')
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# AWSAssumeRole
|
2
|
+
module AWSAssumeRole
|
3
|
+
|
4
|
+
require 'keyring'
|
5
|
+
require 'json'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
# Represents credentials, used for serialising into keychain
|
9
|
+
class Credentials
|
10
|
+
|
11
|
+
include Logging
|
12
|
+
|
13
|
+
def self.load_from_keyring(key)
|
14
|
+
|
15
|
+
logger.debug("Keyring: load '#{key}'")
|
16
|
+
|
17
|
+
keyring = Keyring.new
|
18
|
+
json_session = keyring.get_password('AWSAssumeRole', key)
|
19
|
+
|
20
|
+
unless json_session
|
21
|
+
logger.info('No JSON session data in keyring')
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
|
25
|
+
hash = JSON.parse(json_session, symbolize_names: true)
|
26
|
+
|
27
|
+
unless hash
|
28
|
+
logger.info('Couldn\'t parse keyring data as JSON')
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
32
|
+
hash[:expiration] = Time.parse(hash[:expiration])
|
33
|
+
|
34
|
+
logger.debug("Loaded #{hash}")
|
35
|
+
AWSAssumeRole::Credentials.new(hash)
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create_from_sdk(object)
|
40
|
+
|
41
|
+
raise TypeError unless object.is_a?(Aws::STS::Types::Credentials)
|
42
|
+
AWSAssumeRole::Credentials.new(object.to_h)
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
@credentials = nil
|
47
|
+
|
48
|
+
def initialize(hash)
|
49
|
+
@credentials = hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def secret_access_key
|
53
|
+
@credentials[:secret_access_key]
|
54
|
+
end
|
55
|
+
|
56
|
+
def access_key_id
|
57
|
+
@credentials[:access_key_id]
|
58
|
+
end
|
59
|
+
|
60
|
+
def session_token
|
61
|
+
@credentials[:session_token]
|
62
|
+
end
|
63
|
+
|
64
|
+
def expiration
|
65
|
+
@credentials[:expiration]
|
66
|
+
end
|
67
|
+
|
68
|
+
def store_in_keyring(key)
|
69
|
+
keyring = Keyring.new
|
70
|
+
logger.debug("Keyring: store '#{key}' with #{@credentials.to_json}")
|
71
|
+
keyring.set_password('AWSAssumeRole', key, @credentials.to_json)
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_from_keyring(key)
|
75
|
+
keyring = Keyring.new
|
76
|
+
logger.debug("Keyring: delete '#{key}'")
|
77
|
+
keyring.delete_password('AWSAssumeRole', key)
|
78
|
+
end
|
79
|
+
|
80
|
+
def expired?
|
81
|
+
logger.debug("Checking expiry: #{@credentials[:expiration]} "\
|
82
|
+
'<= Time.now')
|
83
|
+
@credentials[:expiration] <= Time.now
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Mixin to provide global logging object
|
2
|
+
module AWSAssumeRole
|
3
|
+
|
4
|
+
module Logging
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def logger
|
11
|
+
@logger ||= Logger.new($stderr)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_writer :logger
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
|
20
|
+
class << base
|
21
|
+
|
22
|
+
def logger # rubocop:disable Lint/NestedMethodDefinition
|
23
|
+
Logging.logger
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def logger
|
31
|
+
Logging.logger
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'keyring'
|
2
|
+
|
3
|
+
module AWSAssumeRole
|
4
|
+
|
5
|
+
# Base Profile superclass
|
6
|
+
class Profile
|
7
|
+
|
8
|
+
include Logging
|
9
|
+
|
10
|
+
# Class methods for dispatch to individual Profile strategy
|
11
|
+
|
12
|
+
@implementations = {}
|
13
|
+
@named_profiles = {}
|
14
|
+
@config_file = '-'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
attr_accessor :implementations
|
19
|
+
attr_accessor :named_profiles
|
20
|
+
attr_accessor :config_file
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.register_implementation(type, impl)
|
25
|
+
logger.info('Registering implementation ' \
|
26
|
+
"for type '#{type}': #{impl}")
|
27
|
+
AWSAssumeRole::Profile.implementations[type] = impl
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.load_profiles
|
31
|
+
Dir.glob(
|
32
|
+
File.expand_path('profile/*.rb', File.dirname(__FILE__)),
|
33
|
+
).each do |profile_class|
|
34
|
+
require profile_class
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create(name, options)
|
39
|
+
|
40
|
+
options['type'] = 'basic' unless options.key?('type')
|
41
|
+
|
42
|
+
if implementations.key?(options['type'])
|
43
|
+
logger.info("Creating profile '#{name}' "\
|
44
|
+
"with type #{options['type']}")
|
45
|
+
i = implementations[options['type']].new(name, options)
|
46
|
+
named_profiles[name] = i
|
47
|
+
return i
|
48
|
+
end
|
49
|
+
|
50
|
+
STDERR.puts 'No implementation for profiles of type '\
|
51
|
+
"'#{options['type']}'"
|
52
|
+
exit -1 # rubocop:disable Lint/AmbiguousOperator
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.get_by_name(name)
|
57
|
+
unless named_profiles.key?(name)
|
58
|
+
STDERR.puts "No profile '#{name}' found"
|
59
|
+
exit -1 # rubocop:disable Lint/AmbiguousOperator
|
60
|
+
end
|
61
|
+
named_profiles[name]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.load_config_file(config_path)
|
65
|
+
@config_file = config_path
|
66
|
+
logger.info("Loading configuration from #{config_path}")
|
67
|
+
parse_config(File.open(config_path))
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.parse_config(yaml)
|
71
|
+
|
72
|
+
require 'yaml'
|
73
|
+
|
74
|
+
profiles = YAML.load(yaml)
|
75
|
+
profiles.each do |name, options|
|
76
|
+
options['config_file'] = config_file
|
77
|
+
options['name'] = name
|
78
|
+
AWSAssumeRole::Profile.create(name, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# Superclass for Profile strategies
|
84
|
+
|
85
|
+
def set_env(prefix = '') # rubocop:disable Style/AccessorMethodName
|
86
|
+
|
87
|
+
logger.info("Setting environment with prefix '#{prefix}'")
|
88
|
+
|
89
|
+
ENV["#{prefix}AWS_ACCESS_KEY_ID"] = access_key_id
|
90
|
+
ENV["#{prefix}AWS_SECRET_ACCESS_KEY"] = secret_access_key
|
91
|
+
ENV["#{prefix}AWS_DEFAULT_REGION"] = region
|
92
|
+
|
93
|
+
return unless respond_to?(:session_token)
|
94
|
+
|
95
|
+
logger.info(':session_token available, setting environment')
|
96
|
+
ENV["#{prefix}AWS_SESSION_TOKEN"] = session_token
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def access_key_id
|
101
|
+
raise NotImplementedError
|
102
|
+
end
|
103
|
+
|
104
|
+
def secret_access_key
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
107
|
+
|
108
|
+
def region
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
def sts_client
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
attr_reader :name
|
117
|
+
|
118
|
+
def use
|
119
|
+
raise NotImplementedError
|
120
|
+
end
|
121
|
+
|
122
|
+
def token_code
|
123
|
+
puts "Enter your MFA's time-based one time password: "
|
124
|
+
token_code = STDIN.gets
|
125
|
+
token_code.chomp!
|
126
|
+
end
|
127
|
+
|
128
|
+
def keyring_key
|
129
|
+
"#{@options['config_file']}|#{@options['name']}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def session(duration = 3600)
|
133
|
+
|
134
|
+
# See if we already have a non-expired session cached in this
|
135
|
+
# object.
|
136
|
+
|
137
|
+
unless @session.nil?
|
138
|
+
|
139
|
+
logger.info('Found session cached in object')
|
140
|
+
return @session unless @session.expired?
|
141
|
+
logger.info('Session expired, deleting keyring key '\
|
142
|
+
"'#{keyring_key}'")
|
143
|
+
@session.delete_from_keyring(keyring_key)
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
# See if there's a non-exipred session cached in the keyring
|
148
|
+
|
149
|
+
@session = AWSAssumeRole::Credentials.load_from_keyring(keyring_key)
|
150
|
+
|
151
|
+
unless @session.nil?
|
152
|
+
|
153
|
+
logger.info("Found session in keyring for '#{keyring_key}'")
|
154
|
+
return @session unless @session.expired?
|
155
|
+
logger.info('Session expired, deleting keyring key '\
|
156
|
+
"'#{keyring_key}'")
|
157
|
+
@session.delete_from_keyring(keyring_key)
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
identity = sts_client.get_caller_identity
|
162
|
+
user_name = identity.arn.split('/')[1]
|
163
|
+
mfa_arn = "arn:aws:iam::#{identity.account}:mfa/#{user_name}"
|
164
|
+
|
165
|
+
mfa_token_code = token_code
|
166
|
+
|
167
|
+
session = sts_client.get_session_token(
|
168
|
+
duration_seconds: duration,
|
169
|
+
serial_number: mfa_arn,
|
170
|
+
token_code: mfa_token_code,
|
171
|
+
)
|
172
|
+
|
173
|
+
@session = Credentials.create_from_sdk(session.credentials)
|
174
|
+
logger.info("Storing session in keyring '#{keyring_key}'")
|
175
|
+
@session.store_in_keyring(keyring_key)
|
176
|
+
|
177
|
+
@session
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# AWSAssumeRole
|
2
|
+
module AWSAssumeRole
|
3
|
+
|
4
|
+
class Profile
|
5
|
+
|
6
|
+
# A Profile implementation for assuming roles using STS
|
7
|
+
class AssumeRole < Profile
|
8
|
+
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
register_implementation('assume_role', self)
|
12
|
+
|
13
|
+
@sts_client = nil
|
14
|
+
@role = nil
|
15
|
+
@options = nil
|
16
|
+
@name = nil
|
17
|
+
|
18
|
+
def default_options
|
19
|
+
{
|
20
|
+
'parent' => 'default',
|
21
|
+
'duration' => 3600,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(name, options = {})
|
26
|
+
|
27
|
+
require 'aws-sdk'
|
28
|
+
|
29
|
+
@options = default_options.merge(options)
|
30
|
+
@name = name
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def sts_client
|
35
|
+
|
36
|
+
return @sts_client unless @sts_client.nil?
|
37
|
+
|
38
|
+
parent = AWSAssumeRole::Profile.get_by_name(@options['parent'])
|
39
|
+
|
40
|
+
@sts_client = Aws::STS::Client.new(
|
41
|
+
access_key_id: parent.session.access_key_id,
|
42
|
+
secret_access_key: parent.session.secret_access_key,
|
43
|
+
session_token: parent.session.session_token,
|
44
|
+
)
|
45
|
+
|
46
|
+
@sts_client
|
47
|
+
|
48
|
+
rescue Aws::Errors::MissingRegionError
|
49
|
+
|
50
|
+
STDERR.puts 'No region was given. \
|
51
|
+
Set one in the credentials file or environment'
|
52
|
+
exit -1 # rubocop:disable Lint/AmbiguousOperator
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def role_credentials
|
57
|
+
|
58
|
+
# Check for non-expired session cached here
|
59
|
+
|
60
|
+
unless @role_credentials.nil?
|
61
|
+
|
62
|
+
return @role_credentials unless @role_credentials.expired?
|
63
|
+
@role_credentials.delete_from_keyring(keyring_key)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# See if here's a non-exipred session in the keyring
|
68
|
+
|
69
|
+
@role_credentials = Credentials.load_from_keyring(keyring_key)
|
70
|
+
|
71
|
+
unless @role_credentials.nil?
|
72
|
+
|
73
|
+
return @role_credentials unless @role_credentials.expired?
|
74
|
+
@role_credentials.delete_from_keyring(keyring_key)
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
role = sts_client.assume_role(
|
79
|
+
role_arn: @options['role_arn'],
|
80
|
+
role_session_name: name, # use something else?
|
81
|
+
duration_seconds: @options['duration'],
|
82
|
+
)
|
83
|
+
|
84
|
+
@role_credentials =
|
85
|
+
Credentials.create_from_sdk(role.credentials)
|
86
|
+
|
87
|
+
@role_credentials.store_in_keyring(keyring_key)
|
88
|
+
|
89
|
+
@role_credentials
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def access_key_id
|
94
|
+
role_credentials.access_key_id
|
95
|
+
end
|
96
|
+
|
97
|
+
def secret_access_key
|
98
|
+
role_credentials.secret_access_key
|
99
|
+
end
|
100
|
+
|
101
|
+
def session_token
|
102
|
+
role_credentials.session_token
|
103
|
+
end
|
104
|
+
|
105
|
+
def region
|
106
|
+
@options['region']
|
107
|
+
end
|
108
|
+
|
109
|
+
def use
|
110
|
+
set_env if @options['set_environment']
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# AWSAssumeRole
|
2
|
+
module AWSAssumeRole
|
3
|
+
|
4
|
+
class Profile
|
5
|
+
|
6
|
+
# Profile implementation which takes credentials from either
|
7
|
+
# passed options, from the environment, or from .aws/credentials
|
8
|
+
# file (per the standard behaviour of Aws::STS::Client)
|
9
|
+
class Basic < Profile
|
10
|
+
|
11
|
+
include Logging
|
12
|
+
|
13
|
+
register_implementation('basic', self)
|
14
|
+
|
15
|
+
@sts_client = nil
|
16
|
+
@options = nil
|
17
|
+
@name = nil
|
18
|
+
|
19
|
+
def initialize(name, options = {})
|
20
|
+
|
21
|
+
require 'aws-sdk'
|
22
|
+
|
23
|
+
@options = options
|
24
|
+
@name = name
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def sts_client
|
29
|
+
|
30
|
+
return @sts_client unless @sts_client.nil?
|
31
|
+
|
32
|
+
if @options.key?('access_key_id') &&
|
33
|
+
@options.key?('secret_access_key')
|
34
|
+
|
35
|
+
if @options.key?('region')
|
36
|
+
|
37
|
+
@sts_client = Aws::STS::Client.new(
|
38
|
+
access_key_id: @options['access_key_id'],
|
39
|
+
secret_access_key: @options['secret_access_key'],
|
40
|
+
region: @options['region'],
|
41
|
+
)
|
42
|
+
|
43
|
+
else
|
44
|
+
|
45
|
+
@sts_client = Aws::STS::Client.new(
|
46
|
+
access_key_id: @options['access_key_id'],
|
47
|
+
secret_access_key: @options['secret_access_key'],
|
48
|
+
)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
elsif @options.key?('profile')
|
53
|
+
|
54
|
+
logger.info("Loading profile #{@options['profile']} from ~/.aws/credentials")
|
55
|
+
# Attempt to load with profile name suplied
|
56
|
+
@sts_client = Aws::STS::Client.new(
|
57
|
+
profile: @options['profile'],
|
58
|
+
)
|
59
|
+
|
60
|
+
else
|
61
|
+
|
62
|
+
@sts_client = Aws::STS::Client.new
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
@sts_client
|
67
|
+
|
68
|
+
rescue Aws::Errors::MissingRegionError
|
69
|
+
|
70
|
+
STDERR.puts 'No region was given. \
|
71
|
+
Set one in the credentials file or environment'
|
72
|
+
exit -1 # rubocop:disable Lint/AmbiguousOperator
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
def access_key_id
|
77
|
+
@options['access_key_id']
|
78
|
+
end
|
79
|
+
|
80
|
+
def secret_access_key
|
81
|
+
@options['secret_access_key']
|
82
|
+
end
|
83
|
+
|
84
|
+
def region
|
85
|
+
@options['region']
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# AWSAssumeRole
|
2
|
+
module AWSAssumeRole
|
3
|
+
|
4
|
+
class Profile
|
5
|
+
|
6
|
+
# A Profile implementation which aggregates other profiles.
|
7
|
+
# Used to setenv for multiple credentials, but with different
|
8
|
+
# prefixed.
|
9
|
+
class List < Profile
|
10
|
+
|
11
|
+
include Logging
|
12
|
+
|
13
|
+
register_implementation('list', self)
|
14
|
+
|
15
|
+
@options = nil
|
16
|
+
@name = nil
|
17
|
+
|
18
|
+
def initialize(name, options)
|
19
|
+
|
20
|
+
# TODO: validate options
|
21
|
+
|
22
|
+
@options = options
|
23
|
+
@name = name
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def use
|
28
|
+
|
29
|
+
@options['list'].each do |i|
|
30
|
+
|
31
|
+
profile = Profile.get_by_name(i['name'])
|
32
|
+
|
33
|
+
next unless @options['set_environment']
|
34
|
+
|
35
|
+
if i['env_prefix']
|
36
|
+
profile.set_env(i['env_prefix'])
|
37
|
+
end
|
38
|
+
|
39
|
+
if i['map_names']
|
40
|
+
i['map_names'].each do |name,env|
|
41
|
+
call = name.to_sym
|
42
|
+
if profile.respond_to?(call)
|
43
|
+
ENV[env] = profile.send(call)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws_assume_role
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jon Topper
|
8
|
+
- Jack Thomas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-12-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: inifile
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: keyring
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.4.1
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.4.1
|
56
|
+
description: Used to fetch multiple AWS Role Credential Keys using different Session
|
57
|
+
Keys and store them securely using Gnome Keyring or OSX keychain
|
58
|
+
email:
|
59
|
+
- jon@scalefactory.com
|
60
|
+
- jack@scalefactory.com
|
61
|
+
executables:
|
62
|
+
- aws-assume-role
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".rspec"
|
68
|
+
- ".rubocop.yml"
|
69
|
+
- Gemfile
|
70
|
+
- LICENSE.md
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- aws_assume_role.gemspec
|
74
|
+
- bin/aws-assume-role
|
75
|
+
- bin/test.rb
|
76
|
+
- lib/aws_assume_role.rb
|
77
|
+
- lib/aws_assume_role/credentials.rb
|
78
|
+
- lib/aws_assume_role/logging.rb
|
79
|
+
- lib/aws_assume_role/profile.rb
|
80
|
+
- lib/aws_assume_role/profile/assume_role.rb
|
81
|
+
- lib/aws_assume_role/profile/basic.rb
|
82
|
+
- lib/aws_assume_role/profile/list.rb
|
83
|
+
homepage: https://github.com/scalefactory/aws_assume_role
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.5.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Manage AWS STS credentials with MFA
|
107
|
+
test_files: []
|