awskeyring 1.3.2 → 1.7.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: 665195367b45827f29c9a4815a6cf8f17fa414f31c9756a24bb2aefba704333e
4
- data.tar.gz: 5a820147d8e6ab38df951b293bf0fb342a5a3eba2068607997adf69f152ceac2
3
+ metadata.gz: c2690c242849838e2056d06295c4909c77c197e3b5457495970b9c1fde0c7c07
4
+ data.tar.gz: 47f5bcc564492730aa851a0d27998dd6d2c3c47bc4b8cb736759895c3f2eda2a
5
5
  SHA512:
6
- metadata.gz: 49358a374bef7dffd71f9e30687856f80eb2a60e142770083ea1c8f3830593a06a61cff8bf2d69ddd19ae1c1147e891b15cac0df8203bc7727d6d39202866971
7
- data.tar.gz: 898e363ac9613e735168870b65b7896ce44073bade612d4e9eb27176a3d3a2d0b895707516b68568c6b5cc5f82e394a208bf2412af941b3dc9b6e41245d725f9
6
+ metadata.gz: 55442049ab8dc9139d950e13a3a5e317bff29ccf310643d110c6c6f0a8672a605655b3c0481b254b5a48809dec9ca7b2edc4a8c96459f30e615c9965b59ba1fa
7
+ data.tar.gz: 950c7267da2f0df886b28d2472d0b88f664f240b6f8df8b3eb708c56884bce8951b5b8b372fe2719585a7450436bc7c84aaaf057d8f146e40e53bba8fbde428f
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.7.0](https://github.com/servian/awskeyring/tree/v1.7.0) (2020-11-18)
4
+
5
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.6.0...v1.7.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Allow specifying a browser other than the default [\#71](https://github.com/servian/awskeyring/issues/71)
10
+ - Autocomplete for Browsers [\#73](https://github.com/servian/awskeyring/pull/73) ([tristanmorgan](https://github.com/tristanmorgan))
11
+
12
+ ## [v1.6.0](https://github.com/servian/awskeyring/tree/v1.6.0) (2020-08-11)
13
+
14
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.5.0...v1.6.0)
15
+
16
+ **Implemented enhancements:**
17
+
18
+ - Warn about missing accounts/roles [\#69](https://github.com/servian/awskeyring/pull/69) ([tristanmorgan](https://github.com/tristanmorgan))
19
+ - RuboCop and Spec update [\#68](https://github.com/servian/awskeyring/pull/68) ([tristanmorgan](https://github.com/tristanmorgan))
20
+ - Add SimpleCov reports. [\#67](https://github.com/servian/awskeyring/pull/67) ([tristanmorgan](https://github.com/tristanmorgan))
21
+
22
+ **Merged pull requests:**
23
+
24
+ - Updates for added RuboCop checks. [\#70](https://github.com/servian/awskeyring/pull/70) ([tristanmorgan](https://github.com/tristanmorgan))
25
+
26
+ ## [v1.5.0](https://github.com/servian/awskeyring/tree/v1.5.0) (2020-07-08)
27
+
28
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.4.0...v1.5.0)
29
+
30
+ **Implemented enhancements:**
31
+
32
+ - No-Bundle env changes for exec. [\#66](https://github.com/servian/awskeyring/pull/66) ([tristanmorgan](https://github.com/tristanmorgan))
33
+
34
+ ## [v1.4.0](https://github.com/servian/awskeyring/tree/v1.4.0) (2020-06-19)
35
+
36
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.3...v1.4.0)
37
+
38
+ **Implemented enhancements:**
39
+
40
+ - Import Keys and Tokens from shared credentials files. [\#65](https://github.com/servian/awskeyring/pull/65) ([tristanmorgan](https://github.com/tristanmorgan))
41
+
42
+ ## [v1.3.3](https://github.com/servian/awskeyring/tree/v1.3.3) (2020-06-04)
43
+
44
+ [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.2...v1.3.3)
45
+
46
+ **Implemented enhancements:**
47
+
48
+ - Change email references from Vibrato to Servian [\#64](https://github.com/servian/awskeyring/pull/64) ([tristanmorgan](https://github.com/tristanmorgan))
49
+
3
50
  ## [v1.3.2](https://github.com/servian/awskeyring/tree/v1.3.2) (2020-04-27)
4
51
 
5
52
  [Full Changelog](https://github.com/servian/awskeyring/compare/v1.3.1...v1.3.2)
@@ -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
data/Gemfile CHANGED
@@ -15,5 +15,6 @@ group :development do
15
15
  gem 'rubocop-rake'
16
16
  gem 'rubocop-rspec'
17
17
  gem 'rubocop-rubycw'
18
+ gem 'simplecov'
18
19
  gem 'yard'
19
20
  end
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Tristan Morgan
3
+ Copyright (c) 2017-2020 Tristan Morgan
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -65,6 +65,7 @@ The CLI is using [Thor](http://whatisthor.com) with help provided interactively.
65
65
  awskeyring env ACCOUNT # Outputs bourne shell environment exports for an ACCOUNT
66
66
  awskeyring exec ACCOUNT command... # Execute a COMMAND with the environment set for an ACCOUNT
67
67
  awskeyring help [COMMAND] # Describe available commands or one specific command
68
+ awskeyring import ACCOUNT # Import an ACCOUNT to the keyring from ~/.aws/credentials
68
69
  awskeyring initialise # Initialises a new KEYCHAIN
69
70
  awskeyring json ACCOUNT # Outputs AWS CLI compatible JSON for an ACCOUNT
70
71
  awskeyring list # Prints a list of accounts in the keyring
@@ -99,7 +100,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
99
100
  ## Security
100
101
 
101
102
  If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at
102
- [tristan@vibrato.com.au](mailto:tristan@vibrato.com.au). Awskeyring is a Ruby script and as such Ruby is whitelisted
103
+ [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com). Awskeyring is a Ruby script and as such Ruby is whitelisted
103
104
  to access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
104
105
 
105
106
  ## Contributing
@@ -112,6 +113,7 @@ the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
112
113
 
113
114
  * Tristan [tristanmorgan](https://github.com/tristanmorgan)
114
115
  * Adam Sir [AzySir](https://github.com/AzySir)
116
+ * Vito Giarrusso [thtliife](https://github.com/thtliife)
115
117
 
116
118
  ## License
117
119
 
data/Rakefile CHANGED
@@ -15,17 +15,25 @@ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
15
15
  end
16
16
 
17
17
  RuboCop::RakeTask.new do |rubocop|
18
- rubocop.options = ['-D']
18
+ rubocop.options = %w[-D --enable-pending-cops]
19
19
  rubocop.requires << 'rubocop-performance'
20
20
  rubocop.requires << 'rubocop-rake'
21
21
  rubocop.requires << 'rubocop-rspec'
22
22
  rubocop.requires << 'rubocop-rubycw'
23
23
  end
24
24
 
25
- 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
26
33
 
27
34
  desc 'Check filemode bits'
28
35
  task :filemode do
36
+ puts 'Running FileMode...'
29
37
  files = Set.new(`git ls-files -z`.split("\x0"))
30
38
  dirs = Set.new(files.map { |file| File.dirname(file) })
31
39
  failure = []
@@ -40,7 +48,7 @@ end
40
48
 
41
49
  desc 'generate manpage'
42
50
  task :ronn do
43
- puts 'Writing manpage'
51
+ puts 'Running Ronn...'
44
52
  roff_text = Ronn::Document.new('man/awskeyring.5.ronn').to_roff
45
53
  File.write('man/awskeyring.5', roff_text)
46
54
  puts "done\n\n"
@@ -0,0 +1,4 @@
1
+ # Security Policy
2
+
3
+ If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at
4
+ [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com).
@@ -8,18 +8,28 @@ 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.required_ruby_version = '>= 2.6.0'
24
+
25
+ spec.metadata = {
26
+ 'bug_tracker_uri' => "#{Awskeyring::HOMEPAGE}/issues",
27
+ 'changelog_uri' => "#{Awskeyring::HOMEPAGE}/blob/master/CHANGELOG.md",
28
+ 'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{Awskeyring::VERSION}",
29
+ 'source_code_uri' => "#{Awskeyring::HOMEPAGE}/tree/v#{Awskeyring::VERSION}",
30
+ 'wiki_uri' => "#{Awskeyring::HOMEPAGE}/wiki"
31
+ }
32
+
23
33
  spec.add_dependency('aws-sdk-iam')
24
34
  spec.add_dependency('i18n')
25
35
  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,10 +44,12 @@ 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.'
48
51
  path: 'The service PATH to open.'
52
+ browser: 'Specify an alternative browser.'
49
53
  role: 'The ROLE to assume.'
50
54
  secret: 'AWS account secret.'
51
55
  unset: 'Unset environment variables.'
@@ -72,6 +76,8 @@ en:
72
76
  delexpired: '# Removing expired session credentials'
73
77
  exec: '# COMMAND not provided'
74
78
  missing: '# Config missing, run `%{bin} initialise` to recreate.'
79
+ missing_account: '# No accounts added, run `%{bin} add` to add.'
80
+ missing_role: '# No roles added, run `%{bin} add-role` to add.'
75
81
  rotate: '# You have two access keys for account %{account}'
76
82
  temporary: '# Using temporary session credentials.'
77
83
  timeout: '# It is STRONGLY recommended to set your keychain to lock in 5 minutes or less.'
@@ -27,6 +27,8 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
27
27
  DEFAULT_KEY_AGE = 90
28
28
  # Default Console Paths
29
29
  DEFAULT_CONSOLE_LIST = %w[cloudformation ec2/v2 iam rds route53 s3 sns sqs vpc].freeze
30
+ # Default Browsers
31
+ DEFAULT_BROWSER_LIST = %w[Brave FireFox Opera Safari Vivaldi].freeze
30
32
 
31
33
  # Retrieve the preferences
32
34
  #
@@ -72,18 +74,17 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
72
74
 
73
75
  # Return a list of all acount items
74
76
  private_class_method def self.list_items
75
- items = all_items.all.sort do |elem_a, elem_b|
76
- elem_a.attributes[:label] <=> elem_b.attributes[:label]
77
- end
78
- items.select { |elem| elem.attributes[:label].start_with?(ACCOUNT_PREFIX) }
77
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(ACCOUNT_PREFIX) }
79
78
  end
80
79
 
81
80
  # Return a list of all role items
82
81
  private_class_method def self.list_roles
83
- items = all_items.all.sort do |elem_a, elem_b|
84
- elem_a.attributes[:label] <=> elem_b.attributes[:label]
85
- end
86
- items.select { |elem| elem.attributes[:label].start_with?(ROLE_PREFIX) }
82
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(ROLE_PREFIX) }
83
+ end
84
+
85
+ # Return a list of all acount items
86
+ private_class_method def self.list_tokens
87
+ all_items.all.select { |elem| elem.attributes[:label].start_with?(SESSION_KEY_PREFIX) }
87
88
  end
88
89
 
89
90
  # Return all keychain items
@@ -175,17 +176,26 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
175
176
 
176
177
  # Return a list account item names
177
178
  def self.list_account_names
178
- list_items.map { |elem| elem.attributes[:label][(ACCOUNT_PREFIX.length)..-1] }
179
+ items = list_items.map { |elem| elem.attributes[:label][(ACCOUNT_PREFIX.length)..] }
180
+
181
+ tokens = list_tokens.map { |elem| elem.attributes[:label][(SESSION_KEY_PREFIX.length)..] }
182
+
183
+ (items + tokens).uniq.sort
179
184
  end
180
185
 
181
186
  # Return a list role item names
182
187
  def self.list_role_names
183
- list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..-1] }
188
+ list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..] }.sort
189
+ end
190
+
191
+ # Return a list token item names
192
+ def self.list_token_names
193
+ list_tokens.map { |elem| elem.attributes[:label][(SESSION_KEY_PREFIX.length)..] }.sort
184
194
  end
185
195
 
186
196
  # Return a list role item names and arns
187
197
  def self.list_role_names_plus
188
- list_roles.map { |elem| "#{elem.attributes[:label][(ROLE_PREFIX.length)..-1]}\t#{elem.attributes[:account]}" }
198
+ list_roles.map { |elem| "#{elem.attributes[:label][(ROLE_PREFIX.length)..]}\t#{elem.attributes[:account]}" }
189
199
  end
190
200
 
191
201
  # Return a list of console paths
@@ -193,6 +203,11 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
193
203
  prefs.key?('console') ? prefs['console'] : DEFAULT_CONSOLE_LIST
194
204
  end
195
205
 
206
+ # Return a list of browserss
207
+ def self.list_browsers
208
+ prefs.key?('browser') ? prefs['browser'] : DEFAULT_BROWSER_LIST
209
+ end
210
+
196
211
  # Return Key age warning number
197
212
  def self.key_age
198
213
  prefs.key?('keyage') ? prefs['keyage'] : DEFAULT_KEY_AGE
@@ -246,7 +261,7 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
246
261
  # Delete session token items if expired
247
262
  private_class_method def self.delete_expired(key:, token:)
248
263
  expires_at = Time.at(token.attributes[:account].to_i)
249
- if expires_at < Time.now
264
+ if expires_at < Time.new
250
265
  delete_pair(key: key, token: token, message: I18n.t('message.delexpired'))
251
266
  key = nil
252
267
  token = nil
@@ -347,6 +362,16 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
347
362
  role_name
348
363
  end
349
364
 
365
+ # Validate token exists
366
+ #
367
+ # @param [String] token_name the associated account name.
368
+ def self.token_exists(token_name)
369
+ Awskeyring::Validate.account_name(token_name)
370
+ raise 'Token does not exist' unless list_token_names.include?(token_name)
371
+
372
+ token_name
373
+ end
374
+
350
375
  # Validate role arn not exists
351
376
  #
352
377
  # @param [String] role_arn the associated role arn.
@@ -141,10 +141,11 @@ module Awskeyring
141
141
  #
142
142
  # @param [String] key The aws_access_key_id
143
143
  # @param [String] secret The aws_secret_access_key
144
- def self.verify_cred(key:, secret:)
144
+ # @param [String] token The aws_session_token
145
+ def self.verify_cred(key:, secret:, token:)
145
146
  begin
146
147
  ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
147
- 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)
148
149
  sts.get_caller_identity
149
150
  rescue Aws::Errors::ServiceError => e
150
151
  warn e.to_s
@@ -153,6 +154,26 @@ module Awskeyring
153
154
  true
154
155
  end
155
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
+
156
177
  # Retrieves an AWS Console login url
157
178
  #
158
179
  # @param [String] key The aws_access_key_id
@@ -177,9 +198,9 @@ module Awskeyring
177
198
  sessionToken: token
178
199
  }.to_json
179
200
 
180
- destination_param = '&Destination=' + CGI.escape(console_url)
201
+ destination_param = "&Destination=#{CGI.escape(console_url)}"
181
202
 
182
- AWS_SIGNIN_URL + '?Action=login' + token_param(session_json: session_json) + destination_param
203
+ "#{AWS_SIGNIN_URL}?Action=login#{token_param(session_json: session_json)}#{destination_param}"
183
204
  end
184
205
 
185
206
  # Get the signin token param
@@ -193,7 +214,7 @@ module Awskeyring
193
214
  returned_content = request.get(uri).body
194
215
 
195
216
  signin_token = JSON.parse(returned_content)['SigninToken']
196
- '&SigninToken=' + CGI.escape(signin_token)
217
+ "&SigninToken=#{CGI.escape(signin_token)}"
197
218
  end
198
219
 
199
220
  # Get the current region
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'io/console'
4
-
5
3
  # Awskeyring Module,
6
4
  module Awskeyring
7
5
  # Input methods for Awskeyring
@@ -15,18 +13,20 @@ module Awskeyring
15
13
  end
16
14
 
17
15
  private_class_method def self.hide_input # rubocop:disable Metrics/MethodLength
16
+ require 'io/console'
18
17
  password = +''
19
18
  loop do
20
19
  character = $stdin.getch
21
20
  break unless character
22
21
 
23
- if ["\n", "\r"].include? character
22
+ case character
23
+ when "\n", "\r"
24
24
  puts ''
25
25
  break
26
- elsif ["\b", "\u007f"].include? character
26
+ when "\b", "\u007f"
27
27
  password.chop!
28
28
  print "\b\e[P"
29
- elsif character == "\u0003"
29
+ when "\u0003"
30
30
  exit 1
31
31
  else
32
32
  print '*'
@@ -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 %r(\Aarn:aws:iam::[0-9]{12}:mfa\/\S*\z).match?(mfa_arn)
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
@@ -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 %r(\Aarn:aws:iam::[0-9]{12}:role\/\S*\z).match?(role_arn)
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
@@ -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.2'
9
+ VERSION = '1.7.0'
10
10
  # The Gem's homepage
11
11
  HOMEPAGE = 'https://github.com/servian/awskeyring'
12
12
 
@@ -72,6 +72,10 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
72
72
  desc 'list', I18n.t('list.desc')
73
73
  # list the accounts
74
74
  def list
75
+ if Awskeyring.list_account_names.empty?
76
+ warn I18n.t('message.missing_account', bin: File.basename($PROGRAM_NAME))
77
+ exit 1
78
+ end
75
79
  puts Awskeyring.list_account_names.join("\n")
76
80
  end
77
81
 
@@ -80,6 +84,10 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
80
84
  method_option 'detail', type: :boolean, aliases: '-d', desc: I18n.t('method_option.detail'), default: false
81
85
  # List roles
82
86
  def list_role
87
+ if Awskeyring.list_role_names.empty?
88
+ warn I18n.t('message.missing_role', bin: File.basename($PROGRAM_NAME))
89
+ exit 1
90
+ end
83
91
  if options['detail']
84
92
  puts Awskeyring.list_role_names_plus.join("\n")
85
93
  else
@@ -122,8 +130,45 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
122
130
  )
123
131
  end
124
132
 
133
+ desc 'import ACCOUNT', I18n.t('import.desc')
134
+ method_option 'no-remote', type: :boolean, aliases: '-r', desc: I18n.t('method_option.noremote'), default: false
135
+ # Import an Account
136
+ def import(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
137
+ account = ask_check(
138
+ existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_not_exists)
139
+ )
140
+ new_creds = Awskeyring::Awsapi.get_credentials_from_file(account: account)
141
+ unless options['no-remote']
142
+ Awskeyring::Awsapi.verify_cred(
143
+ key: new_creds[:key],
144
+ secret: new_creds[:secret],
145
+ token: new_creds[:token]
146
+ )
147
+ end
148
+ if new_creds[:token].nil?
149
+ Awskeyring.add_account(
150
+ account: new_creds[:account],
151
+ key: new_creds[:key],
152
+ secret: new_creds[:secret],
153
+ mfa: ''
154
+ )
155
+ puts I18n.t('message.addaccount', account: account)
156
+ else
157
+ Awskeyring.add_token(
158
+ account: new_creds[:account],
159
+ key: new_creds[:key],
160
+ secret: new_creds[:secret],
161
+ token: new_creds[:token],
162
+ expiry: new_creds[:expiry].to_i.to_s,
163
+ role: nil
164
+ )
165
+ puts I18n.t('message.addtoken', account: account, time: Time.at(new_creds[:expiry].to_i))
166
+ end
167
+ end
168
+
125
169
  desc 'exec ACCOUNT command...', I18n.t('exec.desc')
126
170
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
171
+ method_option 'no-bundle', type: :boolean, aliases: '-b', desc: I18n.t('method_option.nobundle'), default: false
127
172
  # execute an external command with env set
128
173
  def exec(account, *command)
129
174
  if command.empty?
@@ -132,6 +177,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
132
177
  end
133
178
  cred = age_check_and_get(account: account, no_token: options['no-token'])
134
179
  env_vars = Awskeyring::Awsapi.get_env_array(cred)
180
+ unbundle if options['no-bundle']
135
181
  begin
136
182
  pid = Process.spawn(env_vars, command.join(' '))
137
183
  Process.wait pid
@@ -163,7 +209,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
163
209
  existing: options[:mfa], message: I18n.t('message.mfa'),
164
210
  flags: 'optional', validator: Awskeyring::Validate.method(:mfa_arn)
165
211
  )
166
- Awskeyring::Awsapi.verify_cred(key: key, secret: secret) unless options['no-remote']
212
+ Awskeyring::Awsapi.verify_cred(key: key, secret: secret, token: nil) unless options['no-remote']
167
213
  Awskeyring.add_account(
168
214
  account: account,
169
215
  key: key,
@@ -235,8 +281,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
235
281
  # remove a session token
236
282
  def remove_token(account = nil)
237
283
  account = ask_check(
238
- existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists),
239
- limited_to: Awskeyring.list_account_names
284
+ existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:token_exists),
285
+ limited_to: Awskeyring.list_token_names
240
286
  )
241
287
  Awskeyring.delete_token(account: account, message: I18n.t('message.deltoken', account: account))
242
288
  end
@@ -341,6 +387,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
341
387
 
342
388
  desc 'console ACCOUNT', I18n.t('console.desc')
343
389
  method_option :path, type: :string, aliases: '-p', desc: I18n.t('method_option.path')
390
+ method_option :browser, type: :string, aliases: '-b', desc: I18n.t('method_option.browser')
344
391
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
345
392
  method_option 'no-open', type: :boolean, aliases: '-o', desc: I18n.t('method_option.noopen'), default: false
346
393
  # Open the AWS Console
@@ -371,7 +418,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
371
418
  if options['no-open']
372
419
  puts login_url
373
420
  else
374
- pid = Process.spawn("open \"#{login_url}\"")
421
+ spawn_cmd = options[:browser] ? "open -a \"#{options[:browser]}\" \"#{login_url}\"" : "open \"#{login_url}\""
422
+ pid = Process.spawn(spawn_cmd)
375
423
  Process.wait pid
376
424
  end
377
425
  end
@@ -414,7 +462,11 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
414
462
  when 'remove-role', '-r', 'rmr'
415
463
  comp_len = 2
416
464
  when '--path', '-p'
417
- comp_len = 4
465
+ comp_len = 40
466
+ when 'remove-token', 'rmt'
467
+ comp_len = 50
468
+ when '--browser', '-b'
469
+ comp_len = 60
418
470
  end
419
471
 
420
472
  [curr, comp_len, sub_cmd]
@@ -430,7 +482,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
430
482
  self.class.map[sub_cmd].to_s
431
483
  end
432
484
 
433
- def print_auto_resp(curr, len, sub_cmd)
485
+ def print_auto_resp(curr, len, sub_cmd) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
434
486
  list = []
435
487
  case len
436
488
  when 0
@@ -439,10 +491,14 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
439
491
  list = Awskeyring.list_account_names
440
492
  when 2
441
493
  list = Awskeyring.list_role_names
442
- when 3
494
+ when 3..10
443
495
  list = list_arguments(command: sub_cmd)
444
- when 4
496
+ when 40
445
497
  list = Awskeyring.list_console_path
498
+ when 50
499
+ list = Awskeyring.list_token_names
500
+ when 60
501
+ list = Awskeyring.list_browsers
446
502
  else
447
503
  exit 1
448
504
  end
@@ -497,13 +553,26 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
497
553
 
498
554
  def ask(message:, secure: false, optional: false, limited_to: nil)
499
555
  if secure
500
- Awskeyring::Input.read_secret(message.rjust(20) + ': ')
556
+ Awskeyring::Input.read_secret("#{message.rjust(20)}: ")
501
557
  elsif optional
502
- Thor::LineEditor.readline((message + ' (optional)').rjust(20) + ': ')
558
+ Thor::LineEditor.readline("#{"#{message} (optional)".rjust(20)}: ")
503
559
  elsif limited_to
504
- Thor::LineEditor.readline(message.rjust(20) + ': ', limited_to: limited_to)
560
+ Thor::LineEditor.readline("#{message.rjust(20)}: ", limited_to: limited_to)
505
561
  else
506
- Thor::LineEditor.readline(message.rjust(20) + ': ')
562
+ Thor::LineEditor.readline("#{message.rjust(20)}: ")
563
+ end
564
+ end
565
+
566
+ def unbundle
567
+ to_delete = ENV.keys.select { |elem| elem.start_with?('BUNDLER_ORIG_') }
568
+ bundled_env = to_delete.map { |elem| elem[('BUNDLER_ORIG_'.length)..] }
569
+ to_delete << 'BUNDLE_GEMFILE'
570
+ bundled_env.each do |env_name|
571
+ ENV[env_name] = ENV["BUNDLER_ORIG_#{env_name}"]
572
+ to_delete << env_name if ENV["BUNDLER_ORIG_#{env_name}"].start_with? 'BUNDLER_'
573
+ end
574
+ to_delete.each do |env_name|
575
+ ENV.delete(env_name)
507
576
  end
508
577
  end
509
578
  end
@@ -1,7 +1,7 @@
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" "March 2020" "" ""
4
+ .TH "AWSKEYRING" "5" "November 2020" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain
@@ -24,36 +24,90 @@ The commands are as follows:
24
24
  .IP
25
25
  Prints the version
26
26
  .
27
+ .br
28
+ .
29
+ .IP
30
+ \-r, \-\-no\-remote: Do not validate with remote api\.
31
+ .
27
32
  .TP
28
33
  add ACCOUNT:
29
34
  .
30
35
  .IP
31
36
  Adds an ACCOUNT to the keyring
32
37
  .
38
+ .br
39
+ .
40
+ .IP
41
+ \-k, \-\-key=KEY: AWS account key id\.
42
+ .
43
+ .br
44
+ \-s, \-\-secret=SECRET: AWS account secret\.
45
+ .
46
+ .br
47
+ \-m, \-\-mfa=MFA: AWS virtual mfa arn\.
48
+ .
49
+ .br
50
+ \-r, \-\-no\-remote: Do not validate with remote api\.
51
+ .
33
52
  .TP
34
53
  add\-role ROLE:
35
54
  .
36
55
  .IP
37
56
  Adds a ROLE to the keyring
38
57
  .
58
+ .br
59
+ .
60
+ .IP
61
+ \-a, \-\-arn=ARN: AWS role arn\.
62
+ .
39
63
  .TP
40
- console ACCOUNT:
64
+ awskeyring console ACCOUNT:
41
65
  .
42
66
  .IP
43
67
  Open the AWS Console for the ACCOUNT
44
68
  .
69
+ .br
70
+ .
71
+ .IP
72
+ \-p, \-\-path=PATH: The service PATH to open\.
73
+ .
74
+ .br
75
+ \-b, \-\-browser=BROWSER: Specify an alternative browser\.
76
+ .
77
+ .br
78
+ \-n, \-\-no\-token: Do not use saved token\.
79
+ .
80
+ .br
81
+ \-o, \-\-no\-open: Do not open the url\.
82
+ .
45
83
  .TP
46
84
  env ACCOUNT:
47
85
  .
48
86
  .IP
49
87
  Outputs bourne shell environment exports for an ACCOUNT
50
88
  .
89
+ .br
90
+ .
91
+ .IP
92
+ \-n, \-\-no\-token: Do not use saved token\.
93
+ .
94
+ .br
95
+ \-u, \-\-unset, \-\-no\-unset: Unset environment variables\.
96
+ .
51
97
  .TP
52
98
  exec ACCOUNT command\.\.\.:
53
99
  .
54
100
  .IP
55
101
  Execute a COMMAND with the environment set for an ACCOUNT
56
102
  .
103
+ .br
104
+ .
105
+ .IP
106
+ \-n, \-\-no\-token: Do not use saved token\.
107
+ .
108
+ .br
109
+ \-b, \-\-no\-bundle: Unset Bundler environment variables\.
110
+ .
57
111
  .TP
58
112
  help [COMMAND]:
59
113
  .
@@ -61,17 +115,38 @@ help [COMMAND]:
61
115
  Describe available commands or one specific command
62
116
  .
63
117
  .TP
118
+ import:
119
+ .
120
+ .IP
121
+ Import an ACCOUNT to the keyring from ~/\.aws/credentials
122
+ .
123
+ .br
124
+ .
125
+ .IP
126
+ \-r, \-\-no\-remote: Do not validate with remote api\.
127
+ .
128
+ .TP
64
129
  initialise:
65
130
  .
66
131
  .IP
67
132
  Initialises a new KEYCHAIN
68
133
  .
134
+ .br
135
+ .
136
+ .IP
137
+ \-n, \-\-keychain=KEYCHAIN: Name of KEYCHAIN to initialise\.
138
+ .
69
139
  .TP
70
140
  json ACCOUNT:
71
141
  .
72
142
  .IP
73
143
  Outputs AWS CLI compatible JSON for an ACCOUNT
74
144
  .
145
+ .br
146
+ .
147
+ .IP
148
+ \-n, \-\-no\-token: Do not use saved token\.
149
+ .
75
150
  .TP
76
151
  list:
77
152
  .
@@ -84,6 +159,11 @@ list\-role:
84
159
  .IP
85
160
  Prints a list of roles in the keyring
86
161
  .
162
+ .br
163
+ .
164
+ .IP
165
+ \-d, \-\-detail, \-\-no\-detail: Show more detail\.
166
+ .
87
167
  .TP
88
168
  remove ACCOUNT:
89
169
  .
@@ -114,12 +194,34 @@ token ACCOUNT [ROLE] [MFA]:
114
194
  .IP
115
195
  Create an STS Token from a ROLE or an MFA code
116
196
  .
197
+ .br
198
+ .
199
+ .IP
200
+ \-r, \-\-role=ROLE: The ROLE to assume\.
201
+ .
202
+ .br
203
+ \-c, \-\-code=CODE: Virtual mfa CODE\.
204
+ .
205
+ .br
206
+ \-d, \-\-duration=DURATION: Session DURATION in seconds\.
207
+ .
117
208
  .TP
118
209
  update ACCOUNT:
119
210
  .
120
211
  .IP
121
212
  Updates an ACCOUNT in the keyring
122
213
  .
214
+ .br
215
+ .
216
+ .IP
217
+ \-k, \-\-key=KEY: AWS account key id\.
218
+ .
219
+ .br
220
+ \-s, \-\-secret=SECRET: AWS account secret\.
221
+ .
222
+ .br
223
+ \-r, \-\-no\-remote: Do not validate with remote api\.
224
+ .
123
225
  .SH "ENVIRONMENT"
124
226
  The AWS_DEFAULT_REGION environment variable will be used for AWS API calls where specified or fall back to us\-east\-1 when not\.
125
227
  .
@@ -169,10 +271,10 @@ awskeyring env personal\-aws
169
271
  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
272
  .
171
273
  .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\.
274
+ 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
275
  .
174
276
  .SH "AUTHOR"
175
- Tristan Morgan \fItristan@vibrato\.com\.au\fR is the maintainer of Awskeyring\.
277
+ Tristan Morgan \fItristan\.morgan@servian\.com\fR is the maintainer of Awskeyring\.
176
278
  .
177
279
  .SH "CONTRIBUTORS"
178
280
  .
@@ -182,6 +284,9 @@ Tristan tristanmorgan \fIhttps://github\.com/tristanmorgan\fR
182
284
  .IP "\(bu" 4
183
285
  Adam Sir AzySir \fIhttps://github\.com/AzySir\fR
184
286
  .
287
+ .IP "\(bu" 4
288
+ Vito Giarrusso thtliife \fIhttps://github\.com/thtliife\fR
289
+ .
185
290
  .IP "" 0
186
291
  .
187
292
  .SH "LICENSE"
@@ -16,39 +16,69 @@ The commands are as follows:
16
16
 
17
17
  * --version, -v:
18
18
 
19
- Prints the version
19
+ Prints the version<br>
20
+
21
+ -r, --no-remote: Do not validate with remote api.
20
22
 
21
23
  * add ACCOUNT:
22
24
 
23
- Adds an ACCOUNT to the keyring
25
+ Adds an ACCOUNT to the keyring<br>
26
+
27
+ -k, --key=KEY: AWS account key id.<br>
28
+ -s, --secret=SECRET: AWS account secret.<br>
29
+ -m, --mfa=MFA: AWS virtual mfa arn.<br>
30
+ -r, --no-remote: Do not validate with remote api.
24
31
 
25
32
  * add-role ROLE:
26
33
 
27
- Adds a ROLE to the keyring
34
+ Adds a ROLE to the keyring<br>
35
+
36
+ -a, --arn=ARN: AWS role arn.
28
37
 
29
- * console ACCOUNT:
38
+ * awskeyring console ACCOUNT:
30
39
 
31
- Open the AWS Console for the ACCOUNT
40
+ Open the AWS Console for the ACCOUNT<br>
41
+
42
+ -p, --path=PATH: The service PATH to open.<br>
43
+ -b, --browser=BROWSER: Specify an alternative browser.<br>
44
+ -n, --no-token: Do not use saved token.<br>
45
+ -o, --no-open: Do not open the url.
32
46
 
33
47
  * env ACCOUNT:
34
48
 
35
- Outputs bourne shell environment exports for an ACCOUNT
49
+ Outputs bourne shell environment exports for an ACCOUNT<br>
50
+
51
+ -n, --no-token: Do not use saved token.<br>
52
+ -u, --unset, --no-unset: Unset environment variables.
36
53
 
37
54
  * exec ACCOUNT command...:
38
55
 
39
- Execute a COMMAND with the environment set for an ACCOUNT
56
+ Execute a COMMAND with the environment set for an ACCOUNT<br>
57
+
58
+ -n, --no-token: Do not use saved token.<br>
59
+ -b, --no-bundle: Unset Bundler environment variables.
40
60
 
41
61
  * help [COMMAND]:
42
62
 
43
63
  Describe available commands or one specific command
44
64
 
65
+ * import:
66
+
67
+ Import an ACCOUNT to the keyring from ~/.aws/credentials<br>
68
+
69
+ -r, --no-remote: Do not validate with remote api.
70
+
45
71
  * initialise:
46
72
 
47
- Initialises a new KEYCHAIN
73
+ Initialises a new KEYCHAIN<br>
74
+
75
+ -n, --keychain=KEYCHAIN: Name of KEYCHAIN to initialise.
48
76
 
49
77
  * json ACCOUNT:
50
78
 
51
- Outputs AWS CLI compatible JSON for an ACCOUNT
79
+ Outputs AWS CLI compatible JSON for an ACCOUNT<br>
80
+
81
+ -n, --no-token: Do not use saved token.
52
82
 
53
83
  * list:
54
84
 
@@ -56,7 +86,9 @@ The commands are as follows:
56
86
 
57
87
  * list-role:
58
88
 
59
- Prints a list of roles in the keyring
89
+ Prints a list of roles in the keyring<br>
90
+
91
+ -d, --detail, --no-detail: Show more detail.
60
92
 
61
93
  * remove ACCOUNT:
62
94
 
@@ -76,11 +108,19 @@ The commands are as follows:
76
108
 
77
109
  * token ACCOUNT [ROLE] [MFA]:
78
110
 
79
- Create an STS Token from a ROLE or an MFA code
111
+ Create an STS Token from a ROLE or an MFA code<br>
112
+
113
+ -r, --role=ROLE: The ROLE to assume.<br>
114
+ -c, --code=CODE: Virtual mfa CODE.<br>
115
+ -d, --duration=DURATION: Session DURATION in seconds.
80
116
 
81
117
  * update ACCOUNT:
82
118
 
83
- Updates an ACCOUNT in the keyring
119
+ Updates an ACCOUNT in the keyring<br>
120
+
121
+ -k, --key=KEY: AWS account key id.<br>
122
+ -s, --secret=SECRET: AWS account secret.<br>
123
+ -r, --no-remote: Do not validate with remote api.
84
124
 
85
125
  ## ENVIRONMENT
86
126
 
@@ -117,17 +157,18 @@ like [HashiCorp Vault](https://vaultproject.io/).
117
157
  ## SECURITY
118
158
 
119
159
  If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at
120
- [tristan@vibrato.com.au](mailto:tristan@vibrato.com.au). Awskeyring is a Ruby script and as such Ruby is whitelisted to
160
+ [tristan.morgan@servian.com](mailto:tristan.morgan@servian.com). Awskeyring is a Ruby script and as such Ruby is whitelisted to
121
161
  access your "awskeyring" keychain. Use a strong password and keep the unlock time short.
122
162
 
123
163
  ## AUTHOR
124
164
 
125
- Tristan Morgan <tristan@vibrato.com.au> is the maintainer of Awskeyring.
165
+ Tristan Morgan <tristan.morgan@servian.com> is the maintainer of Awskeyring.
126
166
 
127
167
  ## CONTRIBUTORS
128
168
 
129
169
  * Tristan [tristanmorgan](https://github.com/tristanmorgan)
130
170
  * Adam Sir [AzySir](https://github.com/AzySir)
171
+ * Vito Giarrusso [thtliife](https://github.com/thtliife)
131
172
 
132
173
  ## LICENSE
133
174
 
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.2
4
+ version: 1.7.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-04-27 00:00:00.000000000 Z
11
+ date: 2020-11-18 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: []
@@ -81,6 +80,7 @@ files:
81
80
  - LICENSE.txt
82
81
  - README.md
83
82
  - Rakefile
83
+ - SECURITY.md
84
84
  - awskeyring.gemspec
85
85
  - exe/awskeyring
86
86
  - i18n/en.yml
@@ -95,7 +95,12 @@ files:
95
95
  homepage: https://github.com/servian/awskeyring
96
96
  licenses:
97
97
  - MIT
98
- metadata: {}
98
+ metadata:
99
+ bug_tracker_uri: https://github.com/servian/awskeyring/issues
100
+ changelog_uri: https://github.com/servian/awskeyring/blob/master/CHANGELOG.md
101
+ documentation_uri: https://rubydoc.info/gems/awskeyring/1.7.0
102
+ source_code_uri: https://github.com/servian/awskeyring/tree/v1.7.0
103
+ wiki_uri: https://github.com/servian/awskeyring/wiki
99
104
  post_install_message:
100
105
  rdoc_options: []
101
106
  require_paths:
@@ -104,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
109
  requirements:
105
110
  - - ">="
106
111
  - !ruby/object:Gem::Version
107
- version: '0'
112
+ version: 2.6.0
108
113
  required_rubygems_version: !ruby/object:Gem::Requirement
109
114
  requirements:
110
115
  - - ">="