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 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