aws_assume_role 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +23 -0
- data/aws_assume_role.gemspec +2 -0
- data/i18n/en.yml +3 -0
- data/lib/aws_assume_role/cli/actions/abstract_action.rb +4 -1
- data/lib/aws_assume_role/cli/actions/configure_profile.rb +1 -0
- data/lib/aws_assume_role/cli/actions/configure_role_assumption.rb +1 -0
- data/lib/aws_assume_role/cli/commands/configure.rb +1 -0
- data/lib/aws_assume_role/credentials/providers/mfa_session_credentials.rb +21 -3
- data/lib/aws_assume_role/profile_configuration.rb +1 -0
- data/lib/aws_assume_role/store/keyring.rb +1 -0
- data/lib/aws_assume_role/store/shared_config_with_keyring.rb +9 -2
- data/lib/aws_assume_role/version.rb +1 -1
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a54b02fa08cf71de92e8cae70bc4b8d0b7f3c6cc
|
4
|
+
data.tar.gz: d97b2748840f3a481eb388b5d52be5ab3631e34d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d1135bfcc67444456e07d1a0fdef7c72dac266997af27fd1f1d321b8f82955907ebf4ded1ad19944945519eb5929c201a7cbe162007a9d8d83e4ed51d15b55c
|
7
|
+
data.tar.gz: 5bab6c3e11e56fd1ea80e748fef7c397bd9d6ae9914a09105af9def7e022a2a5a14d13f99c01def3b204e1098dfc6daea29c1d3b43681fb495f81d431634809e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
## 0.2.0
|
2
|
+
|
3
|
+
* Add support for Yubikey as a source for MFA (@davbo)
|
4
|
+
* Remove expired crednetials before writing new STS credentials (@davbo)
|
5
|
+
|
6
|
+
## 0.1.2
|
7
|
+
|
8
|
+
* Become compatible with Ruby 2.1 (@randomvariable)
|
9
|
+
* Added test suite from AWS SDK for Ruby (@randomvariable)
|
10
|
+
|
11
|
+
## 0.1.1
|
12
|
+
|
13
|
+
* Fix logging on Ruby 2.2 (@randomvariable)
|
14
|
+
|
15
|
+
## 0.1.0
|
16
|
+
|
17
|
+
* Complete rewrite with SDK compatible API layer (@randomvariable)
|
18
|
+
|
19
|
+
## 0.0.3
|
20
|
+
|
21
|
+
* Store master credentials in OS credential store. (@mrprimate)
|
22
|
+
|
23
|
+
## 0.0.2
|
24
|
+
|
25
|
+
* Add CLI (@mrprimate)
|
26
|
+
|
27
|
+
## 0.0.1
|
28
|
+
|
29
|
+
* Initial release (@jtopper)
|
data/README.md
CHANGED
@@ -82,6 +82,29 @@ set up a role that you will assume in every day use:
|
|
82
82
|
More options are available in the application help.
|
83
83
|
Use `> aws-assume-role --help ` for help at any time.
|
84
84
|
|
85
|
+
Using MFA TOTP with a Yubikey
|
86
|
+
-----------------------------
|
87
|
+
|
88
|
+
[Yubikeys support TOTP](https://developers.yubico.com/OATH/) this offers some
|
89
|
+
benefits over using a phone. One benefit is the TOTP token can be retrieved by
|
90
|
+
an API call rather than a user reading the token from the device.
|
91
|
+
|
92
|
+
This allows developers to call AWS through aws-assume-role, providing an MFA
|
93
|
+
token without prompting for user input. To use this specify
|
94
|
+
`--yubikey-oath-name` when calling configure role.
|
95
|
+
|
96
|
+
``` sh
|
97
|
+
> aws-assume-role configure role -p company-dev --source-profile company-sso \
|
98
|
+
--role-arn=arn:aws:iam::000000000001:role/ViewEC2 --role-session-name=growthsmith \
|
99
|
+
--mfa-serial automatic --yubikey-oath-name "Amazon Web Services:myuser@company-sso"
|
100
|
+
```
|
101
|
+
|
102
|
+
_Yubikey Support_: `aws-assume-role` uses the [smartcard gem](https://rubygems.org/gems/smartcard)
|
103
|
+
to connect to the Yubikey, this itself depends upon some C libraries being installed. They provide
|
104
|
+
[platform specific instructions](https://github.com/costan/smartcard/blob/master/BUILD#L19)
|
105
|
+
for installing these libraries PC/SC.
|
106
|
+
|
107
|
+
|
85
108
|
Running applications
|
86
109
|
--------------------
|
87
110
|
|
data/aws_assume_role.gemspec
CHANGED
@@ -35,6 +35,8 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_runtime_dependency "launchy", "~> 2.4"
|
36
36
|
spec.add_runtime_dependency "keyring", "~> 0.4", ">= 0.4.1"
|
37
37
|
spec.add_runtime_dependency "pastel", "~> 0.7"
|
38
|
+
spec.add_runtime_dependency "smartcard", "~> 0.5.6"
|
39
|
+
spec.add_runtime_dependency "yubioath", "~> 1.2", ">= 1.2.1"
|
38
40
|
spec.add_development_dependency "rspec", "~> 3.5"
|
39
41
|
spec.add_development_dependency "rubocop", "~> 0.46"
|
40
42
|
spec.add_development_dependency "yard", "~> 0.9"
|
data/i18n/en.yml
CHANGED
@@ -77,6 +77,7 @@ en:
|
|
77
77
|
mfa_token:
|
78
78
|
first_time: "Please provide an MFA token"
|
79
79
|
other_times: "Credentials have expired, please provide another MFA"
|
80
|
+
smartcard_not_supported: "Smartcard drivers not installed, see https://github.com/scalefactory/aws-assume-role/blob/master/README.md for details"
|
80
81
|
default_role: "A default role to assume (leave blank to not use)"
|
81
82
|
external_id: String provided by the external account holder to uniquely identify you.
|
82
83
|
source_profile: Which profile to use to assume this role.
|
@@ -86,9 +87,11 @@ en:
|
|
86
87
|
duration_seconds: Default session length
|
87
88
|
shell_type: What type of shell to use.
|
88
89
|
name_to_delete: Please type the name of the profile, i.e. %s , to continue deletion.
|
90
|
+
yubikey_oath_name: Identifier of the OATH / TOTP secret stored on the yubikey.
|
89
91
|
program_description: "A tool for AWS credential management"
|
90
92
|
errors:
|
91
93
|
NoSuchProfileError: Profile %s not found in shared configuration.
|
94
|
+
SmartcardException: No YubiKey found!
|
92
95
|
MissingCredentialsError: No credentials found!
|
93
96
|
rules:
|
94
97
|
profile:
|
@@ -24,8 +24,11 @@ class AwsAssumeRole::Cli::Actions::AbstractAction
|
|
24
24
|
creds = @provider.resolve(nil_with_role_not_set: true)
|
25
25
|
logger.debug "Got credentials #{creds}"
|
26
26
|
return creds unless creds.nil?
|
27
|
+
rescue Smartcard::PCSC::Exception
|
28
|
+
error t("errors.SmartcardException")
|
29
|
+
exit 403
|
27
30
|
rescue NoMethodError
|
28
|
-
error "
|
31
|
+
error t("errors.MissingCredentialsError")
|
29
32
|
exit 404
|
30
33
|
end
|
31
34
|
|
@@ -10,6 +10,7 @@ class AwsAssumeRole::Cli::Actions::ConfigureRoleAssumption < AwsAssumeRole::Cli:
|
|
10
10
|
required(:role_arn) { filled? & format?(ROLE_REGEX) }
|
11
11
|
required(:external_id).filled?
|
12
12
|
required(:duration_seconds).filled?
|
13
|
+
optional(:yubikey_oath_name)
|
13
14
|
end
|
14
15
|
|
15
16
|
def act_on(config)
|
@@ -20,6 +20,7 @@ module AwsAssumeRole::Cli
|
|
20
20
|
r.flag ["region"], desc: t("options.region")
|
21
21
|
r.flag ["external-id"], desc: t("options.external_id")
|
22
22
|
r.flag ["duration-seconds"], desc: t("options.duration_seconds"), default_value: 3600
|
23
|
+
r.flag ["yubikey-oath-name"], desc: t("options.yubikey_oath_name")
|
23
24
|
|
24
25
|
r.action do |global_options, options, args|
|
25
26
|
AwsAssumeRole::Cli::Actions::ConfigureRoleAssumption.new(global_options, options, args)
|
@@ -1,6 +1,13 @@
|
|
1
1
|
require_relative "includes"
|
2
2
|
require_relative "../../types"
|
3
3
|
require_relative "../../configuration"
|
4
|
+
begin
|
5
|
+
require "smartcard"
|
6
|
+
require "yubioath"
|
7
|
+
SMARTCARD_SUPPORT = true
|
8
|
+
rescue LoadError
|
9
|
+
SMARTCARD_SUPPORT = false
|
10
|
+
end
|
4
11
|
|
5
12
|
class AwsAssumeRole::Credentials::Providers::MfaSessionCredentials < Dry::Struct
|
6
13
|
constructor_type :schema
|
@@ -17,6 +24,7 @@ class AwsAssumeRole::Credentials::Providers::MfaSessionCredentials < Dry::Struct
|
|
17
24
|
attribute :duration_seconds, Dry::Types["coercible.int"].default(3600)
|
18
25
|
attribute :region, AwsAssumeRole::Types::Region.optional
|
19
26
|
attribute :serial_number, AwsAssumeRole::Types::MfaSerial.optional.default("automatic")
|
27
|
+
attribute :yubikey_oath_name, Dry::Types["strict.string"].optional
|
20
28
|
|
21
29
|
def initialize(options)
|
22
30
|
options.each { |key, value| instance_variable_set("@#{key}", value) }
|
@@ -36,8 +44,8 @@ class AwsAssumeRole::Credentials::Providers::MfaSessionCredentials < Dry::Struct
|
|
36
44
|
@sts_client ||= Aws::STS::Client.new(region: @region, credentials: @permanent_credentials)
|
37
45
|
end
|
38
46
|
|
39
|
-
def prompt_for_token
|
40
|
-
text = first_time ? t("options.mfa_token.first_time") : t("options.mfa_token.other_times")
|
47
|
+
def prompt_for_token
|
48
|
+
text = @first_time ? t("options.mfa_token.first_time") : t("options.mfa_token.other_times")
|
41
49
|
Ui.input.ask text
|
42
50
|
end
|
43
51
|
|
@@ -51,8 +59,18 @@ class AwsAssumeRole::Credentials::Providers::MfaSessionCredentials < Dry::Struct
|
|
51
59
|
broadcast(:mfa_completed)
|
52
60
|
end
|
53
61
|
|
62
|
+
def retrieve_yubikey_token
|
63
|
+
raise t("options.mfa_token.smartcard_not_supported") unless SMARTCARD_SUPPORT
|
64
|
+
context = Smartcard::PCSC::Context.new
|
65
|
+
raise "Yubikey not found" unless context.readers.length == 1
|
66
|
+
reader_name = context.readers.first
|
67
|
+
card = Smartcard::PCSC::Card.new(context, reader_name, :shared)
|
68
|
+
codes = YubiOATH.new(card).calculate_all(timestamp: Time.now)
|
69
|
+
codes.fetch(BinData::String.new(@yubikey_oath_name))
|
70
|
+
end
|
71
|
+
|
54
72
|
def refresh_using_mfa
|
55
|
-
token_code = prompt_for_token
|
73
|
+
token_code = @yubikey_oath_name ? retrieve_yubikey_token : prompt_for_token
|
56
74
|
token = sts_client.get_session_token(
|
57
75
|
duration_seconds: @duration_seconds,
|
58
76
|
serial_number: @serial_number,
|
@@ -18,6 +18,7 @@ class AwsAssumeRole::ProfileConfiguration < Dry::Struct
|
|
18
18
|
attribute :role_session_name, Dry::Types["strict.string"].optional
|
19
19
|
attribute :serial_number, Dry::Types["strict.string"].optional
|
20
20
|
attribute :mfa_serial, Dry::Types["strict.string"].optional
|
21
|
+
attribute :yubikey_oath_name, Dry::Types["strict.string"].optional
|
21
22
|
attribute :use_mfa, Dry::Types["strict.bool"].optional.default(false)
|
22
23
|
attribute :no_profile, Dry::Types["strict.bool"].optional.default(false)
|
23
24
|
attribute :shell_type, Dry::Types["strict.string"].optional
|
@@ -52,6 +52,7 @@ module AwsAssumeRole::Store::Keyring
|
|
52
52
|
credentials_to_persist = Serialization.credentials_to_hash(credentials)
|
53
53
|
credentials_to_persist[:expiration] = expiration if expiration
|
54
54
|
semaphore.synchronize do
|
55
|
+
keyring(backend).delete_password(KEYRING_KEY, id)
|
55
56
|
keyring(backend).set_password(KEYRING_KEY, id, credentials_to_persist.to_json)
|
56
57
|
end
|
57
58
|
end
|
@@ -69,7 +69,8 @@ class AwsAssumeRole::Store::SharedConfigWithKeyring < AwsAssumeRole::Vendored::A
|
|
69
69
|
semaphore.synchronize do
|
70
70
|
Keyring.save_credentials profile_name, credentials if credentials.set?
|
71
71
|
merged_config = merged_config.slice :region, :role_arn, :mfa_serial, :source_profile,
|
72
|
-
:role_session_name, :external_id, :duration_seconds
|
72
|
+
:role_session_name, :external_id, :duration_seconds,
|
73
|
+
:yubikey_oath_name
|
73
74
|
configuration.delete_section ckey
|
74
75
|
configuration[ckey] = merged_config.compact
|
75
76
|
save_configuration
|
@@ -155,9 +156,15 @@ class AwsAssumeRole::Store::SharedConfigWithKeyring < AwsAssumeRole::Vendored::A
|
|
155
156
|
opts[:role_arn] ||= prof_cfg["role_arn"]
|
156
157
|
opts[:external_id] ||= prof_cfg["external_id"]
|
157
158
|
opts[:serial_number] ||= prof_cfg["mfa_serial"]
|
159
|
+
opts[:yubikey_oath_name] ||= prof_cfg["yubikey_oath_name"]
|
158
160
|
opts[:region] ||= profile_region(profile)
|
159
161
|
if opts[:serial_number]
|
160
|
-
mfa_opts = {
|
162
|
+
mfa_opts = {
|
163
|
+
credentials: opts[:credentials],
|
164
|
+
region: opts[:region],
|
165
|
+
serial_number: opts[:serial_number],
|
166
|
+
yubikey_oath_name: opts[:yubikey_oath_name],
|
167
|
+
}
|
161
168
|
mfa_creds = mfa_session(cfg, opts[:source_profile], mfa_opts)
|
162
169
|
opts.delete :serial_number
|
163
170
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws_assume_role
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Topper
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-03-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -200,6 +200,40 @@ dependencies:
|
|
200
200
|
- - "~>"
|
201
201
|
- !ruby/object:Gem::Version
|
202
202
|
version: '0.7'
|
203
|
+
- !ruby/object:Gem::Dependency
|
204
|
+
name: smartcard
|
205
|
+
requirement: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - "~>"
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: 0.5.6
|
210
|
+
type: :runtime
|
211
|
+
prerelease: false
|
212
|
+
version_requirements: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - "~>"
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: 0.5.6
|
217
|
+
- !ruby/object:Gem::Dependency
|
218
|
+
name: yubioath
|
219
|
+
requirement: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - "~>"
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '1.2'
|
224
|
+
- - ">="
|
225
|
+
- !ruby/object:Gem::Version
|
226
|
+
version: 1.2.1
|
227
|
+
type: :runtime
|
228
|
+
prerelease: false
|
229
|
+
version_requirements: !ruby/object:Gem::Requirement
|
230
|
+
requirements:
|
231
|
+
- - "~>"
|
232
|
+
- !ruby/object:Gem::Version
|
233
|
+
version: '1.2'
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: 1.2.1
|
203
237
|
- !ruby/object:Gem::Dependency
|
204
238
|
name: rspec
|
205
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -306,6 +340,7 @@ files:
|
|
306
340
|
- ".ruby-version"
|
307
341
|
- ".simplecov"
|
308
342
|
- ".travis.yml"
|
343
|
+
- CHANGELOG.md
|
309
344
|
- Gemfile
|
310
345
|
- LICENSE.md
|
311
346
|
- README.md
|