class AwskeyringCommand

AWSkeyring command line interface.

Public Class Methods

exit_on_failure?() click to toggle source

default to returning an error on failure.

# File lib/awskeyring_command.rb, line 29
def self.exit_on_failure?
  true
end

Public Instance Methods

__version() click to toggle source

print the version number

# File lib/awskeyring_command.rb, line 46
def __version
  puts "Awskeyring v#{Awskeyring::VERSION}"
  if !options['no-remote'] && Awskeyring::VERSION != Awskeyring.latest_version
    puts "the latest version v#{Awskeyring.latest_version}"
  end
  puts "Homepage #{Awskeyring::HOMEPAGE}"
end
add(account = nil) click to toggle source

Add an Account

# File lib/awskeyring_command.rb, line 208
def add(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  account = ask_check(
    existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_not_exists)
  )
  key = ask_check(
    existing: options[:key], message: I18n.t('message.key'), validator: Awskeyring.method(:access_key_not_exists)
  )
  secret = ask_check(
    existing: options[:secret], message: I18n.t('message.secret'),
    flags: 'secure', validator: Awskeyring::Validate.method(:secret_access_key)
  )
  mfa = ask_check(
    existing: options[:mfa], message: I18n.t('message.mfa'),
    flags: 'optional', validator: Awskeyring::Validate.method(:mfa_arn)
  )
  Awskeyring::Awsapi.verify_cred(key: key, secret: secret) unless options['no-remote']
  Awskeyring.add_account(
    account: account,
    key: key,
    secret: secret,
    mfa: mfa
  )
  puts I18n.t('message.addaccount', account: account)
end
add_role(role = nil) click to toggle source

Add a role

# File lib/awskeyring_command.rb, line 263
def add_role(role = nil)
  role = ask_check(
    existing: role, message: I18n.t('message.role'),
    validator: Awskeyring.method(:role_not_exists)
  )
  arn = ask_check(
    existing: options[:arn], message: I18n.t('message.arn'),
    validator: Awskeyring.method(:role_arn_not_exists)
  )

  Awskeyring.add_role(
    role: role,
    arn: arn
  )
  puts I18n.t('message.addrole', role: role)
end
autocomplete(curr, prev = nil) click to toggle source

autocomplete

# File lib/awskeyring_command.rb, line 437
def autocomplete(curr, prev = nil)
  curr, prev = fix_args(curr, prev)
  comp_line = ENV['COMP_LINE']
  comp_point_str = ENV['COMP_POINT']
  unless comp_line && comp_point_str
    exec_name = File.basename($PROGRAM_NAME)
    warn I18n.t('message.awskeyring', path: $PROGRAM_NAME, bin: exec_name)
    exit 1
  end

  comp_lines = comp_line[0..(comp_point_str.to_i)].split

  comp_type, sub_cmd = comp_type(comp_lines: comp_lines, prev: prev)
  list = fetch_auto_resp(comp_type, sub_cmd)
  puts list.select { |elem| elem.start_with?(curr) }.sort!.join("\n")
end
console(account = nil) click to toggle source

Open the AWS Console

