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

Sign up to get free protection for your applications and to get access to all the features.
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