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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 740d4539f3f7bf68b7acedc131890496b67eaef4
4
- data.tar.gz: a5f739bbb05c1cd1edfc59e156ee2a348c88c9ff
3
+ metadata.gz: a54b02fa08cf71de92e8cae70bc4b8d0b7f3c6cc
4
+ data.tar.gz: d97b2748840f3a481eb388b5d52be5ab3631e34d
5
5
  SHA512:
6
- metadata.gz: 0898326d54de021a939f2375466c7376a3e42cca8b7c597d4b03a9b11b4a407b078b4560ec3790418c462a84bbde0836a2f9ea30acf5e1b3cebfab25e2567a55
7
- data.tar.gz: f161cc86abf84c2e893cc9426bea9861b02b30bd31fccbf2ed8f7ac526252d51f236e530b5a178fffde1db94ef910a7cf87fc22c9645f8685f0eaa49eeb58c24
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
 
@@ -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 "Cannot find any credentials"
31
+ error t("errors.MissingCredentialsError")
29
32
  exit 404
30
33
  end
31
34
 
@@ -7,6 +7,7 @@ class AwsAssumeRole::Cli::Actions::ConfigureProfile < AwsAssumeRole::Cli::Action
7
7
  optional(:region) { filled? > format?(REGION_REGEX) }
8
8
  optional(:mfa_serial)
9
9
  optional(:profile_name)
10
+ optional(:yubikey_oath_name)
10
11
  end
11
12
 
12
13
  def act_on(config)
@@ -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(first_time)
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(@first_time)
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 = { credentials: opts[:credentials], region: opts[:region], serial_number: opts[:serial_number] }
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
@@ -1,3 +1,3 @@
1
1
  module AwsAssumeRole
2
- VERSION = "0.1.2".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  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.1.2
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-02-20 00:00:00.000000000 Z
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