awskeyring 1.3.0 → 1.5.0

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
  SHA256:
3
- metadata.gz: cf00f602f71d488e5786047871727580791f3805cf53055c9d720801bae6b75b
4
- data.tar.gz: be06827683d764c13767a1cda773b81dd1bcfa5e4c09958119a1b8b63698ec9c
3
+ metadata.gz: e56bbd10b469c722c15eaca7729bf39eac3aa3bde760c6faa44fd891200c7e2d
4
+ data.tar.gz: f1381f0d3e9c9f69c5404cae3a12499d94bfc003999fc96f3eabc2fe98218852
5
5
  SHA512:
6
- metadata.gz: 1adf1d2f3a8e3791494fb473a91ccebf471390120cebcbb0d1372824407637077eec4dbab37fcd4036b7f88488ba0e12e185169723144db4f5dad9222ab354b0
7
- data.tar.gz: 2d829ba0a4fd623b52364f9b4b1e7918240722da89062f23e14c21d797bf2a7f55943579c39ae09f42933515f0ea476dfe23e6090dd98de6669da34aeda4b329
6
+ metadata.gz: b1fcf72a9fded0b2464f5f12bdcba598928400bb9b7c25bc586deb28f244d8044514433e596d758e1a54121c24c56972e2e137cc6803cca545dfb0486bc45a3e
7
+ data.tar.gz: 46a6c39a038ca5258a84cda96f12a3e8c6b7cea655f918476aef5b0ecc70c032cf02162eefc47ed4ed498e5234a4eb2637ec958309d8b23461b3306013a8a6b0
@@ -1,6 +1,51 @@
1
1
  # Changelog
2
2
 