# File lib/awskeyring_command.rb, line 401
def console(account = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  account = ask_check(
    existing: account,
    message: I18n.t('message.account'),
    validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  cred = age_check_and_get(account: account, no_token: options['no-token'])

  path = options[:path] || 'console'

  begin
    login_url = Awskeyring::Awsapi.get_login_url(
      key: cred[:key],
      secret: cred[:secret],
      token: cred[:token],
      path: path,
      user: ENV['USER']
    )
  rescue Aws::Errors::ServiceError => e
    warn e.to_s
    exit 1
  end

  if options['no-open']
    puts login_url
  else
    spawn_cmd = options[:browser] ? "open -a \"#{options[:browser]}\" \"#{login_url}\"" : "open \"#{login_url}\""
    pid = Process.spawn(spawn_cmd)
    Process.wait pid
  end
end
default() click to toggle source

default command to run

# File lib/awskeyring_command.rb, line 35
def default
  if Awskeyring.prefs.empty?
    invoke :initialise
  else
    invoke :help
  end
end
env(account = nil) click to toggle source

Print Env vars

# File lib/awskeyring_command.rb, line 108
def env(account = nil)
  if options[:unset]
    put_env_string(account: nil, key: nil, secret: nil, token: nil)
  else
    account = ask_check(
      existing: account, message: I18n.t('message.account'),
      validator: Awskeyring.method(:account_exists),
      limited_to: Awskeyring.list_account_names
    )
    cred = age_check_and_get(account: account, no_token: options['no-token'])
    put_env_string(cred)
  end
end
exec(account, *command) click to toggle source

execute an external command with env set

# File lib/awskeyring_command.rb, line 180
def exec(account, *command) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  if command.empty?
    warn I18n.t('message.exec')
    exit 1
  end
  account = ask_check(
    existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  cred = age_check_and_get(account: account, no_token: options['no-token'])
  env_vars = Awskeyring::Awsapi.get_env_array(cred)
  unbundle if options['no-bundle']
  begin
    pid = Process.spawn(env_vars, command.join(' '))
    Process.wait pid
    $CHILD_STATUS
  rescue Errno::ENOENT => e
    warn e.to_s
    exit 1
  end
end
import(account = nil) click to toggle source

Import an Account

# File lib/awskeyring_command.rb, line 143
def import(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  account = ask_check(
    existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_not_exists)
  )
  new_creds = Awskeyring::Awsapi.get_credentials_from_file(account: account)
  unless options['no-remote']
    Awskeyring::Awsapi.verify_cred(
      key: new_creds[:key],
      secret: new_creds[:secret],
      token: new_creds[:token]
    )
  end
  if new_creds[:token].nil?
    Awskeyring.add_account(
      account: new_creds[:account],
      key: new_creds[:key],
      secret: new_creds[:secret],
      mfa: ''
    )
    puts I18n.t('message.addaccount', account: account)
  else
    Awskeyring.add_token(
      account: new_creds[:account],
      key: new_creds[:key],
      secret: new_creds[:secret],
      token: new_creds[:token],
      expiry: new_creds[:expiry].to_i.to_s,
      role: nil
    )
    puts I18n.t('message.addtoken', account: account, time: Time.at(new_creds[:expiry].to_i))
  end
end
initialise() click to toggle source

initialise the keychain

# File lib/awskeyring_command.rb, line 57
def initialise
  unless Awskeyring.prefs.empty?
    puts I18n.t('message.initialise', file: Awskeyring::PREFS_FILE)
    exit 1
  end

  keychain = ask_check(
    existing: options[:keychain],
    flags: 'optional',
    message: I18n.t('message.keychain'),
    validator: Awskeyring::Validate.method(:account_name)
  )
  keychain = 'awskeyring' if keychain.empty?

  puts I18n.t('message.newkeychain')
  Awskeyring.init_keychain(awskeyring: keychain)

  exec_name = File.basename($PROGRAM_NAME)

  puts I18n.t('message.addkeychain', keychain: keychain, exec_name: exec_name)
end
json(account) click to toggle source

Print JSON for use with credential_process

# File lib/awskeyring_command.rb, line 125
def json(account)
  account = ask_check(
    existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  cred = age_check_and_get(account: account, no_token: options['no-token'])
  expiry = Time.at(cred[:expiry]) unless cred[:expiry].nil?
  puts Awskeyring::Awsapi.get_cred_json(
    key: cred[:key],
    secret: cred[:secret],
    token: cred[:token],
    expiry: (expiry || Time.new + Awskeyring::Awsapi::ONE_HOUR).iso8601
  )
end
list() click to toggle source

list the accounts

# File lib/awskeyring_command.rb, line 81
def list
  if Awskeyring.list_account_names.empty?
    warn I18n.t('message.missing_account', bin: File.basename($PROGRAM_NAME))
    exit 1
  end
  puts Awskeyring.list_account_names.join("\n")
end
list_role() click to toggle source

List roles

# File lib/awskeyring_command.rb, line 92
def list_role
  if Awskeyring.list_role_names.empty?
    warn I18n.t('message.missing_role', bin: File.basename($PROGRAM_NAME))
    exit 1
  end
  if options[:detail]
    puts Awskeyring.list_role_names_plus.join("\n")
  else
    puts Awskeyring.list_role_names.join("\n")
  end
end
remove(account = nil) click to toggle source

Remove an account

# File lib/awskeyring_command.rb, line 282
def remove(account = nil)
  account = ask_check(
    existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  Awskeyring.delete_account(account: account, message: I18n.t('message.delaccount', account: account))
end
remove_role(role = nil) click to toggle source

remove a role

# File lib/awskeyring_command.rb, line 302
def remove_role(role = nil)
  role = ask_check(
    existing: role, message: I18n.t('message.role'), validator: Awskeyring.method(:role_exists),
    limited_to: Awskeyring.list_role_names
  )
  Awskeyring.delete_role(role_name: role, message: I18n.t('message.delrole', role: role))
end
remove_token(token = nil) click to toggle source

remove a session token

# File lib/awskeyring_command.rb, line 292
def remove_token(token = nil)
  token = ask_check(
    existing: token, message: I18n.t('message.account'), validator: Awskeyring.method(:token_exists),
    limited_to: Awskeyring.list_token_names
  )
  Awskeyring.delete_token(account: token, message: I18n.t('message.deltoken', account: token))
end
rotate(account = nil) click to toggle source

rotate Account keys

# File lib/awskeyring_command.rb, line 312
def rotate(account = nil) # rubocop:disable Metrics/MethodLength
  account = ask_check(
    existing: account,
    message: I18n.t('message.account'),
    validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  cred = Awskeyring.get_valid_creds(account: account, no_token: true)

  begin
    new_key = Awskeyring::Awsapi.rotate(
      account: cred[:account],
      key: cred[:key],
      secret: cred[:secret],
      key_message: I18n.t('message.rotate', account: account)
    )
  rescue Aws::Errors::ServiceError => e
    warn e.to_s
    exit 1
  end

  Awskeyring.update_account(
    account: account,
    key: new_key[:key],
    secret: new_key[:secret]
  )

  puts I18n.t('message.upaccount', account: account)
end
token(account = nil, role = nil, code = nil) click to toggle source

generate a sessiopn token

# File lib/awskeyring_command.rb, line 346
def token(account = nil, role = nil, code = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  account = ask_check(
    existing: account,
    message: I18n.t('message.account'),
    validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  if role
    role = ask_check(
      existing: role, message: I18n.t('message.role'), validator: Awskeyring.method(:role_exists),
      limited_to: Awskeyring.list_role_names
    )
  end
  code ||= options[:code]
  if code
    code = ask_check(
      existing: code, message: I18n.t('message.code'), validator: Awskeyring::Validate.method(:mfa_code)
    )
  end
  item_hash = age_check_and_get(account: account, no_token: true)

  begin
    new_creds = Awskeyring::Awsapi.get_token(
      code: code,
      role_arn: (Awskeyring.get_role_arn(role_name: role) if role),
      duration: default_duration(options[:duration], role, code),
      mfa: item_hash[:mfa],
      key: item_hash[:key],
      secret: item_hash[:secret],
      user: ENV['USER']
    )
    Awskeyring.delete_token(account: account, message: '# Removing STS credentials')
  rescue Aws::Errors::ServiceError => e
    warn e.to_s
    exit 1
  end

  Awskeyring.add_token(
    account: account,
    key: new_creds[:key],
    secret: new_creds[:secret],
    token: new_creds[:token],
    expiry: new_creds[:expiry].to_i.to_s,
    role: role
  )

  puts I18n.t('message.addtoken', account: account, time: Time.at(new_creds[:expiry].to_i))
end
update(account = nil) click to toggle source

Update an Account

# File lib/awskeyring_command.rb, line 238
def update(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  account = ask_check(
    existing: account, message: I18n.t('message.account'),
    validator: Awskeyring.method(:account_exists),
    limited_to: Awskeyring.list_account_names
  )
  key = ask_check(
    existing: options[:key], message: I18n.t('message.key'), validator: Awskeyring.method(:access_key_not_exists)
  )
  secret = ask_check(
    existing: options[:secret], message: I18n.t('message.secret'),
    flags: 'secure', validator: Awskeyring::Validate.method(:secret_access_key)
  )
  Awskeyring::Awsapi.verify_cred(key: key, secret: secret) unless options['no-remote']
  Awskeyring.update_account(
    account: account,
    key: key,
    secret: secret
  )
  puts I18n.t('message.upaccount', account: account)
end

Private Instance Methods

age_check_and_get(account:, no_token:) click to toggle source

add warning about old keys

# File lib/awskeyring_command.rb, line 543
def age_check_and_get(account:, no_token:)
  cred = Awskeyring.get_valid_creds(account: account, no_token: no_token)

  maxage = Awskeyring.key_age
  age = (Time.new - cred[:updated]).div Awskeyring::Awsapi::ONE_DAY
  warn I18n.t('message.age_check', account: account, age: age) unless age < maxage

  cred
end
ask(message:, secure: false, optional: false, limited_to: nil) click to toggle source

ask in different ways

# File lib/awskeyring_command.rb, line 594
def ask(message:, secure: false, optional: false, limited_to: nil)
  if secure
    Awskeyring::Input.read_secret("#{message.rjust(20)}: ")
  elsif optional
    Thor::LineEditor.readline("#{"#{message} (optional)".rjust(20)}: ")
  elsif limited_to
    Thor::LineEditor.readline("#{message.rjust(20)}: ", limited_to: limited_to)
  else
    Thor::LineEditor.readline("#{message.rjust(20)}: ")
  end
end
ask_check(existing:, message:, flags: nil, validator: nil, limited_to: nil) click to toggle source

ask and validate input values.

# File lib/awskeyring_command.rb, line 568
def ask_check(existing:, message:, flags: nil, validator: nil, limited_to: nil) # rubocop:disable Metrics/MethodLength
  retries ||= 3
  begin
    value = ask_missing(
      existing: existing,
      message: message,
      secure: 'secure'.eql?(flags),
      optional: 'optional'.eql?(flags),
      limited_to: limited_to
    )
    value = validator.call(value) unless value.empty? && 'optional'.eql?(flags)
  rescue RuntimeError => e
    warn e.message
    existing = nil
    retry unless (retries -= 1).zero?
    exit 1
  end
  value
end
ask_missing(existing:, message:, secure: false, optional: false, limited_to: nil) click to toggle source

ask for somthinng if its missing.

# File lib/awskeyring_command.rb, line 589
def ask_missing(existing:, message:, secure: false, optional: false, limited_to: nil)
  existing || ask(message: message, secure: secure, optional: optional, limited_to: limited_to).strip
end
comp_type(comp_lines:, prev:) click to toggle source

determine the type of completion needed

# File lib/awskeyring_command.rb, line 466
def comp_type(comp_lines:, prev:)
  sub_cmd = sub_command(comp_lines)
  comp_idx = comp_lines.rindex(prev)

  case prev
  when '--path', '-p'
    comp_type = :path_type
  when '--browser', '-b'
    comp_type = :browser_type
  else
    comp_type = :command
    comp_type = param_type(comp_idx, sub_cmd) unless sub_cmd.empty?
  end

  [comp_type, sub_cmd]
end
default_duration(duration, role, code) click to toggle source

select duration for sts token types

# File lib/awskeyring_command.rb, line 561
def default_duration(duration, role, code)
  duration ||= Awskeyring::Awsapi::ONE_HOUR.to_s if role
  duration ||= Awskeyring::Awsapi::TWELVE_HOUR.to_s if code
  duration || Awskeyring::Awsapi::ONE_HOUR.to_s
end
fetch_auto_resp(comp_type, sub_cmd) click to toggle source

given a type return the right list for completions

# File lib/awskeyring_command.rb, line 508
def fetch_auto_resp(comp_type, sub_cmd)
  case comp_type
  when :command
    list_commands
  when :account
    Awskeyring.list_account_names
  when :role
    Awskeyring.list_role_names
  when :path_type
    Awskeyring.list_console_path
  when :token
    Awskeyring.list_token_names
  when :browser_type
    Awskeyring.list_browsers
  else
    list_arguments(command: sub_cmd)
  end
end
fix_args(curr, prev) click to toggle source

when a double dash is parsed it is dropped from the args but we need it

# File lib/awskeyring_command.rb, line 457
def fix_args(curr, prev)
  if prev.nil?
    [ARGV[1], ARGV[2]]
  else
    [curr, prev]
  end
end
list_arguments(command:) click to toggle source

list flags for a command

# File lib/awskeyring_command.rb, line 534
def list_arguments(command:)
  options = self.class.all_commands[command].options.values
  exit 1 if options.empty?

  options.map(&:aliases).flatten! +
    options.map(&:switch_name)
end
list_commands() click to toggle source

list command names

# File lib/awskeyring_command.rb, line 528
def list_commands
  commands = self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
  commands.reject! { |elem| %w[autocomplete default].include?(elem) }
end
param_type(comp_idx, sub_cmd) click to toggle source

check params for named params or fall back to flags

# File lib/awskeyring_command.rb, line 484
def param_type(comp_idx, sub_cmd)
  types = %i[opt req]
  param_list = method(sub_cmd).parameters.select { |elem| types.include? elem[0] }
  if comp_idx.zero?
    :command
  elsif comp_idx > param_list.length
    :flag
  else
    param_list[comp_idx - 1][1]
  end
end
put_env_string(cred) click to toggle source

print exports from map

# File lib/awskeyring_command.rb, line 554
def put_env_string(cred)
  env_var = Awskeyring::Awsapi.get_env_array(cred)
  env_var.each { |var, value| puts "export #{var}=\"#{value}\"" }
  Awskeyring::Awsapi::AWS_ENV_VARS.each { |key| puts "unset #{key}" unless env_var.key?(key) }
end
sub_command(comp_lines) click to toggle source

catch the command from prefixes and aliases

# File lib/awskeyring_command.rb, line 497
def sub_command(comp_lines)
  return '' if comp_lines.length < 2

  sub_cmd = comp_lines[1]

  return self.class.map[sub_cmd].to_s if self.class.map.key? sub_cmd

  (Awskeyring.solo_select(list_commands, sub_cmd) || '').tr('-', '_')
end
unbundle() click to toggle source

undo Bundler env vars

# File lib/awskeyring_command.rb, line 607
def unbundle
  to_delete = ENV.keys.select { |elem| elem.start_with?('BUNDLER_ORIG_') }
  bundled_env = to_delete.map { |elem| elem[('BUNDLER_ORIG_'.length)..] }
  to_delete << 'BUNDLE_GEMFILE'
  bundled_env.each do |env_name|
    ENV[env_name] = ENV["BUNDLER_ORIG_#{env_name}"]
    to_delete << env_name if ENV["BUNDLER_ORIG_#{env_name}"].start_with? 'BUNDLER_'
  end
  to_delete.each do |env_name|
    ENV.delete(env_name)
  end
end