aws-session-credentials 0.1.1 → 1.0.0.pre.1

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDkxYjJmODhkZjdiNTRlMzFlOTU0OGYzMmQ1NTBiNGIxYzc0OTU3MQ==
4
+ ZThmODRmYTdkNDdmN2IzNThlMGZjNzVjOTg3MzI1MDhmMzM5NDUwZg==
5
5
  data.tar.gz: !binary |-
6
- NzIyZjQ2ODM1NjlkM2JmMGZlMzAxZGVkODg2YjQ4NTBjNjAxOWY3Zg==
6
+ OWZmM2I4YjQxM2E1NTVjZjc4YjlkNmE1NGFkZTc3NDUwNGE3Y2ZiMg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YjFjMDEyNjRkOWQ4ZDZiNTljNTRjMjg1NWUyMjQ4NmM0MWRiODlhODkxNTI2
10
- OGM1MjZjZGQzN2QzYWMyMzY2NzBjYWM1ZjI1ZThiMmM4MWJmNTA3YWYxNWIz
11
- NTg2MzRkOGRiNzRiN2YxMTFjMjZlZDNiMzUyZDhmNzM3ZDAyMDQ=
9
+ ZTA0MWMzNTY1NjEyODg1NmE3MWQ1MjE4OGU5YjU3YjMwZGVjYzQyNDZhNmY4
10
+ MWJiMjI3NTMzZGVjMTNkYTI4NjcwZDA0ZTBiZmY3ZGRmODNiMWVhZmU2NzNl
11
+ NDJjZWI2NmFmMzgyYzkyZGU1YTM3NjQxMDU4YmUzYjQzNjNhOGE=
12
12
  data.tar.gz: !binary |-
13
- ZDA1MmJjNGRiY2Y5MWRkMDI4NDU4NGY3MTljYjY1YmQ3MGE1OTUxZTU4YzQ4
14
- M2I3Njk1Njk4YTIzMzY2ZjYxZDliNTI4YWRmMGExZDZlYmNjMjA2ZGU4Yzkw
15
- NWZmMTBhOGQxN2Y5ZDU0ODkyNzYwOGI3ZjM0ZmI1ODVmZTcxOTA=
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: gem install bundler -v 1.10.6
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
- [--access-key-id=ACCESS-KEY-ID] # Access key used to generate session token
45
- [--secret-access-key=SECRET-ACCESS-KEY] # Secret key used to generate session token
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
- ### Config File
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
- By default this is located at `~/.aws/credentials.yml`.
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/credentials.yml'
25
- method_option 'credential-file',
24
+ default: '~/.aws/aws-session-config.yml'
25
+ method_option 'source-profile',
26
26
  type: :string,
27
- desc: 'INI file to save session credentials to',
28
- default: '~/.aws/credentials'
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: 1
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
- config = Config.new(options['config-file'])
48
- config.aws_access_key_id ||= options['access-key-id']
49
- config.aws_secret_access_key ||= options['secret-access-key']
50
- config.region ||= options['region']
51
- config.credential_file ||= options['credential-file']
52
- config.profile ||= options['profile']
53
- config.duration ||= options['duration']
54
- config.mfa_device ||= options['mfa-device']
55
- config.mfa_code ||= options['mfa-code']
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
- cf = CredentialFile.new(config.credential_file)
58
- sb = SessionBuilder.new(config.to_h)
59
- sb.update_credential_file(cf)
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
- def initialize(path, config = nil)
7
- @path = File.expand_path(path) if path
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
- def profile
74
- self['profile']
75
- end
9
+ attr_reader :path
76
10
 
77
- def profile=(value)
78
- self['profile'] = value
11
+ def initialize(options = {})
12
+ @path = File.expand_path(options[:path] || default_path)
79
13
  end
80
14
 
81
- def region
82
- self['region']
15
+ def default_path
16
+ File.join(%w(~ .aws aws-session-config.yml))
83
17
  end
84
18
 
85
- def region=(value)
86
- self['region'] = value
19
+ # @return [Hash<String,Hash>]
20
+ def profiles_hash
21
+ self[:profiles] || {}
87
22
  end
88
23
 
89
- def to_h
90
- @config
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
- # AWS credentials file. Usually located on disk at +~/.aws/credentials+.
4
+ # Holds credentials that are read by AWS SDKs
5
5
  class CredentialFile
6
- # @param [String] path location of credentials file on disk
7
- # @param [IniFile] ini_file
8
- def initialize(path = '~/.aws/credentials', ini_file = nil)
9
- @path = File.expand_path(path)
10
- @ini_file = ini_file || init_ini_file
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
- # @api private
14
- def init_ini_file
15
- if File.exist?(@path)
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
- # Sets credentials for provided profile.
25
- #
26
- # Overrides provided options if they already exist for the provided
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,13 @@
1
+ module Aws
2
+ module Session
3
+ module Credentials
4
+ class Profile < OpenStruct
5
+ def aws_credentials
6
+ Aws::Credentials.new(aws_access_key_id,
7
+ aws_secret_access_key,
8
+ aws_session_token)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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] config configuration
7
- # @param [Aws::STS::Client] client STS client
8
- def initialize(config, client = nil)
9
- @config = config
10
- @client = client || init_client
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
- # @api private
14
- def init_client
15
- Aws::STS::Client.new(
16
- region: @config['region'],
17
- access_key_id: @config['aws_access_key_id'],
18
- secret_access_key: @config['aws_secret_access_key']
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
- # Gets a set of session credentials
23
- #
24
- # @return [Aws::STS::Types::Credentials] credentials or +nil+
25
- def session_credentials
26
- resp = @client.get_session_token(
27
- duration_seconds: @config['duration'],
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
- 'aws_access_key_id' => resp.credentials['access_key_id'],
33
- 'aws_secret_access_key' => resp.credentials['secret_access_key'],
34
- 'aws_session_token' => resp.credentials['session_token']
35
- } if resp
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
- # Gets a set of session credentials and updates them in a credential
39
- # file.
40
- #
41
- # @param [String] path location of credential file
42
- def update_credential_file(credential_file)
43
- credential_file.set_credentials(@config['profile'], session_credentials)
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,7 +1,7 @@
1
1
  module Aws
2
2
  module Session
3
3
  module Credentials
4
- VERSION = '0.1.1'
4
+ VERSION = '1.0.0.pre.1'
5
5
  end
6
6
  end
7
7
  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.1.1
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-10-30 00:00:00.000000000 Z
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: '0'
204
+ version: 1.3.1
155
205
  requirements: []
156
206
  rubyforge_project:
157
207
  rubygems_version: 2.4.5