class AwsMfa

Attributes

aws_config_dir[R]

Public Class Methods

new() click to toggle source
# File lib/aws_mfa.rb, line 9
def initialize
  validate_aws_installed
  @aws_config_dir = find_aws_config
end

Public Instance Methods

execute() click to toggle source
# File lib/aws_mfa.rb, line 170
def execute
  profile = 'default'
  profile_index = ARGV.index('--profile')
  if (!profile_index.nil?)
    profile = ARGV.delete_at(profile_index + 1)
    ARGV.delete_at(profile_index)
  end
  arn = load_arn(profile)
  credentials = load_credentials(arn, profile)
  if ARGV.empty?
    print_credentials(credentials)
  else
    export_credentials(credentials)
    exec(*ARGV)
  end
end
export_credentials(credentials) click to toggle source
# File lib/aws_mfa.rb, line 163
def export_credentials(credentials)
  ENV['AWS_SECRET_ACCESS_KEY'] = credentials['SecretAccessKey']
  ENV['AWS_ACCESS_KEY_ID'] = credentials['AccessKeyId']
  ENV['AWS_SESSION_TOKEN'] = credentials['SessionToken']
  ENV['AWS_SECURITY_TOKEN'] = credentials['SessionToken']
end
find_aws_config() click to toggle source
# File lib/aws_mfa.rb, line 18
def find_aws_config
  if ENV['AWS_CREDENTIAL_FILE']
    aws_config_file = ENV['AWS_CREDENTIAL_FILE']
    aws_config_dir = File.dirname(aws_config_file)
  else
    aws_config_dir = File.join(ENV['HOME'], '.aws')
    aws_config_file = File.join(aws_config_dir, 'config')
  end

  unless File.readable?(aws_config_file)
    raise Errors::ConfigurationNotFound, 'Aws configuration not found. You must run `aws configure`'
  end

  aws_config_dir
end
load_arn(profile='default') click to toggle source
# File lib/aws_mfa.rb, line 46
def load_arn(profile='default')
  arn_file_name = 'mfa_device'
  if (! profile.eql? 'default')
    arn_file_name = "#{profile}_#{arn_file_name}"
  end
  arn_file = File.join(aws_config_dir, arn_file_name)

  if File.readable?(arn_file)
    arn = load_arn_from_file(arn_file)
  else
    arn = load_arn_from_aws(profile)
    write_arn_to_file(arn_file, arn)
  end

  arn
end
load_arn_from_aws(profile='default') click to toggle source
# File lib/aws_mfa.rb, line 67
def load_arn_from_aws(profile='default')
  puts 'Fetching MFA devices for your account...'
  if mfa_devices(profile).any?
    mfa_devices(profile).first.fetch('SerialNumber')
  else
    raise Errors::DeviceNotFound, 'No MFA devices were found for your account'
  end
end
load_arn_from_file(arn_file) click to toggle source
# File lib/aws_mfa.rb, line 63
def load_arn_from_file(arn_file)
  File.read(arn_file)
end
load_credentials(arn, profile='default') click to toggle source
# File lib/aws_mfa.rb, line 93
def load_credentials(arn, profile='default')
  credentials_file_name = 'mfa_credentials'
  if (! profile.eql? 'default')
    credentials_file_name = "#{profile}_#{credentials_file_name}"
  end
  credentials_file  = File.join(aws_config_dir, credentials_file_name)

  if File.readable?(credentials_file) && token_not_expired?(credentials_file)
    credentials = load_credentials_from_file(credentials_file)
  else
    credentials = load_credentials_from_aws(arn, profile)
    write_credentials_to_file(credentials_file, credentials)
  end

  JSON.parse(credentials).fetch('Credentials')
end
load_credentials_from_aws(arn, profile='default') click to toggle source
# File lib/aws_mfa.rb, line 114
def load_credentials_from_aws(arn, profile='default')
  code = request_code_from_user
  unset_environment
  credentials_command = "aws --profile #{profile} --output json sts get-session-token --serial-number #{arn} --token-code #{code}"
  result = AwsMfa::ShellCommand.new(credentials_command).call
  if result.succeeded?
    result.output
  else
    raise Errors::InvalidCode, 'There was a problem validating the MFA code with AWS'
  end
end
load_credentials_from_file(credentials_file) click to toggle source
# File lib/aws_mfa.rb, line 110
def load_credentials_from_file(credentials_file)
  File.read(credentials_file)
end
mfa_devices(profile='default') click to toggle source
# File lib/aws_mfa.rb, line 76
def mfa_devices(profile='default')
  @mfa_devices ||= begin
    list_mfa_devices_command = "aws --profile #{profile} --output json iam list-mfa-devices"
    result = AwsMfa::ShellCommand.new(list_mfa_devices_command).call
    if result.succeeded?
      JSON.parse(result.output).fetch('MFADevices')
    else
      raise Errors::Error, 'There was a problem fetching MFA devices from AWS'
    end
  end
end
print_credentials(credentials) click to toggle source
request_code_from_user() click to toggle source
# File lib/aws_mfa.rb, line 130
def request_code_from_user
  puts 'Enter the 6-digit code from your MFA device:'
  code = $stdin.gets.chomp
  raise Errors::InvalidCode, 'That is an invalid MFA code' unless code =~ /^\d{6}$/
  code
end
token_not_expired?(credentials_file) click to toggle source
# File lib/aws_mfa.rb, line 144
def token_not_expired?(credentials_file)
  # default is 12 hours
  expiration_period = 12 * 60 * 60
  mtime = File.stat(credentials_file).mtime
  now = Time.new
  if now - mtime < expiration_period
    true
  else
    false
  end
end
unset_environment() click to toggle source
# File lib/aws_mfa.rb, line 137
def unset_environment
  ENV.delete('AWS_SECRET_ACCESS_KEY')
  ENV.delete('AWS_ACCESS_KEY_ID')
  ENV.delete('AWS_SESSION_TOKEN')
  ENV.delete('AWS_SECURITY_TOKEN')
end
validate_aws_installed() click to toggle source
# File lib/aws_mfa.rb, line 14
def validate_aws_installed
  raise Errors::CommandNotFound, 'Could not find the aws command' unless which('aws')
end
which(cmd) click to toggle source

stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby

# File lib/aws_mfa.rb, line 35
def which(cmd)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each { |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable? exe
    }
  end
  return nil
end
write_arn_to_file(arn_file, arn) click to toggle source
# File lib/aws_mfa.rb, line 88
def write_arn_to_file(arn_file, arn)
  File.open(arn_file, 'w') { |f| f.print arn }
  puts "Using MFA device #{arn}. To change this in the future edit #{arn_file}."
end
write_credentials_to_file(credentials_file, credentials) click to toggle source
# File lib/aws_mfa.rb, line 126
def write_credentials_to_file(credentials_file, credentials)
  File.open(credentials_file, 'w') { |f| f.print credentials }
end