awskeyring 0.0.5 → 0.0.6

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: f560b1fef85ec599c9debb10eace0e76746e64d7
4
- data.tar.gz: 356f577f81a1fd72ef6571e8aaf64e6bd6829a95
3
+ metadata.gz: 2aadd847341fb6a4dd7016dfca3fdd1ba60fe4c9
4
+ data.tar.gz: a8e548fb70b847b3fc7d7e1a277a4d12a054029c
5
5
  SHA512:
6
- metadata.gz: 7cc1ff83f60d58b3f5c9fd9ec104657425f5719649a6b28e88ce8fd6ccd46e6f550e64314129cfbeca4c0e78c0c5a809b30c93642a53f887ef2bcf2e6a2aef9f
7
- data.tar.gz: 70f3b813b8f6c3886bb83cc290e5b065351a16f365f00a2d40761811e18bf45a4768d732443e54c195e6069b6a4c6544e1117dade9a4fc9eb5acc25db85e728b
6
+ metadata.gz: 60a505a585398dc8e6034bbc51c4deef834f650fb76a042bc248f338dfbdb3e6354dab42d2536f404f93bee4785f21dfe5bc9fd4fb2c2a3b7cad52a297fc1414
7
+ data.tar.gz: 8a282201ceb09a170e1beeb2dd3ecc57dc4058ec669d5b3cb9cc1d9ce33a99ad6b3c09ad42e5c1146f504ee6559eb4c258857961a5e864709a8a5741d5bd15e0
data/.rubocop.yml CHANGED
@@ -1,3 +1,4 @@
1
+ ---
1
2
  Metrics/LineLength:
2
3
  Max: 120
3
4
 
@@ -6,7 +7,7 @@ Metrics/MethodLength:
6
7
 
7
8
  Metrics/BlockLength:
8
9
  Exclude:
9
- - spec/*/*
10
+ - spec/**/*
10
11
 
11
12
  Naming/FileName:
12
13
  Exclude:
data/.travis.yml CHANGED
@@ -1,8 +1,18 @@
1
+ ---
1
2
  language: ruby
2
3
  os: osx
3
4
  rvm:
4
- - 2.3.3
5
+ - 2.3.3
5
6
  before_install: gem install bundler
6
7
  notifications:
7
8
  slack:
