aws-session-credentials 0.1.1 → 1.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +5 -2
- data/README.md +49 -23
- data/aws-session-credentials.gemspec +3 -0
- data/lib/aws/session/credentials/cache.rb +31 -0
- data/lib/aws/session/credentials/cli.rb +117 -20
- data/lib/aws/session/credentials/config.rb +13 -77
- data/lib/aws/session/credentials/credential_file.rb +18 -28
- data/lib/aws/session/credentials/file_provider/ini_file_provider.rb +30 -0
- data/lib/aws/session/credentials/file_provider/yaml_file_provider.rb +34 -0
- data/lib/aws/session/credentials/mfa_device/generic_mfa_device.rb +19 -0
- data/lib/aws/session/credentials/mfa_device/yubikey_mfa_device.rb +61 -0
- data/lib/aws/session/credentials/profile.rb +13 -0
- data/lib/aws/session/credentials/profile_storage.rb +41 -0
- data/lib/aws/session/credentials/session_builder.rb +41 -30
- data/lib/aws/session/credentials/session_manager.rb +90 -0
- data/lib/aws/session/credentials/version.rb +1 -1
- data/lib/aws/session/credentials.rb +15 -0
- metadata +54 -4
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZThmODRmYTdkNDdmN2IzNThlMGZjNzVjOTg3MzI1MDhmMzM5NDUwZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OWZmM2I4YjQxM2E1NTVjZjc4YjlkNmE1NGFkZTc3NDUwNGE3Y2ZiMg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTA0MWMzNTY1NjEyODg1NmE3MWQ1MjE4OGU5YjU3YjMwZGVjYzQyNDZhNmY4
|
10
|
+
MWJiMjI3NTMzZGVjMTNkYTI4NjcwZDA0ZTBiZmY3ZGRmODNiMWVhZmU2NzNl
|
11
|
+
NDJjZWI2NmFmMzgyYzkyZGU1YTM3NjQxMDU4YmUzYjQzNjNhOGE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OGFmN2YzZjZmNjhkNjcyMTFlMzkxZTcyOGZlOGY0YmQ3NDcyZjkxOWIzYmUx
|
14
|
+
NWY4YWMyNTFkNTZlM2M4N2ZiOTFiZGMyMzBkYjg1MDlmNWY5YjJmYjNiNzBm
|
15
|
+
OWY1MTMxMDNiOTQ1MWFlM2VkNjY3ODY4ODNhMzM3ZjJhNGQzNWY=
|
data/.travis.yml
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.2.3
|
4
|
-
before_install:
|
3
|
+
- 2.2.3
|
4
|
+
before_install:
|
5
|
+
- gem install bundler -v 1.10.6
|
6
|
+
- sudo apt-get update -qq
|
7
|
+
- sudo apt-get install -y libccid libpcsclite-dev pcscd pcsc-tools
|
5
8
|
deploy:
|
6
9
|
provider: rubygems
|
7
10
|
api_key:
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Aws::Session::Credentials
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/zl4bv/aws-session-credentials.svg)](https://travis-ci.org/zl4bv/aws-session-credentials)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/aws-session-credentials.svg)](https://badge.fury.io/rb/aws-session-credentials)
|
4
5
|
|
5
6
|
Command-line tool to generate AWS session credentials.
|
6
7
|
|
@@ -26,6 +27,8 @@ Or install it yourself as:
|
|
26
27
|
|
27
28
|
## Usage
|
28
29
|
|
30
|
+
### Generating new session credentials
|
31
|
+
|
29
32
|
Example:
|
30
33
|
|
31
34
|
```
|
@@ -41,35 +44,58 @@ Usage:
|
|
41
44
|
aws-session
|
42
45
|
|
43
46
|
Options:
|
44
|
-
|
45
|
-
|
46
|
-
[--region=REGION] # AWS region to connect to
|
47
|
-
[--config-file=CONFIG-FILE] # YAML file to load config from
|
48
|
-
# Default: ~/.aws/credentials.yml
|
49
|
-
[--credential-file=CREDENTIAL-FILE] # INI file to save session credentials to
|
50
|
-
# Default: ~/.aws/credentials
|
51
|
-
[--profile=PROFILE] # Profile that session token will be loaded into
|
52
|
-
# Default: default
|
53
|
-
[--duration=N] # Duration, in seconds, that credentials should remain valid
|
54
|
-
# Default: 1
|
55
|
-
[--mfa-device=MFA-DEVICE] # ARN of MFA device
|
56
|
-
[--mfa-code=MFA-CODE] # Six digit code from MFA device
|
57
|
-
```
|
47
|
+
Usage:
|
48
|
+
aws-session new
|
58
49
|
|
59
|
-
|
50
|
+
Options:
|
51
|
+
[--aws-access-key-id=AWS-ACCESS-KEY-ID] # Access key used to generate session token
|
52
|
+
[--aws-secret-access-key=AWS-SECRET-ACCESS-KEY] # Secret key used to generate session token
|
53
|
+
[--aws-region=AWS-REGION] # AWS region to connect to
|
54
|
+
[--config-file=CONFIG-FILE] # YAML file to load config from
|
55
|
+
# Default: ~/.aws/aws-session-config.yml
|
56
|
+
[--source-profile=SOURCE-PROFILE] # Profile in config file that user credentials will be loaded from
|
57
|
+
# Default: default
|
58
|
+
[--profile=PROFILE] # Profile that session token will be loaded into
|
59
|
+
# Default: default
|
60
|
+
[--duration=N] # Duration, in seconds, that credentials should remain valid
|
61
|
+
[--mfa-device=MFA-DEVICE] # ARN of MFA device
|
62
|
+
[--mfa-code=MFA-CODE] # Six digit code from MFA device
|
63
|
+
[--yubikey-name=YUBIKEY-NAME] # Name of yubikey device
|
64
|
+
# Default: Yubikey
|
65
|
+
[--oath-credential=OATH-CREDENTIAL] # Name of OATH credential
|
66
|
+
```
|
60
67
|
|
61
|
-
|
68
|
+
### Assuming a role
|
62
69
|
|
63
70
|
Example:
|
64
71
|
|
65
|
-
```yaml
|
66
|
-
---
|
67
|
-
aws_access_key_id: AKIAIOSFODNN7EXAMPLE
|
68
|
-
aws_secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
69
|
-
region: ap-southeast-2
|
70
|
-
duration: 86400
|
71
|
-
mfa_device: arn:aws:iam::000000000000:mfa/user.name@example.com
|
72
72
|
```
|
73
|
+
$ aws-session assume-role --role-arn=ROLE_ARN --role-session-name=ROLE_SESSION_NAME
|
74
|
+
```
|
75
|
+
|
76
|
+
### Source profiles
|
77
|
+
|
78
|
+
Instead of specifying all of the options via the command line each time you
|
79
|
+
want to generate new session credentials, you can store the options in a
|
80
|
+
*source profile*.
|
81
|
+
|
82
|
+
```
|
83
|
+
$ aws-session configure
|
84
|
+
Source profile (leave blank for "default"):
|
85
|
+
AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
|
86
|
+
AWS Secret Access Key:
|
87
|
+
AWS region: ap-southeast-2
|
88
|
+
Session duration (in seconds): 86400
|
89
|
+
|
90
|
+
Configure MFA? y
|
91
|
+
MFA device ARN: arn:aws:iam::000000000000:mfa/user.name@example.com
|
92
|
+
|
93
|
+
Configure Yubikey? y
|
94
|
+
OATH credential name: user.name@example.com@accountalias
|
95
|
+
```
|
96
|
+
|
97
|
+
See `aws-session --help configure` for more info. By default, the configuration
|
98
|
+
is stored in `~/.aws/aws-session-config.yml`.
|
73
99
|
|
74
100
|
## Contributing
|
75
101
|
|
@@ -24,7 +24,10 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency 'rspec'
|
25
25
|
spec.add_development_dependency 'rspec-its'
|
26
26
|
|
27
|
+
spec.add_runtime_dependency 'activesupport'
|
27
28
|
spec.add_runtime_dependency 'aws-sdk', '~> 2.1'
|
28
29
|
spec.add_runtime_dependency 'inifile', '~> 3.0'
|
30
|
+
spec.add_runtime_dependency 'smartcard'
|
29
31
|
spec.add_runtime_dependency 'thor', '~> 0.19'
|
32
|
+
spec.add_runtime_dependency 'yubioath'
|
30
33
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
# Holds session credentials
|
5
|
+
class Cache
|
6
|
+
include ProfileStorage
|
7
|
+
include FileProvider::YamlFileProvider
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@path = File.expand_path(options[:path] || default_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_path
|
16
|
+
File.join(%w(~ .aws aws-session-cache.yml))
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash<String,Hash>]
|
20
|
+
def profiles_hash
|
21
|
+
self[:profiles] || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [Hash] hsh
|
25
|
+
def profiles_hash=(hsh)
|
26
|
+
self[:profiles] = hsh
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -6,26 +6,26 @@ module Aws
|
|
6
6
|
module Credentials
|
7
7
|
# Command line interface
|
8
8
|
class Cli < Thor
|
9
|
-
method_option 'access-key-id',
|
9
|
+
method_option 'aws-access-key-id',
|
10
10
|
type: :string,
|
11
11
|
desc: 'Access key used to generate session token',
|
12
12
|
default: nil
|
13
|
-
method_option 'secret-access-key',
|
13
|
+
method_option 'aws-secret-access-key',
|
14
14
|
type: :string,
|
15
15
|
desc: 'Secret key used to generate session token',
|
16
16
|
default: nil
|
17
|
-
method_option 'region',
|
17
|
+
method_option 'aws-region',
|
18
18
|
type: :string,
|
19
19
|
desc: 'AWS region to connect to',
|
20
20
|
default: nil
|
21
21
|
method_option 'config-file',
|
22
22
|
type: :string,
|
23
23
|
desc: 'YAML file to load config from',
|
24
|
-
default: '~/.aws/
|
25
|
-
method_option '
|
24
|
+
default: '~/.aws/aws-session-config.yml'
|
25
|
+
method_option 'source-profile',
|
26
26
|
type: :string,
|
27
|
-
desc: '
|
28
|
-
default: '
|
27
|
+
desc: 'Profile in config file that user credentials will be loaded from',
|
28
|
+
default: 'default'
|
29
29
|
method_option 'profile',
|
30
30
|
type: :string,
|
31
31
|
desc: 'Profile that session token will be loaded into',
|
@@ -33,7 +33,7 @@ module Aws
|
|
33
33
|
method_option 'duration',
|
34
34
|
type: :numeric,
|
35
35
|
desc: 'Duration, in seconds, that credentials should remain valid',
|
36
|
-
default:
|
36
|
+
default: nil
|
37
37
|
method_option 'mfa-device',
|
38
38
|
type: :string,
|
39
39
|
desc: 'ARN of MFA device',
|
@@ -42,21 +42,118 @@ module Aws
|
|
42
42
|
type: :string,
|
43
43
|
desc: 'Six digit code from MFA device',
|
44
44
|
default: nil
|
45
|
+
method_option 'yubikey-name',
|
46
|
+
type: :string,
|
47
|
+
desc: 'Name of yubikey device',
|
48
|
+
default: 'Yubikey'
|
49
|
+
method_option 'oath-credential',
|
50
|
+
type: :string,
|
51
|
+
desc: 'Name of OATH credential',
|
52
|
+
default: nil
|
45
53
|
desc 'new', 'Generates new AWS session credentials'
|
46
54
|
def new
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
cli_opts = options.transform_keys { |key| key.sub(/-/, '_') }
|
56
|
+
SessionManager.new.new_session(cli_opts)
|
57
|
+
end
|
58
|
+
|
59
|
+
method_option 'role_arn',
|
60
|
+
type: :string,
|
61
|
+
desc: 'The ARN of the role to assume',
|
62
|
+
required: true
|
63
|
+
method_option 'role_session_name',
|
64
|
+
type: :string,
|
65
|
+
desc: 'An identifier for the assumed role session',
|
66
|
+
required: true
|
67
|
+
method_option 'profile',
|
68
|
+
type: :string,
|
69
|
+
desc: 'Profile that session token will be loaded into',
|
70
|
+
default: 'default'
|
71
|
+
method_option 'duration',
|
72
|
+
type: :numeric,
|
73
|
+
desc: 'Duration, in seconds, that credentials should remain valid',
|
74
|
+
default: nil
|
75
|
+
method_option 'mfa-device',
|
76
|
+
type: :string,
|
77
|
+
desc: 'ARN of MFA device',
|
78
|
+
default: nil
|
79
|
+
method_option 'mfa-code',
|
80
|
+
type: :string,
|
81
|
+
desc: 'Six digit code from MFA device',
|
82
|
+
default: nil
|
83
|
+
method_option 'yubikey-name',
|
84
|
+
type: :string,
|
85
|
+
desc: 'Name of yubikey device',
|
86
|
+
default: 'Yubikey'
|
87
|
+
method_option 'oath-credential',
|
88
|
+
type: :string,
|
89
|
+
desc: 'Name of OATH credential',
|
90
|
+
default: nil
|
91
|
+
desc 'assume-role', 'Assumes a role'
|
92
|
+
def assume_role
|
93
|
+
cli_opts = options.transform_keys { |key| key.sub(/-/, '_') }
|
94
|
+
SessionManager.new.assume_role(cli_opts)
|
95
|
+
end
|
96
|
+
|
97
|
+
method_option 'aws-access-key-id',
|
98
|
+
type: :string,
|
99
|
+
desc: 'Access key used to generate session token',
|
100
|
+
default: nil
|
101
|
+
method_option 'aws-secret-access-key',
|
102
|
+
type: :string,
|
103
|
+
desc: 'Secret key used to generate session token',
|
104
|
+
default: nil
|
105
|
+
method_option 'aws-region',
|
106
|
+
type: :string,
|
107
|
+
desc: 'AWS region to connect to',
|
108
|
+
default: nil
|
109
|
+
method_option 'config-file',
|
110
|
+
type: :string,
|
111
|
+
desc: 'YAML file to load config from',
|
112
|
+
default: '~/.aws/aws-session-config.yml'
|
113
|
+
method_option 'source-profile',
|
114
|
+
type: :string,
|
115
|
+
desc: 'Profile in config file that user credentials will be loaded from',
|
116
|
+
default: nil
|
117
|
+
method_option 'duration',
|
118
|
+
type: :numeric,
|
119
|
+
desc: 'Duration, in seconds, that credentials should remain valid',
|
120
|
+
default: nil
|
121
|
+
method_option 'mfa-device',
|
122
|
+
type: :string,
|
123
|
+
desc: 'ARN of MFA device',
|
124
|
+
default: nil
|
125
|
+
method_option 'yubikey-name',
|
126
|
+
type: :string,
|
127
|
+
desc: 'Name of yubikey device',
|
128
|
+
default: 'Yubikey'
|
129
|
+
method_option 'oath-credential',
|
130
|
+
type: :string,
|
131
|
+
desc: 'Name of OATH credential',
|
132
|
+
default: nil
|
133
|
+
desc 'configure', 'Configures a new source profile'
|
134
|
+
def configure
|
135
|
+
cli_opts = options.transform_keys { |key| key.sub(/-/, '_') }
|
136
|
+
cli_opts['source_profile'] ||= ask('Source profile (leave blank for "default"):')
|
137
|
+
cli_opts['aws_access_key_id'] ||= ask('AWS Access Key ID:')
|
138
|
+
cli_opts['aws_secret_access_key'] ||= ask('AWS Secret Access Key:', echo: false)
|
139
|
+
puts '' # BUG: No LF printed when echo is set to false
|
140
|
+
cli_opts['aws_region'] ||= ask('AWS region:')
|
141
|
+
cli_opts['duration'] ||= ask('Session duration (in seconds):')
|
142
|
+
|
143
|
+
puts ''
|
144
|
+
if yes?('Configure MFA?')
|
145
|
+
cli_opts['mfa_device'] ||= ask('MFA device ARN:')
|
146
|
+
puts ''
|
147
|
+
if yes?('Configure Yubikey?')
|
148
|
+
cli_opts['oath_credential'] ||= ask('OATH credential name:')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
cli_opts['source_profile'] = 'default' if cli_opts['source_profile'].empty?
|
56
153
|
|
57
|
-
|
58
|
-
|
59
|
-
|
154
|
+
prof = Profile.new(cli_opts.except('config_file', 'source_profile'))
|
155
|
+
cf = Config.new(path: cli_opts['config_file'])
|
156
|
+
cf.set_profile(cli_opts[:source_profile], prof)
|
60
157
|
end
|
61
158
|
|
62
159
|
default_task :new
|
@@ -3,91 +3,27 @@ module Aws
|
|
3
3
|
module Credentials
|
4
4
|
# Holds configuration
|
5
5
|
class Config
|
6
|
-
|
7
|
-
|
8
|
-
@config = config || load_file
|
9
|
-
end
|
10
|
-
|
11
|
-
def [](key)
|
12
|
-
@config[key]
|
13
|
-
end
|
14
|
-
|
15
|
-
def []=(key, value)
|
16
|
-
@config[key] = value
|
17
|
-
end
|
18
|
-
|
19
|
-
def aws_access_key_id
|
20
|
-
self['aws_access_key_id']
|
21
|
-
end
|
22
|
-
|
23
|
-
def aws_access_key_id=(value)
|
24
|
-
self['aws_access_key_id'] = value
|
25
|
-
end
|
26
|
-
|
27
|
-
def aws_secret_access_key
|
28
|
-
self['aws_secret_access_key']
|
29
|
-
end
|
30
|
-
|
31
|
-
def aws_secret_access_key=(value)
|
32
|
-
self['aws_secret_access_key'] = value
|
33
|
-
end
|
34
|
-
|
35
|
-
def credential_file
|
36
|
-
self['credential_file']
|
37
|
-
end
|
38
|
-
|
39
|
-
def credential_file=(value)
|
40
|
-
self['credential_file'] = value
|
41
|
-
end
|
42
|
-
|
43
|
-
def duration
|
44
|
-
self['duration']
|
45
|
-
end
|
46
|
-
|
47
|
-
def duration=(value)
|
48
|
-
self['duration'] = value
|
49
|
-
end
|
50
|
-
|
51
|
-
# @api private
|
52
|
-
def load_file
|
53
|
-
return {} unless File.exist?(@path)
|
54
|
-
YAML.load(File.read(@path))
|
55
|
-
end
|
56
|
-
|
57
|
-
def mfa_code
|
58
|
-
self['mfa_code']
|
59
|
-
end
|
60
|
-
|
61
|
-
def mfa_code=(value)
|
62
|
-
self['mfa_code'] = value
|
63
|
-
end
|
64
|
-
|
65
|
-
def mfa_device
|
66
|
-
self['mfa_device']
|
67
|
-
end
|
68
|
-
|
69
|
-
def mfa_device=(value)
|
70
|
-
self['mfa_device'] = value
|
71
|
-
end
|
6
|
+
include ProfileStorage
|
7
|
+
include FileProvider::YamlFileProvider
|
72
8
|
|
73
|
-
|
74
|
-
self['profile']
|
75
|
-
end
|
9
|
+
attr_reader :path
|
76
10
|
|
77
|
-
def
|
78
|
-
|
11
|
+
def initialize(options = {})
|
12
|
+
@path = File.expand_path(options[:path] || default_path)
|
79
13
|
end
|
80
14
|
|
81
|
-
def
|
82
|
-
|
15
|
+
def default_path
|
16
|
+
File.join(%w(~ .aws aws-session-config.yml))
|
83
17
|
end
|
84
18
|
|
85
|
-
|
86
|
-
|
19
|
+
# @return [Hash<String,Hash>]
|
20
|
+
def profiles_hash
|
21
|
+
self[:profiles] || {}
|
87
22
|
end
|
88
23
|
|
89
|
-
|
90
|
-
|
24
|
+
# @param [Hash] hsh
|
25
|
+
def profiles_hash=(hsh)
|
26
|
+
self[:profiles] = hsh
|
91
27
|
end
|
92
28
|
end
|
93
29
|
end
|
@@ -1,39 +1,29 @@
|
|
1
1
|
module Aws
|
2
2
|
module Session
|
3
3
|
module Credentials
|
4
|
-
#
|
4
|
+
# Holds credentials that are read by AWS SDKs
|
5
5
|
class CredentialFile
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
include FileProvider::IniFileProvider
|
7
|
+
include ProfileStorage
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@path = File.expand_path(options[:path] || default_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_path
|
16
|
+
File.join(%w(~ .aws credentials))
|
11
17
|
end
|
12
18
|
|
13
|
-
# @
|
14
|
-
def
|
15
|
-
|
16
|
-
IniFile.load(@path)
|
17
|
-
else
|
18
|
-
path_dir = File.dirname(@path)
|
19
|
-
FileUtils.mkdir_p(path_dir) unless File.exist?(path_dir)
|
20
|
-
IniFile.new(filename: @path, encoding: 'UTF-8')
|
21
|
-
end
|
19
|
+
# @return [Hash<String,Hash>]
|
20
|
+
def profiles_hash
|
21
|
+
read.to_h
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
# profile. Does not override options if they are not provided. Set an
|
28
|
-
# option to +nil+ to explicitly unset an existing option.
|
29
|
-
# @param [String] profile name of profile to set credentials for
|
30
|
-
# @param [Hash] options settings to set
|
31
|
-
# @option options [String] :access_key_id Access key
|
32
|
-
# @option options [String] :secret_access_key Secret key
|
33
|
-
# @option options [String] :session_token Session token
|
34
|
-
def set_credentials(profile, options = {})
|
35
|
-
@ini_file[profile] = @ini_file[profile].merge(options)
|
36
|
-
@ini_file.write
|
24
|
+
# @param [Hash<String,Hash>] prfs
|
25
|
+
def profiles_hash=(hsh)
|
26
|
+
hsh.each { |key, value| self[key] = value }
|
37
27
|
end
|
38
28
|
end
|
39
29
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
module FileProvider
|
5
|
+
# Mixin to store configuration in an INI file
|
6
|
+
module IniFileProvider
|
7
|
+
def [](key)
|
8
|
+
read[key.to_s]
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, value)
|
12
|
+
ini_file = read
|
13
|
+
ini_file[key.to_s] = value
|
14
|
+
ini_file.save
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# @return [IniFile]
|
19
|
+
def read
|
20
|
+
if File.exist?(path)
|
21
|
+
IniFile.load(path)
|
22
|
+
else
|
23
|
+
IniFile.new(filename: path, encoding: 'UTF-8')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
module FileProvider
|
5
|
+
# Mixin to store configuration in a YAML file
|
6
|
+
module YamlFileProvider
|
7
|
+
def [](key)
|
8
|
+
read[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, value)
|
12
|
+
hash = read.dup
|
13
|
+
hash[key] = value
|
14
|
+
write(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# @return [Hash]
|
19
|
+
def read
|
20
|
+
return {} unless File.exist?(path)
|
21
|
+
YAML.load(File.read(path)).deep_symbolize_keys
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
# @param [Hash] hash
|
26
|
+
def write(hash)
|
27
|
+
hsh = hash.deep_stringify_keys
|
28
|
+
File.open(path, 'w') { |file| file.write(YAML.dump(hsh)) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
module MfaDevice
|
5
|
+
# Represents generic MFA device that generates codes. Class must be
|
6
|
+
# initialized with the code.
|
7
|
+
class GenericMfaDevice
|
8
|
+
attr_reader :code
|
9
|
+
attr_reader :device_arn
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@code = options[:code]
|
13
|
+
@device_arn = options[:device_arn]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
module MfaDevice
|
5
|
+
# Gets MFA codes from a Yubikey using YubiOATH
|
6
|
+
class YubikeyMfaDevice
|
7
|
+
attr_reader :device_arn
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@yubikey_name = options.fetch(:yubikey_name) { 'Yubikey' }
|
11
|
+
@oath_credential = options[:oath_credential]
|
12
|
+
@device_arn = options[:device_arn]
|
13
|
+
end
|
14
|
+
|
15
|
+
def code
|
16
|
+
card_names.each do |card_name|
|
17
|
+
card(card_name) do |crd|
|
18
|
+
oath = YubiOATH.new(crd)
|
19
|
+
codes = oath.calculate_all(timestamp: Time.now)
|
20
|
+
# Credential names are returned as ASCII-8BIT
|
21
|
+
codes.transform_keys! { |key| key.force_encoding('UTF-8') }
|
22
|
+
return codes[@oath_credential]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @param [String] name
|
31
|
+
# @yieldparam card [Smartcard::PCSC::Card]
|
32
|
+
def card(name)
|
33
|
+
context do |cxt|
|
34
|
+
begin
|
35
|
+
crd = cxt.card(name, :shared)
|
36
|
+
yield crd
|
37
|
+
ensure
|
38
|
+
crd.disconnect unless crd.nil?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Array<String>]
|
44
|
+
def card_names
|
45
|
+
context do |cxt|
|
46
|
+
cxt.readers.select { |name| name.include?(@yubikey_name) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @yieldparam context [Smartcard::PCSC::Context]
|
51
|
+
def context
|
52
|
+
context = Smartcard::PCSC::Context.new
|
53
|
+
yield context
|
54
|
+
ensure
|
55
|
+
context.release
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
# Mixin to store profiles
|
5
|
+
module ProfileStorage
|
6
|
+
# @return [Hash<String,Profile>]
|
7
|
+
def profiles
|
8
|
+
prfs = {}
|
9
|
+
profiles_hash.each do |name, options|
|
10
|
+
prfs[name] = Profile.new(options)
|
11
|
+
end
|
12
|
+
prfs
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Hash<String,Profile>] prfs
|
16
|
+
def profiles=(prfs)
|
17
|
+
hash = {}
|
18
|
+
prfs.each do |name, prof|
|
19
|
+
hash[name] = prof.to_h
|
20
|
+
end
|
21
|
+
self.profiles_hash = hash
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [String] name
|
25
|
+
# @return [Profile]
|
26
|
+
def profile(name)
|
27
|
+
profiles[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [String] name
|
31
|
+
# @param [Profile] prof
|
32
|
+
def set_profile(name, prof)
|
33
|
+
profs = profiles.dup
|
34
|
+
profs[name] = prof
|
35
|
+
self.profiles = profs
|
36
|
+
prof
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -3,44 +3,55 @@ module Aws
|
|
3
3
|
module Credentials
|
4
4
|
# Builds AWS session
|
5
5
|
class SessionBuilder
|
6
|
-
# @param [Hash]
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
@
|
6
|
+
# @param [Hash] options
|
7
|
+
def initialize(options)
|
8
|
+
@mfa_device = options[:mfa_device]
|
9
|
+
@session_duration_seconds = options[:session_duration_seconds]
|
10
|
+
@role_duration_seconds = options[:role_duration_seconds]
|
11
|
+
@role_arn = options[:role_arn]
|
12
|
+
@role_session_name = options[:role_session_name]
|
13
|
+
@source_profile = options[:source_profile]
|
14
|
+
@sts_client = options[:sts_client]
|
11
15
|
end
|
12
16
|
|
13
|
-
# @
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
# @return [Profile]
|
18
|
+
def role_profile
|
19
|
+
resp = sts_client.assume_role(
|
20
|
+
role_arn: @role_arn,
|
21
|
+
role_session_name: @role_session_name,
|
22
|
+
duration_seconds: @role_duration_seconds,
|
23
|
+
serial_number: @mfa_device.device_arn,
|
24
|
+
token_code: @mfa_device.code
|
19
25
|
)
|
26
|
+
return Profile.new(
|
27
|
+
aws_access_key_id: resp.credentials['access_key_id'],
|
28
|
+
aws_secret_access_key: resp.credentials['secret_access_key'],
|
29
|
+
aws_session_token: resp.credentials['session_token'],
|
30
|
+
aws_region: @source_profile.aws_region
|
31
|
+
) if resp
|
20
32
|
end
|
21
33
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
serial_number: @config['mfa_device'],
|
29
|
-
token_code: @config['mfa_code']
|
34
|
+
# @return [Profile]
|
35
|
+
def session_profile
|
36
|
+
resp = sts_client.get_session_token(
|
37
|
+
duration_seconds: @session_duration_seconds,
|
38
|
+
serial_number: @mfa_device.device_arn,
|
39
|
+
token_code: @mfa_device.code
|
30
40
|
)
|
31
|
-
return
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
return Profile.new(
|
42
|
+
aws_access_key_id: resp.credentials['access_key_id'],
|
43
|
+
aws_secret_access_key: resp.credentials['secret_access_key'],
|
44
|
+
aws_session_token: resp.credentials['session_token'],
|
45
|
+
aws_region: @source_profile.aws_region
|
46
|
+
) if resp
|
36
47
|
end
|
37
48
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
# @api private
|
50
|
+
def sts_client
|
51
|
+
@client ||= Aws::STS::Client.new(
|
52
|
+
region: @source_profile.aws_region,
|
53
|
+
credentials: @source_profile.aws_credentials
|
54
|
+
)
|
44
55
|
end
|
45
56
|
end
|
46
57
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Aws
|
2
|
+
module Session
|
3
|
+
module Credentials
|
4
|
+
# Manages sessions
|
5
|
+
class SessionManager
|
6
|
+
# Assumes a role from provided options
|
7
|
+
# @param [Hash] options
|
8
|
+
# @option options [String] :profile
|
9
|
+
# @option options [String] :role_arn
|
10
|
+
# @option options [String] :duration
|
11
|
+
def assume_role(options)
|
12
|
+
options[:profile] = options[:profile].to_sym
|
13
|
+
|
14
|
+
session_prof = Cache.new.profile(options[:profile])
|
15
|
+
options = session_prof.to_h.deep_merge(options).deep_symbolize_keys
|
16
|
+
|
17
|
+
sb = SessionBuilder.new(
|
18
|
+
mfa_device: mfa_device(options),
|
19
|
+
role_duration_seconds: options[:duration],
|
20
|
+
role_arn: options[:role_arn],
|
21
|
+
role_session_name: options[:role_session_name],
|
22
|
+
source_profile: session_prof
|
23
|
+
)
|
24
|
+
role_profile = sb.role_profile
|
25
|
+
|
26
|
+
CredentialFile.new.set_profile(options[:profile], profile)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a new session from provided options
|
30
|
+
# @param [Hash] options
|
31
|
+
# @option options [String] :aws_access_key_id
|
32
|
+
# @option options [String] :aws_secret_access_key
|
33
|
+
# @option options [String] :aws_session_token
|
34
|
+
# @option options [String] :aws_region
|
35
|
+
# @option options [String] :source_profile
|
36
|
+
# @option options [String] :profile
|
37
|
+
# @option options [String] :duration
|
38
|
+
# @option options [String] :mfa_device
|
39
|
+
# @option options [String] :mfa_code
|
40
|
+
# @option options [String] :yubikey_name
|
41
|
+
# @option options [String] :oath_credential
|
42
|
+
# @option options [String] :config_file
|
43
|
+
def new_session(options)
|
44
|
+
options[:source_profile] = options[:source_profile].to_sym
|
45
|
+
options[:profile] = options[:profile].to_sym
|
46
|
+
|
47
|
+
user_prof = user_profile(options[:source_profile], options[:config_file])
|
48
|
+
options = user_prof.to_h.deep_merge(options).deep_symbolize_keys
|
49
|
+
|
50
|
+
sb = SessionBuilder.new(
|
51
|
+
mfa_device: mfa_device(options),
|
52
|
+
session_duration_seconds: options[:duration],
|
53
|
+
source_profile: user_prof
|
54
|
+
)
|
55
|
+
set_user_session_profile(options[:profile], sb.session_profile)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def mfa_device(options)
|
61
|
+
opts = {
|
62
|
+
device_arn: options[:mfa_device],
|
63
|
+
yubikey_name: options[:yubikey_name],
|
64
|
+
oath_credential: options[:oath_credential],
|
65
|
+
code: options[:mfa_code]
|
66
|
+
}
|
67
|
+
if options.key?(:oath_credential)
|
68
|
+
MfaDevice::YubikeyMfaDevice.new(opts)
|
69
|
+
else
|
70
|
+
MfaDevice::GenericMfaDevice.new(opts)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_user_session_profile(name, profile)
|
75
|
+
[Cache.new, CredentialFile.new].each do |profile_store|
|
76
|
+
profile_store.set_profile(name, profile)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def user_profile(name, path)
|
81
|
+
Config.new(path: path).profile(name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def user_session_profile(name)
|
85
|
+
Cache.new.profile(name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -1,10 +1,25 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/hash'
|
1
3
|
require 'aws-sdk'
|
2
4
|
require 'fileutils'
|
3
5
|
require 'inifile'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'smartcard'
|
4
8
|
require 'yaml'
|
9
|
+
require 'yubioath'
|
5
10
|
|
11
|
+
require 'aws/session/credentials/file_provider/ini_file_provider'
|
12
|
+
require 'aws/session/credentials/file_provider/yaml_file_provider'
|
13
|
+
require 'aws/session/credentials/mfa_device/generic_mfa_device'
|
14
|
+
require 'aws/session/credentials/mfa_device/yubikey_mfa_device'
|
15
|
+
|
16
|
+
require 'aws/session/credentials/profile_storage'
|
17
|
+
|
18
|
+
require 'aws/session/credentials/cache'
|
6
19
|
require 'aws/session/credentials/config'
|
7
20
|
require 'aws/session/credentials/credential_file'
|
21
|
+
require 'aws/session/credentials/profile'
|
8
22
|
require 'aws/session/credentials/session_builder'
|
23
|
+
require 'aws/session/credentials/session_manager'
|
9
24
|
|
10
25
|
require 'aws/session/credentials/version'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-session-credentials
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Vidulich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ! '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
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'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: aws-sdk
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +108,20 @@ dependencies:
|
|
94
108
|
- - ~>
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: smartcard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: thor
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +136,20 @@ dependencies:
|
|
108
136
|
- - ~>
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: '0.19'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: yubioath
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ! '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
111
153
|
description: Command-line tool to generate AWS session credentials.
|
112
154
|
email:
|
113
155
|
- ben@vidulich.co.nz
|
@@ -129,10 +171,18 @@ files:
|
|
129
171
|
- bin/setup
|
130
172
|
- exe/aws-session
|
131
173
|
- lib/aws/session/credentials.rb
|
174
|
+
- lib/aws/session/credentials/cache.rb
|
132
175
|
- lib/aws/session/credentials/cli.rb
|
133
176
|
- lib/aws/session/credentials/config.rb
|
134
177
|
- lib/aws/session/credentials/credential_file.rb
|
178
|
+
- lib/aws/session/credentials/file_provider/ini_file_provider.rb
|
179
|
+
- lib/aws/session/credentials/file_provider/yaml_file_provider.rb
|
180
|
+
- lib/aws/session/credentials/mfa_device/generic_mfa_device.rb
|
181
|
+
- lib/aws/session/credentials/mfa_device/yubikey_mfa_device.rb
|
182
|
+
- lib/aws/session/credentials/profile.rb
|
183
|
+
- lib/aws/session/credentials/profile_storage.rb
|
135
184
|
- lib/aws/session/credentials/session_builder.rb
|
185
|
+
- lib/aws/session/credentials/session_manager.rb
|
136
186
|
- lib/aws/session/credentials/version.rb
|
137
187
|
homepage: https://github.com/zl4bv/aws-session-credentials
|
138
188
|
licenses:
|
@@ -149,9 +199,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
199
|
version: '0'
|
150
200
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
201
|
requirements:
|
152
|
-
- - ! '
|
202
|
+
- - ! '>'
|
153
203
|
- !ruby/object:Gem::Version
|
154
|
-
version:
|
204
|
+
version: 1.3.1
|
155
205
|
requirements: []
|
156
206
|
rubyforge_project:
|
157
207
|
rubygems_version: 2.4.5
|