Class: AwsMfa

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

Defined Under Namespace

Classes: Errors, ShellCommand, ShellCommandResult

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAwsMfa

Returns a new instance of AwsMfa.



9
10
11
12
# File 'lib/aws_mfa.rb', line 9

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.



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

def aws_config_dir
  @aws_config_dir
end

Instance Method Details

#executeObject



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# 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) ⇒ Object



163
164
165
166
167
168
# 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_configObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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') ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 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') ⇒ Object



67
68
69
70
71
72
73
74
# 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) ⇒ Object



63
64
65
# File 'lib/aws_mfa.rb', line 63

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

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



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# 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') ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
# 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) ⇒ Object



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

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

#mfa_devices(profile = 'default') ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# 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


156
157
158
159
160
161
# File 'lib/aws_mfa.rb', line 156

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



130
131
132
133
134
135
# 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) ⇒ Boolean

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
153
154
# 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_environmentObject



137
138
139
140
141
142
# 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_installedObject



14
15
16
# 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) ⇒ Object



35
36
37
38
39
40
41
42
43
44
# 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) ⇒ Object



88
89
90
91
# 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) ⇒ Object



126
127
128
# 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