8
- secure: okKLlQ93ogj8ut18MZazQD63XTIzCpTfneYQMlKPaLU5HnooAkUFmEpSN8igTJD0GuEQ7Jf8+BkFlhECWl1FwtzIW6z/yMadiiLwY6qA/O1ZVVYZmkS4kpcKtCMrvO0Hf3iPLXpJX0sQGfGsMsho6NZuNs0dzlrr4+HX/cEGmDXBocDDxdv2d25HzHtqt4l+4axeJ+PJdHDmYlDzhtMAXhkGoPfzws7MPvkVcqY0eZRW2WqccO52zlQrBNcphp7BI8mLTW/BwkEY9YndJf2xoBaoEOuaIJbGgHbmskGMRui3vZnd08/fiWkNsCI/EhB5BDJ41bwobsHEtuqu+Qx92kI+hzjtU4D16k1v/6HDm61T6gh6BTp0QEXJeOkiecjVGyzrMlBf5BEyIB7JfLvGPa7RUbvvvUxFGcEtnSbUZ49cXAG7cKiiSgcNOocCrMNIRAe864Tm606Mud1RbOv8tEiS9BJ96ktOpOfvrYPyYMRFTte2gRLwNDwcpXIWTI39CXD2EBxSfRK/OqmggLYMG5AKhxyS/Vl7E2p9gii6syB0LnpFBIZ6t+OSve7fZtCU/wruC5IrI/vHtWfApvADYKsXG6FEYJoJ1LsCDjIRlrkwV1dEVsp/HSSO6zRh9KhJ3b7yzL97Dyi6dCi+nM7aKGrox/8kvcn9alrdRt8sz3c=
9
+ secure: "okKLlQ93ogj8ut18MZazQD63XTIzCpTfneYQMlKPaLU5HnooAkUFmEpSN8ig\
10
+ TJD0GuEQ7Jf8+BkFlhECWl1FwtzIW6z/yMadiiLwY6qA/O1ZVVYZmkS4kpcKtCMrvO0Hf3iPL\
11
+ XpJX0sQGfGsMsho6NZuNs0dzlrr4+HX/cEGmDXBocDDxdv2d25HzHtqt4l+4axeJ+PJdHDmYl\
12
+ DzhtMAXhkGoPfzws7MPvkVcqY0eZRW2WqccO52zlQrBNcphp7BI8mLTW/BwkEY9YndJf2xoBa\
13
+ oEOuaIJbGgHbmskGMRui3vZnd08/fiWkNsCI/EhB5BDJ41bwobsHEtuqu+Qx92kI+hzjtU4D1\
14
+ 6k1v/6HDm61T6gh6BTp0QEXJeOkiecjVGyzrMlBf5BEyIB7JfLvGPa7RUbvvvUxFGcEtnSbUZ\
15
+ 49cXAG7cKiiSgcNOocCrMNIRAe864Tm606Mud1RbOv8tEiS9BJ96ktOpOfvrYPyYMRFTte2gR\
16
+ LwNDwcpXIWTI39CXD2EBxSfRK/OqmggLYMG5AKhxyS/Vl7E2p9gii6syB0LnpFBIZ6t+OSve7\
17
+ fZtCU/wruC5IrI/vHtWfApvADYKsXG6FEYJoJ1LsCDjIRlrkwV1dEVsp/HSSO6zRh9KhJ3b7y\
18
+ zL97Dyi6dCi+nM7aKGrox/8kvcn9alrdRt8sz3c="
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.0.6](https://github.com/vibrato/awskeyring/tree/v0.0.6) (2018-03-01)
4
+ [Full Changelog](https://github.com/vibrato/awskeyring/compare/v0.0.5...v0.0.6)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Credential Rotation Feature [\#4](https://github.com/vibrato/awskeyring/issues/4)
9
+ - Rotate credentials feature. [\#11](https://github.com/vibrato/awskeyring/pull/11) ([tristanmorgan](https://github.com/tristanmorgan))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Input validation [\#10](https://github.com/vibrato/awskeyring/pull/10) ([tristanmorgan](https://github.com/tristanmorgan))
14
+ - Adding a check for incorrect file modes. [\#9](https://github.com/vibrato/awskeyring/pull/9) ([tristanmorgan](https://github.com/tristanmorgan))
15
+
3
16
  ## [v0.0.5](https://github.com/vibrato/awskeyring/tree/v0.0.5) (2018-02-15)
4
17
  [Full Changelog](https://github.com/vibrato/awskeyring/compare/v0.0.4...v0.0.5)
5
18
 
data/README.md CHANGED
@@ -56,6 +56,7 @@ The CLI is using [Thor](http://whatisthor.com) with help provided interactively.
56
56
  awskeyring remove ACCOUNT # Removes an ACCOUNT from the keyring
57
57
  awskeyring remove-role ROLE # Removes a ROLE from the keyring
58
58
  awskeyring remove-token ACCOUNT # Removes a token for ACCOUNT from the keyring
59
+ awskeyring rotate ACCOUNT # Rotate access keys for an ACCOUNT
59
60
  awskeyring token ACCOUNT [ROLE] [MFA] # Create an STS Token from a ROLE or an MFA code
60
61
 
61
62
  and autocomplete that can be installed with:
data/Rakefile CHANGED
@@ -13,4 +13,18 @@ end
13
13
 
14
14
  RSpec::Core::RakeTask.new(:spec)
15
15
 
16
- task default: %i[rubocop spec]
16
+ desc 'Check filemode bits'
17
+ task :filemode do
18
+ files = Dir.glob('**/*')
19
+ failure = false
20
+ files.each do |file|
21
+ mode = File.stat(file).mode
22
+ if (mode & 0x7) != (mode >> 3 & 0x7)
23
+ puts file
24
+ failure = true
25
+ end
26
+ end
27
+ abort 'Error: Incorrect file mode found' if failure
28
+ end
29
+
30
+ task default: %i[filemode rubocop spec]
data/lib/awskeyring.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  require 'keychain'
2
2
  require 'aws-sdk-iam'
3
+ require 'awskeyring/validate'
3
4
 
4
5
  # Aws Key-ring logical object,
5
6
  # gives you an interface to access keychains and items.
6
- module Awskeyring
7
+ module Awskeyring # rubocop:disable Metrics/ModuleLength
7
8
  PREFS_FILE = (File.expand_path '~/.awskeyring').freeze
8
9
  ROLE_PREFIX = 'role '.freeze
9
10
  ACCOUNT_PREFIX = 'account '.freeze
@@ -65,6 +66,13 @@ module Awskeyring
65
66
  )
66
67
  end
67
68
 
69
+ def self.update_item(account:, key:, secret:)
70
+ item = get_item(account)
71
+ item.attributes[:account] = key
72
+ item.password = secret
73
+ item.save!
74
+ end
75
+
68
76
  def self.add_role(role:, arn:, account:)
69
77
  all_items.create(
70
78
  label: "#{ROLE_PREFIX}#{role}",
@@ -0,0 +1,32 @@
1
+ # Validation methods
2
+ module Awskeyring
3
+ def self.account_name(account_name)
4
+ raise 'Invalid Account Name' unless account_name =~ /\S+/
5
+ account_name
6
+ end
7
+
8
+ def self.access_key(aws_access_key)
9
+ raise 'Invalid Access Key' unless aws_access_key =~ /\AAKIA[A-Z0-9]{12,16}\z/
10
+ aws_access_key
11
+ end
12
+
13
+ def self.secret_access_key(aws_secret_access_key)
14
+ raise 'Secret Access Key is not 40 chars' if aws_secret_access_key.length != 40
15
+ aws_secret_access_key
16
+ end
17
+
18
+ def self.mfa_arn(mfa_arn)
19
+ raise 'Invalid MFA ARN' unless mfa_arn =~ %r(\Aarn:aws:iam::[0-9]{12}:mfa\/\S*\z)
20
+ mfa_arn
21
+ end
22
+
23
+ def self.role_name(account_name)
24
+ raise 'Invalid Role Name' unless account_name =~ /\S+/
25
+ account_name
26
+ end
27
+
28
+ def self.role_arn(role_arn)
29
+ raise 'Invalid Role ARN' unless role_arn =~ %r(\Aarn:aws:iam::[0-9]{12}:role\/\S*\z)
30
+ role_arn
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Awskeyring
2
- VERSION = '0.0.5'.freeze
2
+ VERSION = '0.0.6'.freeze
3
3
  end
@@ -37,11 +37,13 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
37
37
  puts 'Creating a new Keychain, you will be prompted for a password for it.'
38
38
  Awskeyring.init_keychain(awskeyring: keychain)
39
39
 
40
+ exec_name = File.basename($PROGRAM_NAME)
41
+
40
42
  puts 'Your keychain has been initialised. It will auto-lock after 5 minutes'
41
43
  puts 'and when sleeping. Use Keychain Access to adjust.'
42
44
  puts
43
45
  puts "Add accounts to your #{keychain} keychain with:"
44
- puts " #{$PROGRAM_NAME} add"
46
+ puts " #{exec_name} add"
45
47
  end
46
48
 
47
49
  desc 'list', 'Prints a list of accounts in the keyring'
@@ -57,7 +59,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
57
59
 
58
60
  desc 'env ACCOUNT', 'Outputs bourne shell environment exports for an ACCOUNT'
59
61
  def env(account = nil)
60
- account = ask_missing(existing: account, message: 'account name')
62
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
61
63
  cred, temp_cred = get_valid_item_pair(account: account)
62
64
  token = temp_cred.password unless temp_cred.nil?
63
65
  put_env_string(
@@ -86,11 +88,16 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
86
88
  method_option :key, type: :string, aliases: '-k', desc: 'AWS account key id.'
87
89
  method_option :secret, type: :string, aliases: '-s', desc: 'AWS account secret.'
88
90
  method_option :mfa, type: :string, aliases: '-m', desc: 'AWS virtual mfa arn.'
89
- def add(account = nil)
90
- account = ask_missing(existing: account, message: 'account name')
91
- key = ask_missing(existing: options[:key], message: 'access key id')
92
- secret = ask_missing(existing: options[:secret], message: 'secret access key', secure: true)
93
- mfa = ask_missing(existing: options[:mfa], message: 'mfa arn', optional: true)
91
+ def add(account = nil) # rubocop:disable Metrics/AbcSize
92
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
93
+ key = ask_check(existing: options[:key], message: 'access key id', validator: Awskeyring.method(:access_key))
94
+ secret = ask_check(
95
+ existing: options[:secret], message: 'secret access key',
96
+ secure: true, validator: Awskeyring.method(:secret_access_key)
97
+ )
98
+ mfa = ask_check(
99
+ existing: options[:mfa], message: 'mfa arn', optional: true, validator: Awskeyring.method(:mfa_arn)
100
+ )
94
101
 
95
102
  Awskeyring.add_item(
96
103
  account: account,
@@ -98,33 +105,37 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
98
105
  secret: secret,
99
106
  comment: mfa
100
107
  )
108
+ puts "# Added account #{account}"
101
109
  end
102
110
 
103
111
  map 'add-role' => :add_role
104
112
  desc 'add-role ROLE', 'Adds a ROLE to the keyring'
105
113
  method_option :arn, type: :string, aliases: '-a', desc: 'AWS role arn.'
106
114
  def add_role(role = nil)
107
- role = ask_missing(existing: role, message: 'role name')
108
- arn = ask_missing(existing: options[:arn], message: 'role arn')
109
- account = ask_missing(existing: account, message: 'account', optional: true)
115
+ role = ask_check(existing: role, message: 'role name', validator: Awskeyring.method(:role_name))
116
+ arn = ask_check(existing: options[:arn], message: 'role arn', validator: Awskeyring.method(:role_arn))
117
+ account = ask_check(
118
+ existing: account, message: 'account', optional: true, validator: Awskeyring.method(:account_name)
119
+ )
110
120
 
111
121
  Awskeyring.add_role(
112
122
  role: role,
113
123
  arn: arn,
114
124
  account: account
115
125
  )
126
+ puts "# Added role #{role}"
116
127
  end
117
128
 
118
129
  desc 'remove ACCOUNT', 'Removes an ACCOUNT from the keyring'
119
130
  def remove(account = nil)
120
- account = ask_missing(existing: account, message: 'account name')
131
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
121
132
  cred, temp_cred = get_valid_item_pair(account: account)
122
133
  Awskeyring.delete_pair(cred, temp_cred, "# Removing account #{account}")
123
134
  end
124
135
 
125
136
  desc 'remove-token ACCOUNT', 'Removes a token for ACCOUNT from the keyring'
126
137
  def remove_token(account = nil)
127
- account = ask_missing(existing: account, message: 'account name')
138
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
128
139
  session_key, session_token = Awskeyring.get_pair(account)
129
140
  session_key, session_token = Awskeyring.delete_expired(session_key, session_token) if session_key
130
141
  Awskeyring.delete_pair(session_key, session_token, "# Removing token for account #{account}") if session_key
@@ -133,17 +144,47 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
133
144
  map 'remove-role' => :remove_role
134
145
  desc 'remove-role ROLE', 'Removes a ROLE from the keyring'
135
146
  def remove_role(role = nil)
136
- role = ask_missing(existing: role, message: 'role name')
147
+ role = ask_check(existing: role, message: 'role name', validator: Awskeyring.method(:role_name))
137
148
  item_role = Awskeyring.get_role(role)
138
149
  Awskeyring.delete_pair(item_role, nil, "# Removing role #{role}")
139
150
  end
140
151
 
152
+ desc 'rotate ACCOUNT', 'Rotate access keys for an ACCOUNT'
153
+ def rotate(account = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
154
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
155
+ item = Awskeyring.get_item(account)
156
+ iam = Aws::IAM::Client.new(access_key_id: item.attributes[:account], secret_access_key: item.password)
157
+
158
+ if iam.list_access_keys[:access_key_metadata].length > 1
159
+ warn "You have two access keys for account #{account}"
160
+ exit 1
161
+ end
162
+
163
+ new_key = iam.create_access_key
164
+ iam = Aws::IAM::Client.new(
165
+ access_key_id: new_key[:access_key][:access_key_id],
166
+ secret_access_key: new_key[:access_key][:secret_access_key]
167
+ )
168
+ retry_backoff do
169
+ iam.delete_access_key(
170
+ access_key_id: item.attributes[:account]
171
+ )
172
+ end
173
+ Awskeyring.update_item(
174
+ account: account,
175
+ key: new_key[:access_key][:access_key_id],
176
+ secret: new_key[:access_key][:secret_access_key]
177
+ )
178
+
179
+ puts "# Updated account #{account}"
180
+ end
181
+
141
182
  desc 'token ACCOUNT [ROLE] [MFA]', 'Create an STS Token from a ROLE or an MFA code'
142
183
  method_option :role, type: :string, aliases: '-r', desc: 'The ROLE to assume.'
143
184
  method_option :code, type: :string, aliases: '-c', desc: 'Virtual mfa CODE.'
144
185
  method_option :duration, type: :string, aliases: '-d', desc: 'Session DURATION in seconds.'
145
186
  def token(account = nil, role = nil, code = nil) # rubocop:disable all
146
- account = ask_missing(existing: account, message: 'account name')
187
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
147
188
  role ||= options[:role]
148
189
  code ||= options[:code]
149
190
  duration = options[:duration]
@@ -206,7 +247,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
206
247
  desc 'console ACCOUNT', 'Open the AWS Console for the ACCOUNT'
207
248
  method_option :path, type: :string, aliases: '-p', desc: 'The service PATH to open.'
208
249
  def console(account = nil) # rubocop:disable all
209
- account = ask_missing(existing: account, message: 'account name')
250
+ account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
210
251
  cred, temp_cred = get_valid_item_pair(account: account)
211
252
  token = temp_cred.password unless temp_cred.nil?
212
253
 
@@ -334,6 +375,34 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
334
375
  puts 'unset AWS_SESSION_TOKEN' unless token
335
376
  end
336
377
 
378
+ def ask_check(existing:, message:, secure: false, optional: false, validator: nil)
379
+ retries ||= 3
380
+ begin
381
+ value = ask_missing(existing: existing, message: message, secure: secure, optional: optional)
382
+ value = validator.call(value) unless value.empty? && optional
383
+ rescue RuntimeError => e
384
+ warn e.message
385
+ retry unless (retries -= 1).zero?
386
+ exit 1
387
+ end
388
+ value
389
+ end
390
+
391
+ def retry_backoff(&block)
392
+ retries ||= 1
393
+ begin
394
+ yield block
395
+ rescue Aws::IAM::Errors::InvalidClientTokenId => e
396
+ if retries < 4
397
+ sleep 2**retries
398
+ retries += 1
399
+ retry
400
+ end
401
+ warn e.message
402
+ exit 1
403
+ end
404
+ end
405
+
337
406
  def ask_missing(existing:, message:, secure: false, optional: false)
338
407
  existing || ask(message: message, secure: secure, optional: optional)
339
408
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awskeyring
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Morgan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-15 00:00:00.000000000 Z
11
+ date: 2018-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-iam
@@ -157,6 +157,7 @@ files:
157
157
  - awskeyring.gemspec
158
158
  - exe/awskeyring
159
159
  - lib/awskeyring.rb
160
+ - lib/awskeyring/validate.rb
160
161
  - lib/awskeyring/version.rb
161
162
  - lib/awskeyring_command.rb
162
163
  homepage: https://github.com/vibrato/awskeyring