awskeyring 1.11.0 → 1.12.1

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: dc910795f877ae9b6a82106d0abb6aa98a1ac41e3b23e995effe1ea5dc8e8ffd
4
- data.tar.gz: 8e6651afc8b0320b9ed9e3d7b32274d6cf473ef36f7d8bf393f4e0115072aed4
3
+ metadata.gz: 155240a1418a0de58a8d3bd88fe97d607cb5f68ea6655f4b3e82ad6628683dad
4
+ data.tar.gz: 99224076df4e631f41e7aef1f0b8a713ff458887943303dbfd691af4e5ddd875
5
5
  SHA512:
6
- metadata.gz: 56112939c5730e6e72812b0e85845018ae762dddc1e8df8c9308c72a8458c010ce6898c1cd957d91f22a2dc3bffcc5504d933c03e711073adea294e218043e9a
7
- data.tar.gz: 60b525d9b832477409eaaa8b59ef4cb34d8ffac239b9ab26011214a8a8e182d1cc9a304ab35ca3ca4b7b128377c019eb6337bac468ed899bfd067fdac677ada0
6
+ metadata.gz: c8cfc74bf58e2886e28c3ab58f3d04955a57dbbcc4bffe9f450a472651df70f2ab3e1585dc21b1118a1f14e5ae8a1703076f56ff5215f19d623c3420c1883b65
7
+ data.tar.gz: 6c0c9f2f7b5916067b016bac45a3088d349298aff18e6b90e554e7ce2791476d8a5865800e0bbe334d2b07146f8a14b6cd68312a82af43ad6aedc5ffde585106
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Awskeyring](https://raw.githubusercontent.com/tristanmorgan/awskeyring/main/awskeyring-144.png)
4
4
 
5
- * [![Build Status](https://app.travis-ci.com/tristanmorgan/awskeyring.svg?branch=main)](https://app.travis-ci.com/github/tristanmorgan/awskeyring)
5
+ * ![Build Passing](https://github.com/tristanmorgan/awskeyring/actions/workflows/ruby.yml/badge.svg)
6
6
  * [![Gem Version](https://img.shields.io/gem/v/awskeyring)](https://badge.fury.io/rb/awskeyring)
7
7
  * [![license MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
8
8
  * [![All Downloads](https://img.shields.io/gem/dt/awskeyring)](https://rubygems.org/gems/awskeyring)
data/exe/awskeyring CHANGED
@@ -11,6 +11,6 @@ end
11
11
  begin
12
12
  AwskeyringCommand.start
13
13
  rescue Keychain::UserCancelledError => e
14
- warn e.to_s
14
+ warn e
15
15
  exit 1
16
16
  end
data/i18n/en.yml CHANGED
@@ -5,6 +5,7 @@ en:
5
5
  add_role_desc: Adds a ROLE to the keyring
6
6
  awskeyring_desc: Autocompletion for bourne shells
7
7
  console_desc: Open the AWS Console for the ACCOUNT
8
+ decode_desc: Decode an account id from a KEY
8
9
  default_desc: Run default help or initialise if needed.
9
10
  env_desc: Outputs bourne shell environment exports for an ACCOUNT
10
11
  exec_desc: Execute a COMMAND with the environment set for an ACCOUNT
@@ -25,6 +25,7 @@ module Awskeyring
25
25
  # AWS Env vars
26
26
  AWS_ENV_VARS = %w[
27
27
  AWS_ACCOUNT_NAME
28
+ AWS_ACCOUNT_ID
28
29
  AWS_ACCESS_KEY_ID
29
30
  AWS_ACCESS_KEY
30
31
  AWS_CREDENTIAL_EXPIRATION
@@ -85,7 +86,7 @@ module Awskeyring
85
86
  )
86
87
  end
87
88
  rescue Aws::STS::Errors::AccessDenied => e
88
- warn e.to_s
89
+ warn e
89
90
  exit 1
90
91
  end
91
92
 
@@ -123,14 +124,16 @@ module Awskeyring
123
124
  # [String] secret The aws_secret_access_key
124
125
  # [String] token The aws_session_token
125
126
  # @return [Hash] env_var hash
126
- def self.get_env_array(params = {})
127
+ def self.get_env_array(params = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
127
128
  env_var = {}
128
129
  env_var['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
129
130
 
130
131
  params[:expiration] = Time.at(params[:expiry]).iso8601 unless params[:expiry].nil?
132
+ params[:account_name] = params.delete(:account)
133
+ params[:account_id] = get_account_id(key: params[:key]) unless params[:key].nil?
131
134
 
132
- params.each_key do |param_name|
133
- AWS_ENV_VARS.each do |var_name|
135
+ AWS_ENV_VARS.each do |var_name|
136
+ params.each_key do |param_name|
134
137
  if var_name.include?(param_name.to_s.upcase) && !params[param_name].nil?
135
138
  env_var[var_name] = params[param_name]
136
139
  end
@@ -151,7 +154,7 @@ module Awskeyring
151
154
  sts = Aws::STS::Client.new(access_key_id: key, secret_access_key: secret, session_token: token)
152
155
  sts.get_caller_identity
153
156
  rescue Aws::Errors::ServiceError => e
154
- warn e.to_s
157
+ warn e
155
158
  exit 1
156
159
  end
157
160
  true
@@ -229,6 +232,30 @@ module Awskeyring
229
232
  region || Aws.shared_config.region(profile: 'default')
230
233
  end
231
234
 
235
+ # Get the account number from an access key
236
+ #
237
+ # @param [String] key The aws_access_key_id
238
+ # @return [String] Account number
239
+ def self.get_account_id(key:)
240
+ padded_no = key[3..12]
241
+ mask = (2 << 39) - 1
242
+ decimal = (decode(padded_no) >> 4) & mask
243
+ decimal.to_s.rjust(12, '0')
244
+ end
245
+
246
+ # base32 decode function
247
+ # returns 0 on failure
248
+ private_class_method def self.decode(str)
249
+ aws_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
250
+ bytes = str.bytes
251
+ bytes.inject do |m, o|
252
+ i = aws_table.index(o.chr)
253
+ return 0 if i.nil?
254
+
255
+ (m << 5) + i
256
+ end
257
+ end
258
+
232
259
  # Rotates the AWS access keys
233
260
  #
234
261
  # @param [String] key The aws_access_key_id
@@ -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.11.0'
9
+ VERSION = '1.12.1'
10
10
  # The Gem's homepage
11
11
  HOMEPAGE = 'https://github.com/tristanmorgan/awskeyring'
12
12
 
data/lib/awskeyring.rb CHANGED
@@ -4,6 +4,7 @@ require 'i18n'
4
4
  require 'json'
5
5
  require 'keychain'
6
6
  require 'awskeyring/validate'
7
+ require 'awskeyring/awsapi'
7
8
 
8
9
  # Awskeyring Module,
9
10
  # gives you an interface to access keychains and items.
@@ -195,6 +196,19 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
195
196
  (items + tokens).uniq.sort
196
197
  end
197
198
 
199
+ # Return a list account item names plus account ids
200
+ def self.list_account_names_plus # rubocop:disable Metrics/AbcSize
201
+ list_items.concat(list_tokens).map do |elem|
202
+ account_id = Awskeyring::Awsapi.get_account_id(key: elem.attributes[:account])
203
+ account_name = if elem.attributes[:label].start_with?(ACCOUNT_PREFIX)
204
+ elem.attributes[:label][(ACCOUNT_PREFIX.length)..]
205
+ else
206
+ elem.attributes[:label][(SESSION_KEY_PREFIX.length)..]
207
+ end
208
+ "#{account_name}\t#{account_id}"
209
+ end.uniq.sort
210
+ end
211
+
198
212
  # Return a list role item names
199
213
  def self.list_role_names
200
214
  list_roles.map { |elem| elem.attributes[:label][(ROLE_PREFIX.length)..] }.sort
@@ -77,13 +77,18 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
77
77
  end
78
78
 
79
79
  desc 'list', I18n.t('list_desc')
80
+ method_option :detail, type: :boolean, aliases: '-d', desc: I18n.t('method_option.detail'), default: false
80
81
  # list the accounts
81
82
  def list
82
83
  if Awskeyring.list_account_names.empty?
83
84
  warn I18n.t('message.missing_account', bin: File.basename($PROGRAM_NAME))
84
85
  exit 1
85
86
  end
86
- puts Awskeyring.list_account_names.join("\n")
87
+ if options[:detail]
88
+ puts Awskeyring.list_account_names_plus.join("\n")
89
+ else
90
+ puts Awskeyring.list_account_names.join("\n")
91
+ end
87
92
  end
88
93
 
89
94
  desc 'list-role', I18n.t('list_role_desc')
@@ -181,8 +186,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
181
186
  method_option 'no-bundle', type: :boolean, aliases: '-b', desc: I18n.t('method_option.nobundle'), default: false
182
187
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
183
188
  # execute an external command with env set
184
- def exec(account, *command) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
185
- if command.empty?
189
+ def exec(account, *exec) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
190
+ if exec.empty?
186
191
  warn I18n.t('message.exec')
187
192
  exit 1
188
193
  end
@@ -194,11 +199,11 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
194
199
  env_vars = Awskeyring::Awsapi.get_env_array(cred)
195
200
  unbundle if options['no-bundle']
196
201
  begin
197
- pid = Process.spawn(env_vars, command.join(' '))
202
+ pid = Process.spawn(env_vars, exec.join(' '))
198
203
  Process.wait pid
199
204
  exit 1 if Process.last_status.exitstatus.positive?
200
205
  rescue Errno::ENOENT => e
201
- warn e.to_s
206
+ warn e
202
207
  exit 1
203
208
  end
204
209
  end
@@ -330,7 +335,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
330
335
  key_message: I18n.t('message.rotate', account: account)
331
336
  )
332
337
  rescue Aws::Errors::ServiceError => e
333
- warn e.to_s
338
+ warn e
334
339
  exit 1
335
340
  end
336
341
 
@@ -426,6 +431,16 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
426
431
  end
427
432
  end
428
433
 
434
+ desc 'decode KEY', I18n.t('decode_desc'), hide: true
435
+ # decode account numbers
436
+ def decode(key = nil)
437
+ key = ask_check(
438
+ existing: key, message: I18n.t('message.key'), validator: Awskeyring::Validate.method(:access_key)
439
+ )
440
+
441
+ puts Awskeyring::Awsapi.get_account_id(key: key)
442
+ end
443
+
429
444
  desc "#{File.basename($PROGRAM_NAME)} CURR PREV", I18n.t('awskeyring_desc'), hide: true
430
445
  map File.basename($PROGRAM_NAME) => :autocomplete
431
446
  # autocomplete
@@ -441,7 +456,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
441
456
 
442
457
  comp_lines = comp_line[0..(comp_point_str.to_i)].split
443
458
 
444
- comp_type, sub_cmd = comp_type(comp_lines: comp_lines, prev: prev)
459
+ comp_type, sub_cmd = comp_type(comp_lines: comp_lines, prev: prev, curr: curr)
445
460
  list = fetch_auto_resp(comp_type, sub_cmd)
446
461
  puts list.select { |elem| elem.start_with?(curr) }.sort!.join("\n")
447
462
  end
@@ -458,7 +473,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
458
473
  end
459
474
 
460
475
  # determine the type of completion needed
461
- def comp_type(comp_lines:, prev:)
476
+ def comp_type(comp_lines:, prev:, curr:)
462
477
  sub_cmd = sub_command(comp_lines)
463
478
  comp_idx = comp_lines.rindex(prev)
464
479
 
@@ -469,19 +484,19 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
469
484
  comp_type = :browser_type
470
485
  else
471
486
  comp_type = :command
472
- comp_type = param_type(comp_idx, sub_cmd) unless sub_cmd.empty?
487
+ comp_type = param_type(comp_idx, sub_cmd, curr) unless sub_cmd.empty?
473
488
  end
474
489
 
475
490
  [comp_type, sub_cmd]
476
491
  end
477
492
 
478
493
  # check params for named params or fall back to flags
479
- def param_type(comp_idx, sub_cmd)
480
- types = %i[opt req]
494
+ def param_type(comp_idx, sub_cmd, curr)
495
+ types = %i[opt req rest]
481
496
  param_list = method(sub_cmd).parameters.select { |elem| types.include? elem[0] }
482
497
  if comp_idx.zero?
483
498
  :command
484
- elsif comp_idx > param_list.length
499
+ elsif comp_idx > param_list.length || curr.start_with?('-')
485
500
  :flag
486
501
  else
487
502
  param_list[comp_idx - 1][1]
@@ -500,7 +515,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
500
515
  end
501
516
 
502
517
  # given a type return the right list for completions
503
- def fetch_auto_resp(comp_type, sub_cmd)
518
+ def fetch_auto_resp(comp_type, sub_cmd) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
504
519
  case comp_type
505
520
  when :command
506
521
  list_commands
@@ -514,6 +529,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
514
529
  Awskeyring.list_token_names
515
530
  when :browser_type
516
531
  Awskeyring.list_browsers
532
+ when :exec
533
+ list_exec
517
534
  else
518
535
  list_arguments(command: sub_cmd)
519
536
  end
@@ -522,7 +539,18 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
522
539
  # list command names
523
540
  def list_commands
524
541
  commands = self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
525
- commands.reject! { |elem| %w[autocomplete default].include?(elem) }
542
+ commands.reject! { |elem| %w[autocomplete default decode].include?(elem) }
543
+ end
544
+
545
+ # list executables
546
+ def list_exec
547
+ list = []
548
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
549
+ list.concat(Dir.children(File.expand_path(path)))
550
+ rescue Errno::ENOENT
551
+ next
552
+ end
553
+ list.flatten
526
554
  end
527
555
 
528
556
  # list flags for a command
data/man/awskeyring.5 CHANGED
@@ -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" "February 2023" "" ""
4
+ .TH "AWSKEYRING" "5" "June 2024" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain
@@ -159,6 +159,9 @@ list:
159
159
  .IP
160
160
  Prints a list of accounts in the keyring
161
161
  .
162
+ .IP
163
+ \-d, \-\-detail, \-\-no\-detail: Show more detail\.
164
+ .
162
165
  .TP
163
166
  list\-role:
164
167
  .
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.11.0
4
+ version: 1.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Morgan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-21 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-iam
@@ -92,9 +92,9 @@ licenses:
92
92
  metadata:
93
93
  bug_tracker_uri: https://github.com/tristanmorgan/awskeyring/issues
94
94
  changelog_uri: https://github.com/tristanmorgan/awskeyring/blob/main/CHANGELOG.md
95
- documentation_uri: https://rubydoc.info/gems/awskeyring/1.11.0
95
+ documentation_uri: https://rubydoc.info/gems/awskeyring/1.12.1
96
96
  rubygems_mfa_required: 'true'
97
- source_code_uri: https://github.com/tristanmorgan/awskeyring/tree/v1.11.0
97
+ source_code_uri: https://github.com/tristanmorgan/awskeyring/tree/v1.12.1
98
98
  wiki_uri: https://github.com/tristanmorgan/awskeyring/wiki
99
99
  post_install_message:
100
100
  rdoc_options: []