aws_assume_role 0.1.2 → 0.2.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 +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
|