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 +4 -4
- data/.rubocop.yml +2 -1
- data/.travis.yml +12 -2
- data/CHANGELOG.md +13 -0
- data/README.md +1 -0
- data/Rakefile +15 -1
- data/lib/awskeyring.rb +9 -1
- data/lib/awskeyring/validate.rb +32 -0
- data/lib/awskeyring/version.rb +1 -1
- data/lib/awskeyring_command.rb +84 -15
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aadd847341fb6a4dd7016dfca3fdd1ba60fe4c9
|
4
|
+
data.tar.gz: a8e548fb70b847b3fc7d7e1a277a4d12a054029c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60a505a585398dc8e6034bbc51c4deef834f650fb76a042bc248f338dfbdb3e6354dab42d2536f404f93bee4785f21dfe5bc9fd4fb2c2a3b7cad52a297fc1414
|
7
|
+
data.tar.gz: 8a282201ceb09a170e1beeb2dd3ecc57dc4058ec669d5b3cb9cc1d9ce33a99ad6b3c09ad42e5c1146f504ee6559eb4c258857961a5e864709a8a5741d5bd15e0
|
data/.rubocop.yml
CHANGED
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:
|
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
|
-
|
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
|
data/lib/awskeyring/version.rb
CHANGED
data/lib/awskeyring_command.rb
CHANGED
@@ -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 " #{
|
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 =
|
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 =
|
91
|
-
key =
|
92
|
-
secret =
|
93
|
-
|
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 =
|
108
|
-
arn =
|
109
|
-
account =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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.
|
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-
|
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
|