Class: AwsMfa

Inherits:
Object
  • Object
show all
Defined in:
lib/aws_mfa.rb,
lib/aws_mfa/errors.rb

Defined Under Namespace

Classes: Errors

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAwsMfa

Returns a new instance of AwsMfa.



7
8
9
10
# File 'lib/aws_mfa.rb', line 7

def initialize
  validate_aws_installed
  @aws_config_dir = find_aws_config
end

Instance Attribute Details

#aws_config_dirObject (readonly)

Returns the value of attribute aws_config_dir.



5
6
7
# File 'lib/aws_mfa.rb', line 5

def aws_config_dir
  @aws_config_dir
end

Instance Method Details

#executeObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/aws_mfa.rb', line 154

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) ⇒ Object



147
148
149
150
151
152
# File 'lib/aws_mfa.rb', line 147

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_configObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/aws_mfa.rb', line 16

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') ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/aws_mfa.rb', line 44

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') ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/aws_mfa.rb', line 65

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) ⇒ Object



61
62
63
# File 'lib/aws_mfa.rb', line 61

def load_arn_from_file(arn_file)
  File.read(arn_file)
end

#load_credentials(arn, profile = 'default') ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/aws_mfa.rb', line 83

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') ⇒ Object



104
105
106
107
108
# File 'lib/aws_mfa.rb', line 104

def load_credentials_from_aws(arn, profile='default')
  code = request_code_from_user
  unset_environment
  `aws --profile #{profile} --output json sts get-session-token --serial-number #{arn} --token-code #{code}`
end

#load_credentials_from_file(credentials_file) ⇒ Object



100
101
102
# File 'lib/aws_mfa.rb', line 100

def load_credentials_from_file(credentials_file)
  File.read(credentials_file)
end

#mfa_devices(profile = 'default') ⇒ Object



74
75
76
# File 'lib/aws_mfa.rb', line 74

def mfa_devices(profile='default')
  @mfa_devices ||= JSON.parse(`aws --profile #{profile} --output json iam list-mfa-devices`).fetch('MFADevices')
end


140
141
142
143
144
145
# File 'lib/aws_mfa.rb', line 140

def print_credentials(credentials)
  puts "export AWS_SECRET_ACCESS_KEY='#{credentials['SecretAccessKey']}'"
  puts "export AWS_ACCESS_KEY_ID='#{credentials['AccessKeyId']}'"
  puts "export AWS_SESSION_TOKEN='#{credentials['SessionToken']}'"
  puts "export AWS_SECURITY_TOKEN='#{credentials['SessionToken']}'"
end

#request_code_from_userObject



114
115
116
117
118
119
# File 'lib/aws_mfa.rb', line 114

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) ⇒ Boolean

Returns:

  • (Boolean)


128
129
130
131
132
133
134
135
136
137
138
# File 'lib/aws_mfa.rb', line 128

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_environmentObject



121
122
123
124
125
126
# File 'lib/aws_mfa.rb', line 121

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_installedObject



12
13
14
# File 'lib/aws_mfa.rb', line 12

def validate_aws_installed
  raise Errors::CommandNotFound, 'Could not find the aws command' unless which('aws')
end

#which(cmd) ⇒ Object



33
34
35
36
37
38
39
40
41
42
# File 'lib/aws_mfa.rb', line 33

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) ⇒ Object



78
79
80
81
# File 'lib/aws_mfa.rb', line 78

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) ⇒ Object



110
111
112
# File 'lib/aws_mfa.rb', line 110

def write_credentials_to_file(credentials_file, credentials)
  File.open(credentials_file, 'w') { |f| f.print credentials }
end