awskeyring 0.0.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +22 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +11 -0
- data/awskeyring.gemspec +30 -0
- data/exe/awskeyring +10 -0
- data/lib/awskeyring/version.rb +3 -0
- data/lib/awskeyring.rb +127 -0
- data/lib/awskeyring_command.rb +314 -0
- metadata +171 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 45a2721acc4cc5f198c2bf55ee253dcb9f519093
|
|
4
|
+
data.tar.gz: 6ea5cdc339047ba1e057a46a8733d90ea5af77da
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0ae669db134da8e9ba7ecff535bb3e3410e6f314358a818a925761e500f3eac220f63bd740ec72906c83040357dd324495c307550f30b91a4f7aab61834a4eee
|
|
7
|
+
data.tar.gz: 6b005ea78e4fb94f21566edc1c702d75c8e5f2308b21f8bac6fd02329332e0dbd71394d301bf2852c6f82aa2df42dc7b7ffbb6229fdd21ca9a618e90f7a15f04
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metrics/AbcSize:
|
|
2
|
+
Max: 31
|
|
3
|
+
|
|
4
|
+
Metrics/LineLength:
|
|
5
|
+
Max: 120
|
|
6
|
+
|
|
7
|
+
Metrics/ClassLength:
|
|
8
|
+
Exclude:
|
|
9
|
+
- lib/awskeyring_command.rb
|
|
10
|
+
|
|
11
|
+
Metrics/MethodLength:
|
|
12
|
+
Max: 16
|
|
13
|
+
|
|
14
|
+
Naming/FileName:
|
|
15
|
+
Exclude:
|
|
16
|
+
- Gemfile
|
|
17
|
+
- Rakefile
|
|
18
|
+
- aws-keychain-util.gemspec
|
|
19
|
+
|
|
20
|
+
AllCops:
|
|
21
|
+
Exclude:
|
|
22
|
+
- bin/*
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributor Code of Conduct
|
|
2
|
+
|
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
|
7
|
+
|
|
8
|
+
We are committed to making participation in this project a harassment-free
|
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
|
12
|
+
|
|
13
|
+
Examples of unacceptable behavior by participants include:
|
|
14
|
+
|
|
15
|
+
* The use of sexualized language or imagery
|
|
16
|
+
* Personal attacks
|
|
17
|
+
* Trolling or insulting/derogatory comments
|
|
18
|
+
* Public or private harassment
|
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
|
20
|
+
addresses, without explicit permission
|
|
21
|
+
* Other unethical or unprofessional conduct
|
|
22
|
+
|
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
27
|
+
threatening, offensive, or harmful.
|
|
28
|
+
|
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
|
32
|
+
Conduct may be permanently removed from the project team.
|
|
33
|
+
|
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
|
35
|
+
when an individual is representing the project or its community.
|
|
36
|
+
|
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
38
|
+
reported by contacting a project maintainer at tristanmorgan@users.noreply.github.com. All
|
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
|
42
|
+
incident.
|
|
43
|
+
|
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
45
|
+
version 1.3.0, available at
|
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
|
47
|
+
|
|
48
|
+
[homepage]: http://contributor-covenant.org
|
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Tristan Morgan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Awskeyring
|
|
2
|
+
|
|
3
|
+
Awskeyring is a small tool to manage AWS account keys in the macOS Keychain.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install it with:
|
|
8
|
+
|
|
9
|
+
$ gem install awskeyring
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The CLI is using [Thor](http://whatisthor.com) with help provided interactivly.
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
awskeyring --version, -v # Prints the version
|
|
17
|
+
awskeyring add ACCOUNT # Adds an ACCOUNT to the keyring
|
|
18
|
+
awskeyring add-role ROLE # Adds a ROLE to the keyring
|
|
19
|
+
awskeyring console ACCOUNT # Open the AWS Console for the ACCOUNT
|
|
20
|
+
awskeyring env ACCOUNT # Outputs bourne shell environment exports for an ACCOUNT
|
|
21
|
+
awskeyring help [COMMAND] # Describe available commands or one specific command
|
|
22
|
+
awskeyring initialise # Initialises a new KEYCHAIN
|
|
23
|
+
awskeyring list # Prints a list of accounts in the keyring
|
|
24
|
+
awskeyring list-role # Prints a list of roles in the keyring
|
|
25
|
+
awskeyring remove ACCOUNT # Removes an ACCOUNT from the keyring
|
|
26
|
+
awskeyring remove-role ROLE # Removes a ROLE from the keyring
|
|
27
|
+
awskeyring token ACCOUNT [ROLE] [MFA] # Create an STS Token from a ROLE or an MFA code
|
|
28
|
+
|
|
29
|
+
and autocomplete that can be installed with:
|
|
30
|
+
|
|
31
|
+
$ complete -C /usr/local/bin/aws-creds aws-creds
|
|
32
|
+
|
|
33
|
+
To set your environment easily the following function helps:
|
|
34
|
+
|
|
35
|
+
awsenv() { eval "$(awskeyring env $1)"; }
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec awskeyring` to use the gem in this directory, ignoring other installed copies of this gem.
|
|
40
|
+
|
|
41
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
42
|
+
|
|
43
|
+
## Contributing
|
|
44
|
+
|
|
45
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tristanmorgan/awskeyring. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
51
|
+
|
data/Rakefile
ADDED
data/awskeyring.gemspec
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'awskeyring/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'awskeyring'
|
|
7
|
+
spec.version = Awskeyring::VERSION
|
|
8
|
+
spec.authors = ['Tristan Morgan']
|
|
9
|
+
spec.email = ['tristanmorgan@users.noreply.github.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Manages AWS credentials in the OS X keychain'
|
|
12
|
+
spec.description = 'Manages AWS credentials in the OS X keychain'
|
|
13
|
+
spec.homepage = 'https://github.com/tristanmorgan/awskeyring'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = 'exe'
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
spec.add_dependency('aws-sdk-iam')
|
|
22
|
+
spec.add_dependency('highline')
|
|
23
|
+
spec.add_dependency('ruby-keychain')
|
|
24
|
+
spec.add_dependency('thor')
|
|
25
|
+
|
|
26
|
+
spec.add_development_dependency 'bundler'
|
|
27
|
+
spec.add_development_dependency 'rake'
|
|
28
|
+
spec.add_development_dependency 'rspec'
|
|
29
|
+
spec.add_development_dependency 'rubocop'
|
|
30
|
+
end
|
data/exe/awskeyring
ADDED
data/lib/awskeyring.rb
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require 'keychain'
|
|
2
|
+
require 'aws-sdk-iam'
|
|
3
|
+
|
|
4
|
+
require 'awskeyring/version'
|
|
5
|
+
|
|
6
|
+
# Aws Key-ring logical object,
|
|
7
|
+
# gives you an interface to access keychains and items.
|
|
8
|
+
module Awskeyring
|
|
9
|
+
PREFS_FILE = (File.expand_path '~/.awskeyring').freeze
|
|
10
|
+
ROLE_PREFIX = 'role '.freeze
|
|
11
|
+
ACCOUNT_PREFIX = 'account '.freeze
|
|
12
|
+
|
|
13
|
+
def self.prefs
|
|
14
|
+
if File.exist? PREFS_FILE
|
|
15
|
+
JSON.parse(File.read(PREFS_FILE))
|
|
16
|
+
else
|
|
17
|
+
{}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.init_keychain(awskeyring:)
|
|
22
|
+
keychain = Keychain.create(awskeyring)
|
|
23
|
+
keychain.lock_interval = 300
|
|
24
|
+
keychain.lock_on_sleep = true
|
|
25
|
+
|
|
26
|
+
prefs = { awskeyring: awskeyring }
|
|
27
|
+
File.new(Awskeyring::PREFS_FILE, 'w').write JSON.dump(prefs)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.load_keychain
|
|
31
|
+
unless File.exist?(Awskeyring::PREFS_FILE) || prefs.empty?
|
|
32
|
+
warn "Config missing, run `#{$PROGRAM_NAME} initialise` to recreate."
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
keychain = Keychain.open(prefs['awskeyring'])
|
|
37
|
+
if keychain && keychain.lock_interval > 300
|
|
38
|
+
warn 'It is STRONGLY reccomended to set your keychain to lock in 5 minutes or less.'
|
|
39
|
+
end
|
|
40
|
+
keychain
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.list_items
|
|
44
|
+
items = all_items.all.sort do |a, b|
|
|
45
|
+
a.attributes[:label] <=> b.attributes[:label]
|
|
46
|
+
end
|
|
47
|
+
items.select { |elem| elem.attributes[:label].start_with?(ACCOUNT_PREFIX) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.list_roles
|
|
51
|
+
items = all_items.all.sort do |a, b|
|
|
52
|
+
a.attributes[:label] <=> b.attributes[:label]
|
|
53
|
+
end
|
|
54
|
+
items.select { |elem| elem.attributes[:label].start_with?(ROLE_PREFIX) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.all_items
|
|
58
|
+
load_keychain.generic_passwords
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.add_item(account:, key:, secret:, comment:)
|
|
62
|
+
all_items.create(
|
|
63
|
+
label: "#{ACCOUNT_PREFIX}#{account}",
|
|
64
|
+
account: key,
|
|
65
|
+
password: secret,
|
|
66
|
+
comment: comment
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.add_role(role:, arn:, account:)
|
|
71
|
+
all_items.create(
|
|
72
|
+
label: "#{ROLE_PREFIX}#{role}",
|
|
73
|
+
account: arn,
|
|
74
|
+
password: '',
|
|
75
|
+
comment: account
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.add_pair(account:, key:, secret:, token:, expiry:, role:)
|
|
80
|
+
all_items.create(label: "session-key #{account}",
|
|
81
|
+
account: key,
|
|
82
|
+
password: secret,
|
|
83
|
+
comment: "#{ROLE_PREFIX}#{role}")
|
|
84
|
+
all_items.create(label: "session-token #{account}",
|
|
85
|
+
account: expiry,
|
|
86
|
+
password: token,
|
|
87
|
+
comment: "#{ROLE_PREFIX}#{role}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.get_item(account)
|
|
91
|
+
all_items.where(label: "#{ACCOUNT_PREFIX}#{account}").first
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.get_role(name)
|
|
95
|
+
all_items.where(label: "#{ROLE_PREFIX}#{name}").first
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.get_pair(account)
|
|
99
|
+
session_key = all_items.where(label: "session-key #{account}").first
|
|
100
|
+
session_token = all_items.where(label: "session-token #{account}").first if session_key
|
|
101
|
+
[session_key, session_token]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.list_item_names
|
|
105
|
+
list_items.map { |elem| elem.attributes[:label][(ACCOUNT_PREFIX.length)..-1] }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.list_role_names
|
|
109
|
+
list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..-1] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.delete_expired(key, token)
|
|
113
|
+
expires_at = Time.at(token.attributes[:account].to_i)
|
|
114
|
+
if expires_at < Time.now
|
|
115
|
+
delete_pair(key, token, '# Removing expired session credentials')
|
|
116
|
+
key = nil
|
|
117
|
+
token = nil
|
|
118
|
+
end
|
|
119
|
+
[key, token]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.delete_pair(key, token, message)
|
|
123
|
+
puts message if message
|
|
124
|
+
token.delete if token
|
|
125
|
+
key.delete if key
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
require 'aws-sdk-iam'
|
|
2
|
+
require 'cgi'
|
|
3
|
+
require 'highline'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'open-uri'
|
|
6
|
+
require 'thor'
|
|
7
|
+
|
|
8
|
+
require_relative 'awskeyring'
|
|
9
|
+
|
|
10
|
+
# AWS Key-ring command line interface.
|
|
11
|
+
class AwskeyringCommand < Thor
|
|
12
|
+
map %w[--version -v] => :__version
|
|
13
|
+
map ['ls'] => :list
|
|
14
|
+
map ['lsr'] => :list_role
|
|
15
|
+
map ['rm'] => :remove
|
|
16
|
+
map ['rmr'] => :remove_role
|
|
17
|
+
|
|
18
|
+
desc '--version, -v', 'Prints the version'
|
|
19
|
+
def __version
|
|
20
|
+
puts Awskeyring::VERSION
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc 'initialise', 'Initialises a new KEYCHAIN'
|
|
24
|
+
method_option :keychain, type: :string, aliases: '-n', desc: 'Name of KEYCHAIN to initialise.'
|
|
25
|
+
def initialise
|
|
26
|
+
unless Awskeyring.prefs.empty?
|
|
27
|
+
puts "#{Awskeyring::PREFS_FILE} exists. no need to initialise."
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
keychain ||= options[:keychain]
|
|
32
|
+
keychain ||= ask(message: "Name for new keychain (default: 'awskeyring')")
|
|
33
|
+
keychain = 'awskeyring' if keychain.empty?
|
|
34
|
+
|
|
35
|
+
puts 'Creating a new Keychain, you will be prompted for a password for it.'
|
|
36
|
+
Awskeyring.init_keychain(awskeyring: keychain)
|
|
37
|
+
|
|
38
|
+
puts 'Your keychain has been initialised. It will auto-lock after 5 minutes'
|
|
39
|
+
puts 'and when sleeping. Use Keychain Access to adjust.'
|
|
40
|
+
puts
|
|
41
|
+
puts "Add accounts to your #{keychain} keychain with:"
|
|
42
|
+
puts " #{$PROGRAM_NAME} add"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'list', 'Prints a list of accounts in the keyring'
|
|
46
|
+
def list
|
|
47
|
+
puts Awskeyring.list_item_names.join("\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
map 'list-role' => :list_role
|
|
51
|
+
desc 'list-role', 'Prints a list of roles in the keyring'
|
|
52
|
+
def list_role
|
|
53
|
+
puts Awskeyring.list_role_names.join("\n")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
desc 'env ACCOUNT', 'Outputs bourne shell environment exports for an ACCOUNT'
|
|
57
|
+
def env(account = nil)
|
|
58
|
+
account ||= ask(message: 'account name')
|
|
59
|
+
cred, temp_cred = get_valid_item_pair(account: account)
|
|
60
|
+
token = temp_cred.password unless temp_cred.nil?
|
|
61
|
+
put_env_string(
|
|
62
|
+
account: cred.attributes[:label],
|
|
63
|
+
key: cred.attributes[:account],
|
|
64
|
+
secret: cred.password,
|
|
65
|
+
token: token
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc 'add ACCOUNT', 'Adds an ACCOUNT to the keyring'
|
|
70
|
+
method_option :key, type: :string, aliases: '-k', desc: 'AWS account key id.'
|
|
71
|
+
method_option :secret, type: :string, aliases: '-s', desc: 'AWS account secret.'
|
|
72
|
+
method_option :mfa, type: :string, aliases: '-m', desc: 'AWS virtual mfa arn.'
|
|
73
|
+
def add(account = nil)
|
|
74
|
+
account ||= ask(message: 'account name')
|
|
75
|
+
key ||= options[:key]
|
|
76
|
+
key ||= ask(message: 'access key id')
|
|
77
|
+
secret ||= options[:secret]
|
|
78
|
+
secret ||= ask(message: 'secret access key', secure: true)
|
|
79
|
+
mfa ||= options[:mfa]
|
|
80
|
+
mfa ||= ask(message: 'mfa arn', optional: true)
|
|
81
|
+
|
|
82
|
+
Awskeyring.add_item(
|
|
83
|
+
account: account,
|
|
84
|
+
key: key,
|
|
85
|
+
secret: secret,
|
|
86
|
+
comment: mfa
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
map 'add-role' => :add_role
|
|
91
|
+
desc 'add-role ROLE', 'Adds a ROLE to the keyring'
|
|
92
|
+
method_option :arn, type: :string, aliases: '-a', desc: 'AWS role arn.'
|
|
93
|
+
def add_role(role = nil)
|
|
94
|
+
role ||= ask(message: 'role name')
|
|
95
|
+
arn ||= options[:arn]
|
|
96
|
+
arn ||= ask(message: 'role arn')
|
|
97
|
+
account ||= ask(message: 'account', optional: true)
|
|
98
|
+
|
|
99
|
+
Awskeyring.add_role(
|
|
100
|
+
role: role,
|
|
101
|
+
arn: arn,
|
|
102
|
+
account: account
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
desc 'remove ACCOUNT', 'Removes an ACCOUNT from the keyring'
|
|
107
|
+
def remove(account = nil)
|
|
108
|
+
account ||= ask(message: 'account name')
|
|
109
|
+
cred, temp_cred = get_valid_item_pair(account: account)
|
|
110
|
+
Awskeyring.delete_pair(cred, temp_cred, "# Removing account #{account}")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
map 'remove-role' => :remove_role
|
|
114
|
+
desc 'remove-role ROLE', 'Removes a ROLE from the keyring'
|
|
115
|
+
def remove_role(role = nil)
|
|
116
|
+
role ||= ask(message: 'role name')
|
|
117
|
+
item_role = Awskeyring.get_role(role)
|
|
118
|
+
Awskeyring.delete_pair(item_role, nil, "# Removing role #{role}")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
desc 'token ACCOUNT [ROLE] [MFA]', 'Create an STS Token from a ROLE or an MFA code'
|
|
122
|
+
method_option :role, type: :string, aliases: '-r', desc: 'The ROLE to assume.'
|
|
123
|
+
method_option :code, type: :string, aliases: '-c', desc: 'Virtual mfa CODE.'
|
|
124
|
+
method_option :duration, type: :string, aliases: '-d', desc: 'Session DURATION in seconds.'
|
|
125
|
+
def token(account = nil, role = nil, code = nil)
|
|
126
|
+
account ||= ask(message: 'account name')
|
|
127
|
+
role ||= options[:role]
|
|
128
|
+
code ||= options[:code]
|
|
129
|
+
duration = options[:duration]
|
|
130
|
+
duration ||= (60 * 60 * 1).to_s if role
|
|
131
|
+
duration ||= (60 * 60 * 12).to_s if code
|
|
132
|
+
|
|
133
|
+
if !role && !code
|
|
134
|
+
warn 'Please use either a role or a code'
|
|
135
|
+
exit 2
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
session_key, session_token = Awskeyring.get_pair(account)
|
|
139
|
+
Awskeyring.delete_pair(session_key, session_token, '# Removing STS credentials') if session_key
|
|
140
|
+
|
|
141
|
+
item = Awskeyring.get_item(account)
|
|
142
|
+
item_role = Awskeyring.get_role(role) if role
|
|
143
|
+
|
|
144
|
+
sts = Aws::STS::Client.new(access_key_id: item.attributes[:account], secret_access_key: item.password)
|
|
145
|
+
|
|
146
|
+
begin
|
|
147
|
+
response =
|
|
148
|
+
if code && role
|
|
149
|
+
sts.assume_role(
|
|
150
|
+
duration_seconds: duration.to_i,
|
|
151
|
+
role_arn: item_role.attributes[:account],
|
|
152
|
+
role_session_name: ENV['USER'],
|
|
153
|
+
serial_number: item.attributes[:comment],
|
|
154
|
+
token_code: code
|
|
155
|
+
)
|
|
156
|
+
elsif role
|
|
157
|
+
sts.assume_role(
|
|
158
|
+
duration_seconds: duration.to_i,
|
|
159
|
+
role_arn: item_role.attributes[:account],
|
|
160
|
+
role_session_name: ENV['USER']
|
|
161
|
+
)
|
|
162
|
+
elsif code
|
|
163
|
+
sts.get_session_token(
|
|
164
|
+
duration_seconds: duration.to_i,
|
|
165
|
+
serial_number: item.attributes[:comment],
|
|
166
|
+
token_code: code
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
rescue Aws::STS::Errors::AccessDenied => e
|
|
170
|
+
puts e.to_s
|
|
171
|
+
exit 1
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
Awskeyring.add_pair(
|
|
175
|
+
account: account,
|
|
176
|
+
key: response.credentials[:access_key_id],
|
|
177
|
+
secret: response.credentials[:secret_access_key],
|
|
178
|
+
token: response.credentials[:session_token],
|
|
179
|
+
expiry: response.credentials[:expiration].to_i.to_s,
|
|
180
|
+
role: role
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
puts "Authentication valid until #{response.credentials[:expiration]}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
desc 'console ACCOUNT', 'Open the AWS Console for the ACCOUNT'
|
|
187
|
+
def console(account = nil)
|
|
188
|
+
account ||= ask(message: 'account name')
|
|
189
|
+
cred, temp_cred = get_valid_item_pair(account: account)
|
|
190
|
+
token = temp_cred.password unless temp_cred.nil?
|
|
191
|
+
|
|
192
|
+
console_url = 'https://console.aws.amazon.com/console/home'
|
|
193
|
+
signin_url = 'https://signin.aws.amazon.com/federation'
|
|
194
|
+
policy_json = {
|
|
195
|
+
Version: '2012-10-17',
|
|
196
|
+
Statement: [{
|
|
197
|
+
Action: '*',
|
|
198
|
+
Resource: '*',
|
|
199
|
+
Effect: 'Allow'
|
|
200
|
+
}]
|
|
201
|
+
}.to_json
|
|
202
|
+
|
|
203
|
+
if temp_cred
|
|
204
|
+
session_json = {
|
|
205
|
+
sessionId: cred.attributes[:account],
|
|
206
|
+
sessionKey: cred.password,
|
|
207
|
+
sessionToken: token
|
|
208
|
+
}.to_json
|
|
209
|
+
else
|
|
210
|
+
sts = Aws::STS::Client.new(access_key_id: cred.attributes[:account],
|
|
211
|
+
secret_access_key: cred.password)
|
|
212
|
+
|
|
213
|
+
session = sts.get_federation_token(name: ENV['USER'],
|
|
214
|
+
policy: policy_json,
|
|
215
|
+
duration_seconds: (60 * 60 * 12))
|
|
216
|
+
session_json = {
|
|
217
|
+
sessionId: session.credentials[:access_key_id],
|
|
218
|
+
sessionKey: session.credentials[:secret_access_key],
|
|
219
|
+
sessionToken: session.credentials[:session_token]
|
|
220
|
+
}.to_json
|
|
221
|
+
|
|
222
|
+
end
|
|
223
|
+
get_signin_token_url = signin_url + '?Action=getSigninToken' \
|
|
224
|
+
'&Session=' + CGI.escape(session_json)
|
|
225
|
+
|
|
226
|
+
returned_content = open(get_signin_token_url).read
|
|
227
|
+
|
|
228
|
+
signin_token = JSON.parse(returned_content)['SigninToken']
|
|
229
|
+
signin_token_param = '&SigninToken=' + CGI.escape(signin_token)
|
|
230
|
+
destination_param = '&Destination=' + CGI.escape(console_url)
|
|
231
|
+
|
|
232
|
+
login_url = signin_url + '?Action=login' + signin_token_param + destination_param
|
|
233
|
+
|
|
234
|
+
pid = spawn("open \"#{login_url}\"")
|
|
235
|
+
Process.wait pid
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# autocomplete
|
|
239
|
+
desc 'awskeyring CURR PREV', 'Autocompletion for bourne shells', hide: true
|
|
240
|
+
def awskeyring(curr, prev)
|
|
241
|
+
comp_line = ENV['COMP_LINE']
|
|
242
|
+
unless comp_line
|
|
243
|
+
warn "enable autocomplete with 'complete -C /path-to-command/awskeyring awskeyring'"
|
|
244
|
+
exit 1
|
|
245
|
+
end
|
|
246
|
+
comp_len = comp_line.split.length
|
|
247
|
+
comp_len += 1 if curr == ''
|
|
248
|
+
|
|
249
|
+
case comp_len
|
|
250
|
+
when 2
|
|
251
|
+
puts list_commands.select { |elem| elem.start_with?(curr) }.join("\n")
|
|
252
|
+
when 3
|
|
253
|
+
if prev == 'help'
|
|
254
|
+
puts list_commands.select { |elem| elem.start_with?(curr) }.join("\n")
|
|
255
|
+
elsif prev == 'remove-role'
|
|
256
|
+
puts Awskeyring.list_role_names.select { |elem| elem.start_with?(curr) }.join("\n")
|
|
257
|
+
else
|
|
258
|
+
puts Awskeyring.list_item_names.select { |elem| elem.start_with?(curr) }.join("\n")
|
|
259
|
+
end
|
|
260
|
+
when 4
|
|
261
|
+
puts Awskeyring.list_role_names.select { |elem| elem.start_with?(curr) }.join("\n")
|
|
262
|
+
else
|
|
263
|
+
exit 1
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
def list_commands
|
|
270
|
+
self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def get_valid_item_pair(account:)
|
|
274
|
+
session_key, session_token = Awskeyring.get_pair(account)
|
|
275
|
+
session_key, session_token = Awskeyring.delete_expired(session_key, session_token) if session_key
|
|
276
|
+
|
|
277
|
+
if session_key && session_token
|
|
278
|
+
puts '# Using temporary session credentials'
|
|
279
|
+
return session_key, session_token
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
item = Awskeyring.get_item(account)
|
|
283
|
+
if item.nil?
|
|
284
|
+
warn "# Credential not found with name: #{account}"
|
|
285
|
+
exit 2
|
|
286
|
+
end
|
|
287
|
+
[item, nil]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def put_env_string(account:, key:, secret:, token:)
|
|
291
|
+
puts "export AWS_ACCOUNT_NAME=\"#{account}\""
|
|
292
|
+
puts "export AWS_ACCESS_KEY_ID=\"#{key}\""
|
|
293
|
+
puts "export AWS_ACCESS_KEY=\"#{key}\""
|
|
294
|
+
puts "export AWS_SECRET_ACCESS_KEY=\"#{secret}\""
|
|
295
|
+
puts "export AWS_SECRET_KEY=\"#{secret}\""
|
|
296
|
+
if token
|
|
297
|
+
puts "export AWS_SECURITY_TOKEN=\"#{token}\""
|
|
298
|
+
puts "export AWS_SESSION_TOKEN=\"#{token}\""
|
|
299
|
+
else
|
|
300
|
+
puts 'unset AWS_SECURITY_TOKEN'
|
|
301
|
+
puts 'unset AWS_SESSION_TOKEN'
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def ask(message:, secure: false, optional: false)
|
|
306
|
+
if secure
|
|
307
|
+
HighLine.new.ask(message.rjust(20) + ': ') { |q| q.echo = '*' }
|
|
308
|
+
elsif optional
|
|
309
|
+
HighLine.new.ask((message + ' (optional)').rjust(20) + ': ')
|
|
310
|
+
else
|
|
311
|
+
HighLine.new.ask(message.rjust(20) + ': ')
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: awskeyring
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tristan Morgan
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-12-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: aws-sdk-iam
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: highline
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: ruby-keychain
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: thor
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: bundler
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rake
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
description: Manages AWS credentials in the OS X keychain
|
|
126
|
+
email:
|
|
127
|
+
- tristanmorgan@users.noreply.github.com
|
|
128
|
+
executables:
|
|
129
|
+
- awskeyring
|
|
130
|
+
extensions: []
|
|
131
|
+
extra_rdoc_files: []
|
|
132
|
+
files:
|
|
133
|
+
- ".gitignore"
|
|
134
|
+
- ".rspec"
|
|
135
|
+
- ".rubocop.yml"
|
|
136
|
+
- ".travis.yml"
|
|
137
|
+
- CODE_OF_CONDUCT.md
|
|
138
|
+
- Gemfile
|
|
139
|
+
- LICENSE.txt
|
|
140
|
+
- README.md
|
|
141
|
+
- Rakefile
|
|
142
|
+
- awskeyring.gemspec
|
|
143
|
+
- exe/awskeyring
|
|
144
|
+
- lib/awskeyring.rb
|
|
145
|
+
- lib/awskeyring/version.rb
|
|
146
|
+
- lib/awskeyring_command.rb
|
|
147
|
+
homepage: https://github.com/tristanmorgan/awskeyring
|
|
148
|
+
licenses:
|
|
149
|
+
- MIT
|
|
150
|
+
metadata: {}
|
|
151
|
+
post_install_message:
|
|
152
|
+
rdoc_options: []
|
|
153
|
+
require_paths:
|
|
154
|
+
- lib
|
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - ">="
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '0'
|
|
165
|
+
requirements: []
|
|
166
|
+
rubyforge_project:
|
|
167
|
+
rubygems_version: 2.6.12
|
|
168
|
+
signing_key:
|
|
169
|
+
specification_version: 4
|
|
170
|
+
summary: Manages AWS credentials in the OS X keychain
|
|
171
|
+
test_files: []
|