3
- ## [v1.3.0](https://github.com/servian/awskeyring/tree/v1.3.0) (2020-02-20)
3
+ ## [v1.5.0](https://github.com/servian/awskeyring/tree/v1.5.0) (2020-07-08)
4
+
5
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.4.0...v1.5.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - No-Bundle env changes for exec. [\#66](https://github.com/servian/awskeyring/pull/66) ([tristanmorgan](https://github.com/tristanmorgan))
10
+
11
+ ## [v1.4.0](https://github.com/servian/awskeyring/tree/v1.4.0) (2020-06-19)
12
+
13
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.3...v1.4.0)
14
+
15
+ **Implemented enhancements:**
16
+
17
+ - Import Keys and Tokens from shared credentials files. [\#65](https://github.com/servian/awskeyring/pull/65) ([tristanmorgan](https://github.com/tristanmorgan))
18
+
19
+ ## [v1.3.3](https://github.com/servian/awskeyring/tree/v1.3.3) (2020-06-04)
20
+
21
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.2...v1.3.3)
22
+
23
+ **Implemented enhancements:**
24
+
25
+ - Change email references from Vibrato to Servian [\#64](https://github.com/servian/awskeyring/pull/64) ([tristanmorgan](https://github.com/tristanmorgan))
26
+
27
+ ## [v1.3.2](https://github.com/servian/awskeyring/tree/v1.3.2) (2020-04-27)
28
+
29
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.1...v1.3.2)
30
+
31
+ **Fixed bugs:**
32
+
33
+ - Fix I18n message load when used as a library. [\#63](https://github.com/servian/awskeyring/pull/63) ([tristanmorgan](https://github.com/tristanmorgan))
34
+
35
+ ## [v1.3.1](https://github.com/servian/awskeyring/tree/v1.3.1) (2020-03-19)
36
+
37
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.0...v1.3.1)
38
+
39
+ **Implemented enhancements:**
40
+
41
+ - Markdown linting changes and removed Rubocop-MD. [\#61](https://github.com/servian/awskeyring/pull/61) ([tristanmorgan](https://github.com/tristanmorgan))
42
+ - Removed some redundant code. [\#60](https://github.com/servian/awskeyring/pull/60) ([tristanmorgan](https://github.com/tristanmorgan))
43
+
44
+ **Merged pull requests:**
45
+
46
+ - Update Ronn code and PR template. [\#59](https://github.com/servian/awskeyring/pull/59) ([tristanmorgan](https://github.com/tristanmorgan))
47
+
48
+ ## [v1.3.0](https://github.com/servian/awskeyring/tree/v1.3.0) (2020-02-19)
4
49
 
5
50
  [Full Changelog](https://github.com/servian/awskeyring/compare/v1.2.0...v1.3.0)
6
51
 
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
55
55
  ## Enforcement
56
56
 
57
57
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at [tristan@vibrato.com.au]. All
58
+ reported by contacting the project team at [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com). All
59
59
  complaints will be reviewed and investigated and will result in a response that
60
60
  is deemed necessary and appropriate to the circumstances. The project team is
61
61
  obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -68,6 +68,6 @@ members of the project's leadership.
68
68
  ## Attribution
69
69
 
70
70
  This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
71
+ available [here](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
72
72
 
73
73
  [homepage]: https://www.contributor-covenant.org
@@ -35,7 +35,7 @@ A friendly `README.md` written for many audiences.
35
35
 
36
36
  The [wiki].
37
37
 
38
- ### API documentation
38
+ ### API documentation
39
39
 
40
40
  API documentation is written as [YARD] docblocks in the Ruby code.
41
41
 
data/Gemfile CHANGED
@@ -11,7 +11,6 @@ group :development do
11
11
  gem 'ronn'
12
12
  gem 'rspec'
13
13
  gem 'rubocop'
14
- gem 'rubocop-md'
15
14
  gem 'rubocop-performance'
16
15
  gem 'rubocop-rake'
17
16
  gem 'rubocop-rspec'
data/README.md CHANGED
@@ -9,7 +9,8 @@
9
9
  * [![Version Downloads](https://ruby-gem-downloads-badge.herokuapp.com/awskeyring?label=downloads-current-version)](https://rubygems.org/gems/awskeyring)
10
10
  * [![Documentation](https://img.shields.io/badge/yard-docs-brightgreen.svg)](https://www.rubydoc.info/gems/awskeyring)
11
11
 
12
- Awskeyring is a small tool to manage AWS account keys in the macOS Keychain.
12
+ Awskeyring is a small tool to manage AWS account keys in the macOS Keychain. It has
13
+ grown to incorporate a lot of [features](https://github.com/servian/awskeyring/wiki/Awskeyring-features).
13
14
 
14
15
  ## Motivation
15
16
 
@@ -34,17 +35,19 @@ Please see the [Wiki](https://github.com/servian/awskeyring/wiki) for full usage
34
35
 
35
36
  First you need to initialise your keychain to hold your AWS credentials.
36
37
 
37
- awskeyring initialise
38
+ $ awskeyring initialise
38
39
 
39
40
  Then add your keys to it.
40
41
 
41
- awskeyring add personal-aws
42
+ $ awskeyring add personal-aws
42
43
 
43
44
  Now your keys are stored safely in the macOS keychain. To print environment variables run...
44
45
 
45
- awskeyring env personal-aws
46
+ $ awskeyring env personal-aws
46
47
 
47
- Alternatively you can create a profile using the credential_process config variable. See the [AWS CLI Config docs](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#cli-aws-help-config-vars) for more details on this config option.
48
+ Alternatively you can create a profile using the credential_process config variable. See the
49
+ [AWS CLI Config docs](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#cli-aws-help-config-vars) for
50
+ more details on this config option.
48
51
 
49
52
  [profile personal]
50
53
  region = us-west-1
@@ -62,6 +65,7 @@ The CLI is using [Thor](http://whatisthor.com) with help provided interactively.
62
65
  awskeyring env ACCOUNT # Outputs bourne shell environment exports for an ACCOUNT
63
66
  awskeyring exec ACCOUNT command... # Execute a COMMAND with the environment set for an ACCOUNT
64
67
  awskeyring help [COMMAND] # Describe available commands or one specific command
68
+ awskeyring import ACCOUNT # Import an ACCOUNT to the keyring from ~/.aws/credentials
65
69
  awskeyring initialise # Initialises a new KEYCHAIN
66
70
  awskeyring json ACCOUNT # Outputs AWS CLI compatible JSON for an ACCOUNT
67
71
  awskeyring list # Prints a list of accounts in the keyring
@@ -87,22 +91,28 @@ To set your environment easily the following bash function helps:
87
91
 
88
92
  ## Development
89
93
 
90
- After checking out the repo, run `bundle update` to install dependencies. Then, run `bundle exec rake` to run the tests. Run `bundle exec awskeyring` to use the gem in this directory, ignoring other installed copies of this gem. Awskeyring is tested against the last two versions of Ruby shipped with macOS.
94
+ After checking out the repo, run `bundle update` to install dependencies. Then, run `bundle exec rake` to run the
95
+ tests. Run `bundle exec awskeyring` to use the gem in this directory, ignoring other installed copies of this gem.
96
+ Awskeyring is tested against the last two versions of Ruby shipped with macOS.
91
97
 
92
98
  To install this gem onto your local machine, run `bundle exec rake install`.
93
99
 
94
100
  ## Security
95
101
 
96
- If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at [tristan@vibrato.com.au](mailto:tristan@vibrato.com.au). Awskeyring is a Ruby script and as such Ruby is whitelisted to access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
102
+ If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at
103
+ [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com). Awskeyring is a Ruby script and as such Ruby is whitelisted
104
+ to access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
97
105
 
98
106
  ## Contributing
99
107
 
100
- Bug reports and pull requests are welcome on GitHub at https://github.com/servian/awskeyring. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
108
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/servian/awskeyring](https://github.com/servian/awskeyring).
109
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
110
+ the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
101
111
 
102
112
  ### Contributors
103
113
 
104
- * Tristan [tristanmorgan](https://github.com/tristanmorgan)
105
- * Adam Sir [AzySir](https://github.com/AzySir)
114
+ * Tristan [tristanmorgan](https://github.com/tristanmorgan)
115
+ * Adam Sir [AzySir](https://github.com/AzySir)
106
116
 
107
117
  ## License
108
118
 
data/Rakefile CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'rubocop/rake_task'
6
+ require 'ronn'
6
7
  require 'github_changelog_generator/task'
7
8
  require 'yard'
8
9
 
@@ -14,31 +15,43 @@ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
14
15
  end
15
16
 
16
17
  RuboCop::RakeTask.new do |rubocop|
17
- rubocop.options = ['-D']
18
+ rubocop.options = %w[-D --enable-pending-cops]
19
+ rubocop.requires << 'rubocop-performance'
20
+ rubocop.requires << 'rubocop-rake'
21
+ rubocop.requires << 'rubocop-rspec'
22
+ rubocop.requires << 'rubocop-rubycw'
18
23
  end
19
24
 
20
- RSpec::Core::RakeTask.new(:spec)
25
+ desc 'Run RSpec code examples'
26
+ task :spec do
27
+ puts 'Running RSpec...'
28
+ require 'rspec/core'
29
+ runner = RSpec::Core::Runner
30
+ xcode = runner.run(%w[--pattern spec/**{,/*/**}/*_spec.rb --order rand --format documentation --color])
31
+ abort 'RSpec failed' if xcode.positive?
32
+ end
21
33
 
22
34
  desc 'Check filemode bits'
23
35
  task :filemode do
24
- files = `git ls-files -z`.split("\x0")
25
- failure = false
26
- files.each do |file|
36
+ puts 'Running FileMode...'
37
+ files = Set.new(`git ls-files -z`.split("\x0"))
38
+ dirs = Set.new(files.map { |file| File.dirname(file) })
39
+ failure = []
40
+ files.merge(dirs).each do |file|
27
41
  mode = File.stat(file).mode
28
42
  print '.'
29
- if (mode & 0x7) != (mode >> 3 & 0x7)
30
- puts file
31
- failure = true
32
- end
43
+ failure << file if (mode & 0x7) != (mode >> 3 & 0x7)
33
44
  end
34
- abort 'Error: Incorrect file mode found' if failure
45
+ abort "\nError: Incorrect file mode found\n#{failure.join("\n")}" unless failure.empty?
35
46
  print "\n"
36
47
  end
37
48
 
38
49
  desc 'generate manpage'
39
50
  task :ronn do
40
- system('ronn -w -r man/awskeyring.5.ronn')
41
- puts
51
+ puts 'Running Ronn...'
52
+ roff_text = Ronn::Document.new('man/awskeyring.5.ronn').to_roff
53
+ File.write('man/awskeyring.5', roff_text)
54
+ puts "done\n\n"
42
55
  end
43
56
 
44
57
  YARD::Rake::YardocTask.new do |t|
@@ -8,18 +8,26 @@ Gem::Specification.new do |spec|
8
8
  spec.name = 'awskeyring'
9
9
  spec.version = Awskeyring::VERSION
10
10
  spec.authors = ['Tristan Morgan']
11
- spec.email = ['tristan@vibrato.com.au']
11
+ spec.email = 'tristan.morgan@servian.com'
12
12
 
13
13
  spec.summary = 'Manages AWS credentials in the macOS keychain'
14
14
  spec.description = 'Manages AWS credentials in the macOS keychain'
15
15
  spec.homepage = Awskeyring::HOMEPAGE
16
- spec.license = 'MIT'
16
+ spec.licenses = ['MIT']
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/|^\..*|^.*\.png}) }
19
19
  spec.bindir = 'exe'
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
+ spec.metadata = {
24
+ 'bug_tracker_uri' => "#{Awskeyring::HOMEPAGE}/issues",
25
+ 'changelog_uri' => "#{Awskeyring::HOMEPAGE}/blob/master/CHANGELOG.md",
26
+ 'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{Awskeyring::VERSION}",
27
+ 'source_code_uri' => "#{Awskeyring::HOMEPAGE}/tree/v#{Awskeyring::VERSION}",
28
+ 'wiki_uri' => "#{Awskeyring::HOMEPAGE}/wiki"
29
+ }
30
+
23
31
  spec.add_dependency('aws-sdk-iam')
24
32
  spec.add_dependency('i18n')
25
33
  spec.add_dependency('ruby-keychain')
@@ -14,6 +14,8 @@ en:
14
14
  desc: Outputs bourne shell environment exports for an ACCOUNT
15
15
  exec:
16
16
  desc: Execute a COMMAND with the environment set for an ACCOUNT
17
+ import:
18
+ desc: Import an ACCOUNT to the keyring from ~/.aws/credentials
17
19
  initialise:
18
20
  desc: Initialises a new KEYCHAIN
19
21
  json:
@@ -42,6 +44,7 @@ en:
42
44
  key: 'AWS account key id.'
43
45
  keychain: 'Name of KEYCHAIN to initialise.'
44
46
  mfa: 'AWS virtual mfa arn.'
47
+ nobundle: 'Unset Bundler environment variables.'
45
48
  noopen: 'Do not open the url.'
46
49
  notoken: 'Do not use saved token.'
47
50
  noremote: 'Do not validate with remote api.'
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'i18n'
3
4
  require 'json'
4
5
  require 'keychain'
5
6
  require 'awskeyring/validate'
@@ -7,6 +8,9 @@ require 'awskeyring/validate'
7
8
  # Awskeyring Module,
8
9
  # gives you an interface to access keychains and items.
9
10
  module Awskeyring # rubocop:disable Metrics/ModuleLength
11
+ I18n.load_path = Dir.glob(File.join(File.realpath(__dir__), '..', 'i18n', '*.{yml,yaml}'))
12
+ I18n.backend.load_translations
13
+
10
14
  # Default rpeferences fole path
11
15
  PREFS_FILE = (File.expand_path '~/.awskeyring').freeze
12
16
  # Prefix for Roles
@@ -68,18 +72,17 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
68
72
 
69
73
  # Return a list of all acount items
70
74
  private_class_method def self.list_items
71
- items = all_items.all.sort do |elem_a, elem_b|
72
- elem_a.attributes[:label] <=> elem_b.attributes[:label]
73
- end
74
- items.select { |elem| elem.attributes[:label].start_with?(ACCOUNT_PREFIX) }
75
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(ACCOUNT_PREFIX) }
75
76
  end
76
77
 
77
78
  # Return a list of all role items
78
79
  private_class_method def self.list_roles
79
- items = all_items.all.sort do |elem_a, elem_b|
80
- elem_a.attributes[:label] <=> elem_b.attributes[:label]
81
- end
82
- items.select { |elem| elem.attributes[:label].start_with?(ROLE_PREFIX) }
80
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(ROLE_PREFIX) }
81
+ end
82
+
83
+ # Return a list of all acount items
84
+ private_class_method def self.list_tokens
85
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(SESSION_KEY_PREFIX) }
83
86
  end
84
87
 
85
88
  # Return all keychain items
@@ -171,12 +174,21 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
171
174
 
172
175
  # Return a list account item names
173
176
  def self.list_account_names
174
- list_items.map { |elem| elem.attributes[:label][(ACCOUNT_PREFIX.length)..-1] }
177
+ items = list_items.map { |elem| elem.attributes[:label][(ACCOUNT_PREFIX.length)..-1] }
178
+
179
+ tokens = list_tokens.map { |elem| elem.attributes[:label][(SESSION_KEY_PREFIX.length)..-1] }
180
+
181
+ (items + tokens).uniq.sort
175
182
  end
176
183
 
177
184
  # Return a list role item names
178
185
  def self.list_role_names
179
- list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..-1] }
186
+ list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..-1] }.sort
187
+ end
188
+
189
+ # Return a list token item names
190
+ def self.list_token_names
191
+ list_tokens.map { |elem| elem.attributes[:label][(SESSION_KEY_PREFIX.length)..-1] }.sort
180
192
  end
181
193
 
182
194
  # Return a list role item names and arns
@@ -242,7 +254,7 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
242
254
  # Delete session token items if expired
243
255
  private_class_method def self.delete_expired(key:, token:)
244
256
  expires_at = Time.at(token.attributes[:account].to_i)
245
- if expires_at < Time.now
257
+ if expires_at < Time.new
246
258
  delete_pair(key: key, token: token, message: I18n.t('message.delexpired'))
247
259
  key = nil
248
260
  token = nil
@@ -343,6 +355,16 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
343
355
  role_name
344
356
  end
345
357
 
358
+ # Validate token exists
359
+ #
360
+ # @param [String] token_name the associated account name.
361
+ def self.token_exists(token_name)
362
+ Awskeyring::Validate.account_name(token_name)
363
+ raise 'Token does not exist' unless list_token_names.include?(token_name)
364
+
365
+ token_name
366
+ end
367
+
346
368
  # Validate role arn not exists
347
369
  #
348
370
  # @param [String] role_arn the associated role arn.
@@ -24,6 +24,7 @@ module Awskeyring
24
24
 
25
25
  # AWS Env vars
26
26
  AWS_ENV_VARS = %w[
27
+ AWS_ACCOUNT_NAME
27
28
  AWS_ACCESS_KEY_ID
28
29
  AWS_ACCESS_KEY
29
30
  AWS_SECRET_ACCESS_KEY
@@ -116,29 +117,23 @@ module Awskeyring
116
117
  # Generates Environment Variables for the AWS CLI
117
118
  #
118
119
  # @param [Hash] params including
119
- # [String] account The aws_access_key_id
120
+ # [String] account The aws account name
121
+ # [String] key The aws_access_key_id
120
122
  # [String] secret The aws_secret_access_key
121
123
  # [String] token The aws_session_token
122
124
  # @return [Hash] env_var hash
123
125
  def self.get_env_array(params = {})
124
126
  env_var = {}
125
127
  env_var['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
126
- env_var['AWS_ACCOUNT_NAME'] = params[:account] if params[:account]
127
128
 
128
- if params[:key]
129
- env_var['AWS_ACCESS_KEY_ID'] = params[:key]
130
- env_var['AWS_ACCESS_KEY'] = params[:key]
131
- end
132
-
133
- if params[:secret]
134
- env_var['AWS_SECRET_ACCESS_KEY'] = params[:secret]
135
- env_var['AWS_SECRET_KEY'] = params[:secret]
129
+ params.each_key do |param_name|
130
+ AWS_ENV_VARS.each do |var_name|
131
+ if var_name.include?(param_name.to_s.upcase) && !params[param_name].nil?
132
+ env_var[var_name] = params[param_name]
133
+ end
134
+ end
136
135
  end
137
136
 
138
- if params[:token]
139
- env_var['AWS_SECURITY_TOKEN'] = params[:token]
140
- env_var['AWS_SESSION_TOKEN'] = params[:token]
141
- end
142
137
  env_var
143
138
  end
144
139
 
@@ -146,10 +141,11 @@ module Awskeyring
146
141
  #
147
142
  # @param [String] key The aws_access_key_id
148
143
  # @param [String] secret The aws_secret_access_key
149
- def self.verify_cred(key:, secret:)
144
+ # @param [String] token The aws_session_token
145
+ def self.verify_cred(key:, secret:, token:)
150
146
  begin
151
147
  ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
152
- sts = Aws::STS::Client.new(access_key_id: key, secret_access_key: secret)
148
+ sts = Aws::STS::Client.new(access_key_id: key, secret_access_key: secret, session_token: token)
153
149
  sts.get_caller_identity
154
150
  rescue Aws::Errors::ServiceError => e
155
151
  warn e.to_s
@@ -158,6 +154,26 @@ module Awskeyring
158
154
  true
159
155
  end
160
156
 
157
+ # Retrieve credentials from the AWS Credentials file
158
+ #
159
+ # @param [String] account the profile name wanted
160
+ # @return [Hash] with the new credentials
161
+ # key The aws_access_key_id
162
+ # secret The aws_secret_access_key
163
+ # token The aws_session_token
164
+ # expiry expiry time
165
+ def self.get_credentials_from_file(account:)
166
+ creds = Aws::SharedCredentials.new(profile_name: account)
167
+ {
168
+ account: account,
169
+ key: creds.credentials.access_key_id,
170
+ secret: creds.credentials.secret_access_key,
171
+ token: creds.credentials.session_token,
172
+ expiry: Time.new + TWELVE_HOUR,
173
+ role: nil
174
+ }
175
+ end
176
+
161
177
  # Retrieves an AWS Console login url
162
178
  #
163
179
  # @param [String] key The aws_access_key_id
@@ -166,30 +182,22 @@ module Awskeyring
166
182
  # @param [String] user The local username
167
183
  # @param [String] path within the Console to access
168
184
  # @return [String] login_url to access
169
- def self.get_login_url(key:, secret:, token:, path:, user:) # rubocop:disable Metrics/MethodLength
185
+ def self.get_login_url(key:, secret:, token:, path:, user:)
170
186
  console_url = "https://console.aws.amazon.com/#{path}/home"
171
187
 
172
- if token
173
- session_json = {
174
- sessionId: key,
175
- sessionKey: secret,
176
- sessionToken: token
177
- }.to_json
178
- else
179
- ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
180
- sts = Aws::STS::Client.new(access_key_id: key,
181
- secret_access_key: secret)
182
-
183
- session = sts.get_federation_token(name: user,
184
- policy: ADMIN_POLICY,
185
- duration_seconds: TWELVE_HOUR)
186
- session_json = {
187
- sessionId: session.credentials[:access_key_id],
188
- sessionKey: session.credentials[:secret_access_key],
189
- sessionToken: session.credentials[:session_token]
190
- }.to_json
188
+ unless token
189
+ cred = get_token({ key: key, secret: secret, user: user, duration: TWELVE_HOUR })
190
+ key = cred[:key]
191
+ secret = cred[:secret]
192
+ token = cred[:token]
191
193
  end
192
194
 
195
+ session_json = {
196
+ sessionId: key,
197
+ sessionKey: secret,
198
+ sessionToken: token
199
+ }.to_json
200
+
193
201
  destination_param = '&Destination=' + CGI.escape(console_url)
194
202
 
195
203
  AWS_SIGNIN_URL + '?Action=login' + token_param(session_json: session_json) + destination_param
@@ -235,10 +243,10 @@ module Awskeyring
235
243
  exit 1
236
244
  end
237
245
 
238
- new_key = iam.create_access_key
246
+ new_key = iam.create_access_key[:access_key]
239
247
  iam = Aws::IAM::Client.new(
240
- access_key_id: new_key[:access_key][:access_key_id],
241
- secret_access_key: new_key[:access_key][:secret_access_key]
248
+ access_key_id: new_key[:access_key_id],
249
+ secret_access_key: new_key[:secret_access_key]
242
250
  )
243
251
  retry_backoff do
244
252
  iam.delete_access_key(
@@ -247,8 +255,8 @@ module Awskeyring
247
255
  end
248
256
  {
249
257
  account: account,
250
- key: new_key[:access_key][:access_key_id],
251
- secret: new_key[:access_key][:secret_access_key]
258
+ key: new_key[:access_key_id],
259
+ secret: new_key[:secret_access_key]
252
260
  }
253
261
  end
254
262
 
@@ -9,7 +9,7 @@ module Awskeyring
9
9
  #
10
10
  # @param [String] account_name the associated account name.
11
11
  def self.account_name(account_name)
12
- raise 'Invalid Account Name' unless account_name =~ /\S+/
12
+ raise 'Invalid Account Name' unless /\S+/.match?(account_name)
13
13
 
14
14
  account_name
15
15
  end
@@ -18,7 +18,7 @@ module Awskeyring
18
18
  #
19
19
  # @param [String] aws_access_key The aws_access_key_id
20
20
  def self.access_key(aws_access_key)
21
- raise 'Invalid Access Key' unless aws_access_key =~ /\AAKIA[A-Z0-9]{12,16}\z/
21
+ raise 'Invalid Access Key' unless /\AAKIA[A-Z0-9]{12,16}\z/.match?(aws_access_key)
22
22
 
23
23
  aws_access_key
24
24
  end
@@ -36,7 +36,7 @@ module Awskeyring
36
36
  #
37
37
  # @param [String] mfa_arn The users MFA arn
38
38
  def self.mfa_arn(mfa_arn)
39
- raise 'Invalid MFA ARN' unless mfa_arn =~ %r(\Aarn:aws:iam::[0-9]{12}:mfa\/\S*\z)
39
+ raise 'Invalid MFA ARN' unless %r(\Aarn:aws:iam::[0-9]{12}:mfa/\S*\z).match?(mfa_arn)
40
40
 
41
41
  mfa_arn
42
42
  end
@@ -45,7 +45,7 @@ module Awskeyring
45
45
  #
46
46
  # @param [String] role_name
47
47
  def self.role_name(role_name)
48
- raise 'Invalid Role Name' unless role_name =~ /\S+/
48
+ raise 'Invalid Role Name' unless /\S+/.match?(role_name)
49
49
 
50
50
  role_name
51
51
  end
@@ -54,7 +54,7 @@ module Awskeyring
54
54
  #
55
55
  # @param [String] role_arn The role arn
56
56
  def self.role_arn(role_arn)
57
- raise 'Invalid Role ARN' unless role_arn =~ %r(\Aarn:aws:iam::[0-9]{12}:role\/\S*\z)
57
+ raise 'Invalid Role ARN' unless %r(\Aarn:aws:iam::[0-9]{12}:role/\S*\z).match?(role_arn)
58
58
 
59
59
  role_arn
60
60
  end
@@ -63,7 +63,7 @@ module Awskeyring
63
63
  #
64
64
  # @param [String] mfa_code The mfa code
65
65
  def self.mfa_code(mfa_code)
66
- raise 'Invalid MFA CODE' unless mfa_code =~ /\A\d{6}\z/
66
+ raise 'Invalid MFA CODE' unless /\A\d{6}\z/.match?(mfa_code)
67
67
 
68
68
  mfa_code
69
69
  end
@@ -6,7 +6,7 @@ require 'json'
6
6
  # Version const and query of latest.
7
7
  module Awskeyring
8
8
  # The Gem's version number
9
- VERSION = '1.3.0'
9
+ VERSION = '1.5.0'
10
10
  # The Gem's homepage
11
11
  HOMEPAGE = 'https://github.com/servian/awskeyring'
12
12
 
@@ -122,8 +122,45 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
122
122
  )
123
123
  end
124
124
 
125
+ desc 'import ACCOUNT', I18n.t('import.desc')
126
+ method_option 'no-remote', type: :boolean, aliases: '-r', desc: I18n.t('method_option.noremote'), default: false
127
+ # Import an Account
128
+ def import(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
129
+ account = ask_check(
130
+ existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_not_exists)
131
+ )
132
+ new_creds = Awskeyring::Awsapi.get_credentials_from_file(account: account)
133
+ unless options['no-remote']
134
+ Awskeyring::Awsapi.verify_cred(
135
+ key: new_creds[:key],
136
+ secret: new_creds[:secret],
137
+ token: new_creds[:token]
138
+ )
139
+ end
140
+ if new_creds[:token].nil?
141
+ Awskeyring.add_account(
142
+ account: new_creds[:account],
143
+ key: new_creds[:key],
144
+ secret: new_creds[:secret],
145
+ mfa: ''
146
+ )
147
+ puts I18n.t('message.addaccount', account: account)
148
+ else
149
+ Awskeyring.add_token(
150
+ account: new_creds[:account],
151
+ key: new_creds[:key],
152
+ secret: new_creds[:secret],
153
+ token: new_creds[:token],
154
+ expiry: new_creds[:expiry].to_i.to_s,
155
+ role: nil
156
+ )
157
+ puts I18n.t('message.addtoken', account: account, time: Time.at(new_creds[:expiry].to_i))
158
+ end
159
+ end
160
+
125
161
  desc 'exec ACCOUNT command...', I18n.t('exec.desc')
126
162
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
163
+ method_option 'no-bundle', type: :boolean, aliases: '-b', desc: I18n.t('method_option.nobundle'), default: false
127
164
  # execute an external command with env set
128
165
  def exec(account, *command)
129
166
  if command.empty?
@@ -132,6 +169,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
132
169
  end
133
170
  cred = age_check_and_get(account: account, no_token: options['no-token'])
134
171
  env_vars = Awskeyring::Awsapi.get_env_array(cred)
172
+ unbundle if options['no-bundle']
135
173
  begin
136
174
  pid = Process.spawn(env_vars, command.join(' '))
137
175
  Process.wait pid
@@ -163,7 +201,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
163
201
  existing: options[:mfa], message: I18n.t('message.mfa'),
164
202
  flags: 'optional', validator: Awskeyring::Validate.method(:mfa_arn)
165
203
  )
166
- Awskeyring::Awsapi.verify_cred(key: key, secret: secret) unless options['no-remote']
204
+ Awskeyring::Awsapi.verify_cred(key: key, secret: secret, token: nil) unless options['no-remote']
167
205
  Awskeyring.add_account(
168
206
  account: account,
169
207
  key: key,
@@ -235,8 +273,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
235
273
  # remove a session token
236
274
  def remove_token(account = nil)
237
275
  account = ask_check(
238
- existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists),
239
- limited_to: Awskeyring.list_account_names
276
+ existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:token_exists),
277
+ limited_to: Awskeyring.list_token_names
240
278
  )
241
279
  Awskeyring.delete_token(account: account, message: I18n.t('message.deltoken', account: account))
242
280
  end
@@ -289,7 +327,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
289
327
  method_option :code, type: :string, aliases: '-c', desc: I18n.t('method_option.code')
290
328
  method_option :duration, type: :string, aliases: '-d', desc: I18n.t('method_option.duration')
291
329
  # generate a sessiopn token
292
- def token(account = nil, role = nil, code = nil) # rubocop:disable all
330
+ def token(account = nil, role = nil, code = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
293
331
  account = ask_check(
294
332
  existing: account,
295
333
  message: I18n.t('message.account'),
@@ -309,19 +347,13 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
309
347
  existing: code, message: I18n.t('message.code'), validator: Awskeyring::Validate.method(:mfa_code)
310
348
  )
311
349
  end
312
- duration = options[:duration]
313
- duration ||= Awskeyring::Awsapi::ONE_HOUR.to_s if role
314
- duration ||= Awskeyring::Awsapi::TWELVE_HOUR.to_s if code
315
- duration ||= Awskeyring::Awsapi::ONE_HOUR.to_s
316
-
317
350
  item_hash = age_check_and_get(account: account, no_token: true)
318
- role_arn = Awskeyring.get_role_arn(role_name: role) if role
319
351
 
320
352
  begin
321
353
  new_creds = Awskeyring::Awsapi.get_token(
322
354
  code: code,
323
- role_arn: role_arn,
324
- duration: duration,
355
+ role_arn: (Awskeyring.get_role_arn(role_name: role) if role),
356
+ duration: default_duration(options[:duration], role, code),
325
357
  mfa: item_hash[:mfa],
326
358
  key: item_hash[:key],
327
359
  secret: item_hash[:secret],
@@ -421,6 +453,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
421
453
  comp_len = 2
422
454
  when '--path', '-p'
423
455
  comp_len = 4
456
+ when 'remove-token', 'rmt'
457
+ comp_len = 5
424
458
  end
425
459
 
426
460
  [curr, comp_len, sub_cmd]
@@ -436,7 +470,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
436
470
  self.class.map[sub_cmd].to_s
437
471
  end
438
472
 
439
- def print_auto_resp(curr, len, sub_cmd)
473
+ def print_auto_resp(curr, len, sub_cmd) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
440
474
  list = []
441
475
  case len
442
476
  when 0
@@ -449,6 +483,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
449
483
  list = list_arguments(command: sub_cmd)
450
484
  when 4
451
485
  list = Awskeyring.list_console_path
486
+ when 5
487
+ list = Awskeyring.list_token_names
452
488
  else
453
489
  exit 1
454
490
  end
@@ -471,6 +507,12 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
471
507
  Awskeyring::Awsapi::AWS_ENV_VARS.each { |key| puts "unset #{key}" unless env_var.key?(key) }
472
508
  end
473
509
 
510
+ def default_duration(duration, role, code)
511
+ duration ||= Awskeyring::Awsapi::ONE_HOUR.to_s if role
512
+ duration ||= Awskeyring::Awsapi::TWELVE_HOUR.to_s if code
513
+ duration || Awskeyring::Awsapi::ONE_HOUR.to_s
514
+ end
515
+
474
516
  def ask_check(existing:, message:, flags: nil, validator: nil, limited_to: nil) # rubocop:disable Metrics/MethodLength
475
517
  retries ||= 3
476
518
  begin
@@ -506,4 +548,17 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
506
548
  Thor::LineEditor.readline(message.rjust(20) + ': ')
507
549
  end
508
550
  end
551
+
552
+ def unbundle
553
+ to_delete = ENV.keys.select { |elem| elem.start_with?('BUNDLER_ORIG_') }
554
+ bundled_env = to_delete.map { |elem| elem[('BUNDLER_ORIG_'.length)..-1] }
555
+ to_delete << 'BUNDLE_GEMFILE'
556
+ bundled_env.each do |env_name|
557
+ ENV[env_name] = ENV['BUNDLER_ORIG_' + env_name]
558
+ to_delete << env_name if ENV['BUNDLER_ORIG_' + env_name].start_with? 'BUNDLER_'
559
+ end
560
+ to_delete.each do |env_name|
561
+ ENV.delete(env_name)
562
+ end
563
+ end
509
564
  end
@@ -1,10 +1,10 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "AWSKEYRING" "5" "February 2020" "" ""
4
+ .TH "AWSKEYRING" "5" "June 2020" "" ""
5
5
  .
6
6
  .SH "NAME"
7
- \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain\.
7
+ \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain
8
8
  .
9
9
  .SH "SYNOPSIS"
10
10
  awskeyring COMMAND [ACCOUNT|ROLE] [OPTIONS]
@@ -13,7 +13,7 @@ awskeyring COMMAND [ACCOUNT|ROLE] [OPTIONS]
13
13
  awskeyring help COMMAND
14
14
  .
15
15
  .SH "DESCRIPTION"
16
- The Awskeyring utility stores and manages AWS access keys and provides the facailty to generate access tokens with combinations of assumed roles and multi\-factor\-authentication codes\. It includes autocompletion features and multiple validation checks for input parsing\. It also includes the ability for the AWS CLI to call it directly to provide authentication\.
16
+ The Awskeyring utility stores and manages AWS access keys and provides the facility to generate access tokens with combinations of assumed roles and multi\-factor\-authentication codes\. It includes autocompletion features and multiple validation checks for input parsing\. It also includes the ability for the AWS CLI to call it directly to provide authentication\.
17
17
  .
18
18
  .P
19
19
  The commands are as follows:
@@ -61,6 +61,12 @@ help [COMMAND]:
61
61
  Describe available commands or one specific command
62
62
  .
63
63
  .TP
64
+ import:
65
+ .
66
+ .IP
67
+ Import an ACCOUNT to the keyring from ~/\.aws/credentials
68
+ .
69
+ .TP
64
70
  initialise:
65
71
  .
66
72
  .IP
@@ -169,10 +175,10 @@ awskeyring env personal\-aws
169
175
  The motivation of this application is to provide a local secure store of AWS credentials using specifically in the macOS Keychain, to have them easily accessed from the Terminal, and to provide useful functions like assuming roles and opening the AWS Console from the cli\. For Enterprise environments there are better suited tools to use like HashiCorp Vault \fIhttps://vaultproject\.io/\fR\.
170
176
  .
171
177
  .SH "SECURITY"
172
- If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at \fItristan@vibrato\.com\.au\fR\. Awskeyring is a Ruby script and as such Ruby is whitelisted to access your "awskeyring" keychain\. Use a strong password and keep the unlock time short\.
178
+ If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at \fItristan\.morgan@servian\.com\fR\. Awskeyring is a Ruby script and as such Ruby is whitelisted to access your "awskeyring" keychain\. Use a strong password and keep the unlock time short\.
173
179
  .
174
180
  .SH "AUTHOR"
175
- Tristan Morgan \fItristan@vibrato\.com\.au\fR is the maintainer of Awskeyring\.
181
+ Tristan Morgan \fItristan\.morgan@servian\.com\fR is the maintainer of Awskeyring\.
176
182
  .
177
183
  .SH "CONTRIBUTORS"
178
184
  .
@@ -1,4 +1,4 @@
1
- # Awskeyring -- is a small tool to manage AWS account keys in the macOS Keychain.
1
+ # Awskeyring -- is a small tool to manage AWS account keys in the macOS Keychain
2
2
 
3
3
  ## SYNOPSIS
4
4
 
@@ -8,81 +8,88 @@ awskeyring help COMMAND
8
8
 
9
9
  ## DESCRIPTION
10
10
 
11
- The Awskeyring utility stores and manages AWS access keys and provides the facailty to generate access tokens with combinations of assumed roles and multi-factor-authentication codes. It includes autocompletion features and multiple validation checks for input parsing. It also includes the ability for the AWS CLI to call it directly to provide authentication.
11
+ The Awskeyring utility stores and manages AWS access keys and provides the facility to generate access tokens with
12
+ combinations of assumed roles and multi-factor-authentication codes. It includes autocompletion features and multiple
13
+ validation checks for input parsing. It also includes the ability for the AWS CLI to call it directly to provide authentication.
12
14
 
13
15
  The commands are as follows:
14
16
 
15
- * --version, -v:
17
+ * --version, -v:
16
18
 
17
19
  Prints the version
18
20
 
19
- * add ACCOUNT:
21
+ * add ACCOUNT:
20
22
 
21
23
  Adds an ACCOUNT to the keyring
22
24
 
23
- * add-role ROLE:
25
+ * add-role ROLE:
24
26
 
25
27
  Adds a ROLE to the keyring
26
28
 
27
- * console ACCOUNT:
29
+ * console ACCOUNT:
28
30
 
29
31
  Open the AWS Console for the ACCOUNT
30
32
 
31
- * env ACCOUNT:
33
+ * env ACCOUNT:
32
34
 
33
35
  Outputs bourne shell environment exports for an ACCOUNT
34
36
 
35
- * exec ACCOUNT command...:
37
+ * exec ACCOUNT command...:
36
38
 
37
39
  Execute a COMMAND with the environment set for an ACCOUNT
38
40
 
39
- * help [COMMAND]:
41
+ * help [COMMAND]:
40
42
 
41
43
  Describe available commands or one specific command
42
44
 
43
- * initialise:
45
+ * import:
46
+
47
+ Import an ACCOUNT to the keyring from ~/.aws/credentials
48
+
49
+ * initialise:
44
50
 
45
51
  Initialises a new KEYCHAIN
46
52
 
47
- * json ACCOUNT:
53
+ * json ACCOUNT:
48
54
 
49
55
  Outputs AWS CLI compatible JSON for an ACCOUNT
50
56
 
51
- * list:
57
+ * list:
52
58
 
53
59
  Prints a list of accounts in the keyring
54
60
 
55
- * list-role:
61
+ * list-role:
56
62
 
57
63
  Prints a list of roles in the keyring
58
64
 
59
- * remove ACCOUNT:
65
+ * remove ACCOUNT:
60
66
 
61
67
  Removes an ACCOUNT from the keyring
62
68
 
63
- * remove-role ROLE:
69
+ * remove-role ROLE:
64
70
 
65
71
  Removes a ROLE from the keyring
66
72
 
67
- * remove-token ACCOUNT:
73
+ * remove-token ACCOUNT:
68
74
 
69
75
  Removes a token for ACCOUNT from the keyring
70
76
 
71
- * rotate ACCOUNT:
77
+ * rotate ACCOUNT:
72
78
 
73
79
  Rotate access keys for an ACCOUNT
74
80
 
75
- * token ACCOUNT [ROLE] [MFA]:
81
+ * token ACCOUNT [ROLE] [MFA]:
76
82
 
77
83
  Create an STS Token from a ROLE or an MFA code
78
84
 
79
- * update ACCOUNT:
85
+ * update ACCOUNT:
80
86
 
81
87
  Updates an ACCOUNT in the keyring
82
88
 
83
89
  ## ENVIRONMENT
84
90
 
85
- The AWS_DEFAULT_REGION environment variable will be used for AWS API calls where specified or fall back to us-east-1 when not.
91
+ The AWS_DEFAULT_REGION environment variable will be used for AWS API calls where specified or fall back to us-east-1
92
+ when not.
86
93
 
87
94
  ## EXIT STATUS
88
95
 
@@ -113,16 +120,18 @@ like [HashiCorp Vault](https://vaultproject.io/).
113
120
 
114
121
  ## SECURITY
115
122
 
116
- If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at [tristan@vibrato.com.au](mailto:tristan@vibrato.com.au). Awskeyring is a Ruby script and as such Ruby is whitelisted to access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
123
+ If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at
124
+ [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com). Awskeyring is a Ruby script and as such Ruby is whitelisted to
125
+ access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
117
126
 
118
127
  ## AUTHOR
119
128
 
120
- Tristan Morgan <tristan@vibrato.com.au> is the maintainer of Awskeyring.
129
+ Tristan Morgan <tristan.morgan@servian.com> is the maintainer of Awskeyring.
121
130
 
122
131
  ## CONTRIBUTORS
123
132
 
124
- * Tristan [tristanmorgan](https://github.com/tristanmorgan)
125
- * Adam Sir [AzySir](https://github.com/AzySir)
133
+ * Tristan [tristanmorgan](https://github.com/tristanmorgan)
134
+ * Adam Sir [AzySir](https://github.com/AzySir)
126
135
 
127
136
  ## LICENSE
128
137
 
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: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Morgan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-19 00:00:00.000000000 Z
11
+ date: 2020-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-iam
@@ -67,8 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: Manages AWS credentials in the macOS keychain
70
- email:
71
- - tristan@vibrato.com.au
70
+ email: tristan.morgan@servian.com
72
71
  executables:
73
72
  - awskeyring
74
73
  extensions: []
@@ -95,7 +94,12 @@ files:
95
94
  homepage: https://github.com/servian/awskeyring
96
95
  licenses:
97
96
  - MIT
98
- metadata: {}
97
+ metadata:
98
+ bug_tracker_uri: https://github.com/servian/awskeyring/issues
99
+ changelog_uri: https://github.com/servian/awskeyring/blob/master/CHANGELOG.md
100
+ documentation_uri: https://rubydoc.info/gems/awskeyring/1.5.0
101
+ source_code_uri: https://github.com/servian/awskeyring/tree/v1.5.0
102
+ wiki_uri: https://github.com/servian/awskeyring/wiki
99
103
  post_install_message:
100
104
  rdoc_options: []
101
105
  require_paths: