lib/awskeyring_command.rb in awskeyring-0.0.6 vs lib/awskeyring_command.rb in awskeyring-0.1.0
- old
+ new
@@ -1,33 +1,33 @@
-require 'aws-sdk-iam'
-require 'cgi'
require 'highline'
-require 'json'
-require 'open-uri'
require 'thor'
-require_relative 'awskeyring'
+require 'awskeyring'
+require 'awskeyring/awsapi'
+require 'awskeyring/validate'
require 'awskeyring/version'
-# AWS Key-ring command line interface.
+# AWSkeyring command line interface.
class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
map %w[--version -v] => :__version
map ['init'] => :initialise
map ['ls'] => :list
map ['lsr'] => :list_role
map ['rm'] => :remove
map ['rmr'] => :remove_role
map ['rmt'] => :remove_token
desc '--version, -v', 'Prints the version'
+ # print the version number
def __version
puts Awskeyring::VERSION
end
desc 'initialise', 'Initialises a new KEYCHAIN'
method_option :keychain, type: :string, aliases: '-n', desc: 'Name of KEYCHAIN to initialise.'
- def initialise # rubocop:disable Metrics/AbcSize
+ # initialise the keychain
+ def initialise
unless Awskeyring.prefs.empty?
puts "#{Awskeyring::PREFS_FILE} exists. no need to initialise."
exit 1
end
@@ -45,79 +45,89 @@
puts "Add accounts to your #{keychain} keychain with:"
puts " #{exec_name} add"
end
desc 'list', 'Prints a list of accounts in the keyring'
+ # list the accounts
def list
- puts Awskeyring.list_item_names.join("\n")
+ puts Awskeyring.list_account_names.join("\n")
end
map 'list-role' => :list_role
desc 'list-role', 'Prints a list of roles in the keyring'
+ # List roles
def list_role
puts Awskeyring.list_role_names.join("\n")
end
desc 'env ACCOUNT', 'Outputs bourne shell environment exports for an ACCOUNT'
+ # Print Env vars
def env(account = nil)
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- cred, temp_cred = get_valid_item_pair(account: account)
- token = temp_cred.password unless temp_cred.nil?
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
+ cred = Awskeyring.get_valid_creds(account: account)
put_env_string(
- account: cred.attributes[:label],
- key: cred.attributes[:account],
- secret: cred.password,
- token: token
+ account: cred[:account],
+ key: cred[:key],
+ secret: cred[:secret],
+ token: cred[:token]
)
end
desc 'exec ACCOUNT command...', 'Execute a COMMAND with the environment set for an ACCOUNT'
+ # execute an external command with env set
def exec(account, *command)
- cred, temp_cred = get_valid_item_pair(account: account)
- token = temp_cred.password unless temp_cred.nil?
+ cred = Awskeyring.get_valid_creds(account: account)
env_vars = env_vars(
- account: cred.attributes[:label],
- key: cred.attributes[:account],
- secret: cred.password,
- token: token
+ account: cred[:account],
+ key: cred[:key],
+ secret: cred[:secret],
+ token: cred[:token]
)
pid = Process.spawn(env_vars, command.join(' '))
Process.wait pid
end
desc 'add ACCOUNT', 'Adds an ACCOUNT to the keyring'
method_option :key, type: :string, aliases: '-k', desc: 'AWS account key id.'
method_option :secret, type: :string, aliases: '-s', desc: 'AWS account secret.'
method_option :mfa, type: :string, aliases: '-m', desc: 'AWS virtual mfa arn.'
- def add(account = nil) # rubocop:disable Metrics/AbcSize
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- key = ask_check(existing: options[:key], message: 'access key id', validator: Awskeyring.method(:access_key))
+ # Add an Account
+ def add(account = nil) # rubocop:disable Metrics/MethodLength
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
+ key = ask_check(
+ existing: options[:key], message: 'access key id', validator: Awskeyring::Validate.method(:access_key)
+ )
secret = ask_check(
existing: options[:secret], message: 'secret access key',
- secure: true, validator: Awskeyring.method(:secret_access_key)
+ secure: true, validator: Awskeyring::Validate.method(:secret_access_key)
)
mfa = ask_check(
- existing: options[:mfa], message: 'mfa arn', optional: true, validator: Awskeyring.method(:mfa_arn)
+ existing: options[:mfa], message: 'mfa arn', optional: true, validator: Awskeyring::Validate.method(:mfa_arn)
)
- Awskeyring.add_item(
+ Awskeyring.add_account(
account: account,
key: key,
secret: secret,
- comment: mfa
+ mfa: mfa
)
puts "# Added account #{account}"
end
map 'add-role' => :add_role
desc 'add-role ROLE', 'Adds a ROLE to the keyring'
method_option :arn, type: :string, aliases: '-a', desc: 'AWS role arn.'
+ # Add a role
def add_role(role = nil)
- role = ask_check(existing: role, message: 'role name', validator: Awskeyring.method(:role_name))
- arn = ask_check(existing: options[:arn], message: 'role arn', validator: Awskeyring.method(:role_arn))
+ role = ask_check(existing: role, message: 'role name', validator: Awskeyring::Validate.method(:role_name))
+ arn = ask_check(existing: options[:arn], message: 'role arn', validator: Awskeyring::Validate.method(:role_arn))
account = ask_check(
- existing: account, message: 'account', optional: true, validator: Awskeyring.method(:account_name)
+ existing: account, message: 'account', optional: true, validator: Awskeyring::Validate.method(:account_name)
)
Awskeyring.add_role(
role: role,
arn: arn,
@@ -125,68 +135,61 @@
)
puts "# Added role #{role}"
end
desc 'remove ACCOUNT', 'Removes an ACCOUNT from the keyring'
+ # Remove an account
def remove(account = nil)
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- cred, temp_cred = get_valid_item_pair(account: account)
- Awskeyring.delete_pair(cred, temp_cred, "# Removing account #{account}")
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
+ Awskeyring.delete_account(account: account, message: "# Removing account #{account}")
end
desc 'remove-token ACCOUNT', 'Removes a token for ACCOUNT from the keyring'
+ # remove a session token
def remove_token(account = nil)
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- session_key, session_token = Awskeyring.get_pair(account)
- session_key, session_token = Awskeyring.delete_expired(session_key, session_token) if session_key
- Awskeyring.delete_pair(session_key, session_token, "# Removing token for account #{account}") if session_key
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
+ Awskeyring.delete_token(account: account, message: "# Removing token for account #{account}")
end
map 'remove-role' => :remove_role
desc 'remove-role ROLE', 'Removes a ROLE from the keyring'
+ # remove a role
def remove_role(role = nil)
- role = ask_check(existing: role, message: 'role name', validator: Awskeyring.method(:role_name))
- item_role = Awskeyring.get_role(role)
- Awskeyring.delete_pair(item_role, nil, "# Removing role #{role}")
+ role = ask_check(existing: role, message: 'role name', validator: Awskeyring::Validate.method(:role_name))
+ Awskeyring.delete_role(role_name: role, message: "# Removing role #{role}")
end
desc 'rotate ACCOUNT', 'Rotate access keys for an ACCOUNT'
- def rotate(account = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- item = Awskeyring.get_item(account)
- iam = Aws::IAM::Client.new(access_key_id: item.attributes[:account], secret_access_key: item.password)
-
- if iam.list_access_keys[:access_key_metadata].length > 1
- warn "You have two access keys for account #{account}"
- exit 1
- end
-
- new_key = iam.create_access_key
- iam = Aws::IAM::Client.new(
- access_key_id: new_key[:access_key][:access_key_id],
- secret_access_key: new_key[:access_key][:secret_access_key]
+ # rotate Account keys
+ def rotate(account = nil)
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
)
- retry_backoff do
- iam.delete_access_key(
- access_key_id: item.attributes[:account]
- )
- end
- Awskeyring.update_item(
+ item_hash = Awskeyring.get_account_hash(account: account)
+ new_key = Awskeyring::Awsapi.rotate(account: item_hash[:account], key: item_hash[:key], secret: item_hash[:secret])
+ Awskeyring.update_account(
account: account,
- key: new_key[:access_key][:access_key_id],
- secret: new_key[:access_key][:secret_access_key]
+ key: new_key[:key],
+ secret: new_key[:secret]
)
puts "# Updated account #{account}"
end
desc 'token ACCOUNT [ROLE] [MFA]', 'Create an STS Token from a ROLE or an MFA code'
method_option :role, type: :string, aliases: '-r', desc: 'The ROLE to assume.'
method_option :code, type: :string, aliases: '-c', desc: 'Virtual mfa CODE.'
method_option :duration, type: :string, aliases: '-d', desc: 'Session DURATION in seconds.'
+ # generate a sessiopn token
def token(account = nil, role = nil, code = nil) # rubocop:disable all
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
role ||= options[:role]
code ||= options[:code]
duration = options[:duration]
duration ||= (60 * 60 * 1).to_s if role
duration ||= (60 * 60 * 12).to_s if code
@@ -194,115 +197,62 @@
if !role && !code
warn 'Please use either a role or a code'
exit 2
end
- session_key, session_token = Awskeyring.get_pair(account)
- Awskeyring.delete_pair(session_key, session_token, '# Removing STS credentials') if session_key
+ Awskeyring.delete_token(account: account, message: '# Removing STS credentials')
- item = Awskeyring.get_item(account)
- item_role = Awskeyring.get_role(role) if role
+ item_hash = Awskeyring.get_account_hash(account: account)
+ role_arn = Awskeyring.get_role_arn(role_name: role) if role
- sts = Aws::STS::Client.new(access_key_id: item.attributes[:account], secret_access_key: item.password)
+ new_creds = Awskeyring::Awsapi.get_token(
+ code: code,
+ role_arn: role_arn,
+ duration: duration,
+ mfa: item_hash[:mfa],
+ key: item_hash[:key],
+ secret: item_hash[:secret],
+ user: ENV['USER']
+ )
- begin
- response =
- if code && role
- sts.assume_role(
- duration_seconds: duration.to_i,
- role_arn: item_role.attributes[:account],
- role_session_name: ENV['USER'],
- serial_number: item.attributes[:comment],
- token_code: code
- )
- elsif role
- sts.assume_role(
- duration_seconds: duration.to_i,
- role_arn: item_role.attributes[:account],
- role_session_name: ENV['USER']
- )
- elsif code
- sts.get_session_token(
- duration_seconds: duration.to_i,
- serial_number: item.attributes[:comment],
- token_code: code
- )
- end
- rescue Aws::STS::Errors::AccessDenied => e
- puts e.to_s
- exit 1
- end
-
- Awskeyring.add_pair(
+ Awskeyring.add_token(
account: account,
- key: response.credentials[:access_key_id],
- secret: response.credentials[:secret_access_key],
- token: response.credentials[:session_token],
- expiry: response.credentials[:expiration].to_i.to_s,
+ key: new_creds[:key],
+ secret: new_creds[:secret],
+ token: new_creds[:token],
+ expiry: new_creds[:expiry].to_i.to_s,
role: role
)
- puts "Authentication valid until #{response.credentials[:expiration]}"
+ puts "Authentication valid until #{new_creds[:expiry]}"
end
desc 'console ACCOUNT', 'Open the AWS Console for the ACCOUNT'
method_option :path, type: :string, aliases: '-p', desc: 'The service PATH to open.'
- def console(account = nil) # rubocop:disable all
- account = ask_check(existing: account, message: 'account name', validator: Awskeyring.method(:account_name))
- cred, temp_cred = get_valid_item_pair(account: account)
- token = temp_cred.password unless temp_cred.nil?
+ # Open the AWS Console
+ def console(account = nil)
+ account = ask_check(
+ existing: account, message: 'account name', validator: Awskeyring::Validate.method(:account_name)
+ )
+ cred = Awskeyring.get_valid_creds(account: account)
path = options[:path] || 'console'
- console_url = "https://console.aws.amazon.com/#{path}/home"
- signin_url = 'https://signin.aws.amazon.com/federation'
- policy_json = {
- Version: '2012-10-17',
- Statement: [{
- Action: '*',
- Resource: '*',
- Effect: 'Allow'
- }]
- }.to_json
+ login_url = Awskeyring::Awsapi.get_login_url(
+ key: cred[:key],
+ secret: cred[:secret],
+ token: cred[:token],
+ path: path,
+ user: ENV['USER']
+ )
- if temp_cred
- session_json = {
- sessionId: cred.attributes[:account],
- sessionKey: cred.password,
- sessionToken: token
- }.to_json
- else
- sts = Aws::STS::Client.new(access_key_id: cred.attributes[:account],
- secret_access_key: cred.password)
-
- session = sts.get_federation_token(name: ENV['USER'],
- policy: policy_json,
- duration_seconds: (60 * 60 * 12))
- session_json = {
- sessionId: session.credentials[:access_key_id],
- sessionKey: session.credentials[:secret_access_key],
- sessionToken: session.credentials[:session_token]
- }.to_json
-
- end
- get_signin_token_url = signin_url + '?Action=getSigninToken' \
- '&Session=' + CGI.escape(session_json)
-
- returned_content = open(get_signin_token_url).read
-
- signin_token = JSON.parse(returned_content)['SigninToken']
- signin_token_param = '&SigninToken=' + CGI.escape(signin_token)
- destination_param = '&Destination=' + CGI.escape(console_url)
-
- login_url = signin_url + '?Action=login' + signin_token_param + destination_param
-
pid = Process.spawn("open \"#{login_url}\"")
Process.wait pid
end
- # autocomplete
desc 'awskeyring CURR PREV', 'Autocompletion for bourne shells', hide: true
+ # autocomplete
def awskeyring(curr, prev)
comp_line = ENV['COMP_LINE']
unless comp_line
exec_name = File.basename($PROGRAM_NAME)
warn "enable autocomplete with 'complete -C /path-to-command/#{exec_name} #{exec_name}'"
@@ -316,16 +266,16 @@
print_auto_resp(curr, comp_len)
end
private
- def print_auto_resp(curr, len) # rubocop:disable Metrics/AbcSize
+ def print_auto_resp(curr, len)
case len
when 2
puts list_commands.select { |elem| elem.start_with?(curr) }.join("\n")
when 3
- puts Awskeyring.list_item_names.select { |elem| elem.start_with?(curr) }.join("\n")
+ puts Awskeyring.list_account_names.select { |elem| elem.start_with?(curr) }.join("\n")
when 4
puts Awskeyring.list_role_names.select { |elem| elem.start_with?(curr) }.join("\n")
else
exit 1
end
@@ -333,27 +283,10 @@
def list_commands
self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
end
- def get_valid_item_pair(account:)
- session_key, session_token = Awskeyring.get_pair(account)
- session_key, session_token = Awskeyring.delete_expired(session_key, session_token) if session_key
-
- if session_key && session_token
- puts '# Using temporary session credentials'
- return session_key, session_token
- end
-
- item = Awskeyring.get_item(account)
- if item.nil?
- warn "# Credential not found with name: #{account}"
- exit 2
- end
- [item, nil]
- end
-
def env_vars(account:, key:, secret:, token:)
env_var = {}
env_var['AWS_DEFAULT_REGION'] = 'us-east-1' unless ENV['AWS_DEFAULT_REGION']
env_var['AWS_ACCOUNT_NAME'] = account
env_var['AWS_ACCESS_KEY_ID'] = key
@@ -384,24 +317,9 @@
warn e.message
retry unless (retries -= 1).zero?
exit 1
end
value
- end
-
- def retry_backoff(&block)
- retries ||= 1
- begin
- yield block
- rescue Aws::IAM::Errors::InvalidClientTokenId => e
- if retries < 4
- sleep 2**retries
- retries += 1
- retry
- end
- warn e.message
- exit 1
- end
end
def ask_missing(existing:, message:, secure: false, optional: false)
existing || ask(message: message, secure: secure, optional: optional)
end