Class: AwsMfaSecure::Base

Inherits:
Object
  • Object
show all
Extended by:
Memoist
Defined in:
lib/aws_mfa_secure/base.rb

Direct Known Subclasses

Credentials, Exports, Session, Unsets

Instance Method Summary collapse

Instance Method Details

#aws_cli_installed?Boolean

Returns:

  • (Boolean)


34
35
36
37
# File 'lib/aws_mfa_secure/base.rb', line 34

def aws_cli_installed?
  return false unless File.exist?("#{ENV['HOME']}/.aws")
  system("type aws > /dev/null 2>&1")
end

#aws_cli_setup?Boolean

Returns:

  • (Boolean)


39
40
41
42
# File 'lib/aws_mfa_secure/base.rb', line 39

def aws_cli_setup?
  File.exist?("#{ENV['HOME']}/.aws/config") &&
  File.exist?("#{ENV['HOME']}/.aws/credentials")
end

#aws_config(prop) ⇒ Object



134
135
136
137
138
139
# File 'lib/aws_mfa_secure/base.rb', line 134

def aws_config(prop)
  profile_data = AWSConfig[aws_profile]
  return unless profile_data
  v = profile_data[prop.to_s]
  v unless v.blank?
end

#aws_mfa_env_set?Boolean

Returns:

  • (Boolean)


28
29
30
31
32
# File 'lib/aws_mfa_secure/base.rb', line 28

def aws_mfa_env_set?
  ENV['AWS_ACCESS_KEY_ID'] &&
  ENV['AWS_SECRET_ACCESS_KEY'] &&
  ENV['AWS_MFA_SERIAL']
end

#aws_profileObject



142
143
144
# File 'lib/aws_mfa_secure/base.rb', line 142

def aws_profile
  ENV['AWS_PROFILE'] || 'default'
end

#credentialsObject



55
56
57
# File 'lib/aws_mfa_secure/base.rb', line 55

def credentials
  JSON.load(IO.read(session_creds_path))
end

#fetch_creds?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/aws_mfa_secure/base.rb', line 44

def fetch_creds?
  !good_session_creds?
end

#get_session_token(shell: false) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/aws_mfa_secure/base.rb', line 70

def get_session_token(shell: false)
  retries = 0
  begin
    token_code = mfa_prompt
    options = {
      serial_number: mfa_serial,
      token_code: token_code,
    }
    options[:duration_seconds] = ENV['AWS_MFA_TTL'] if ENV['AWS_MFA_TTL']

    if shell
      shell_get_session_token(options) # mimic ruby sdk
    else # ruby sdk
      sts.get_session_token(options)
    end
  rescue Aws::STS::Errors::ValidationError, Aws::STS::Errors::AccessDenied, MfaError => e
    $stderr.puts "#{e.class}: #{e.message}"
    $stderr.puts "Incorrect MFA code.  Please try again."
    retries += 1
    if retries >= 3
      $stderr.puts "Giving up after #{retries} retries."
      exit 1
    end
    retry
  end
end

#good_session_creds?Boolean

Returns:

  • (Boolean)


48
49
50
51
52
53
# File 'lib/aws_mfa_secure/base.rb', line 48

def good_session_creds?
  return false unless File.exist?(session_creds_path)

  expiration = Time.parse(credentials["expiration"])
  Time.now.utc < expiration # not expired
end

#iam_mfa?Boolean

Returns:

  • (Boolean)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/aws_mfa_secure/base.rb', line 13

def iam_mfa?
  return true if aws_mfa_env_set?
  return false unless aws_cli_installed? && aws_cli_setup?
  return false unless mfa_serial

  # The iam_mfa? check will only return true for the case when mfa_serial is set and access keys are used.
  # This is because for assume role cases, the current aws cli tool supports mfa_serial already.
  # Sending session AWS based access keys intefere with the current aws cli assume role mfa_serial support
  aws_access_key_id = aws_config(:aws_access_key_id)
  aws_secret_access_key = aws_config(:aws_secret_access_key)
  source_profile = aws_config(:source_profile)

  aws_access_key_id && aws_secret_access_key && !source_profile
end

#mfa_promptObject



97
98
99
100
101
102
103
104
105
# File 'lib/aws_mfa_secure/base.rb', line 97

def mfa_prompt
  if ENV['AWS_MFA_TOKEN']
    token_code = ENV.delete('AWS_MFA_TOKEN') # only use once, prompt afterwards if incorrect
    return token_code
  end

  $stderr.print "Please provide your MFA code: "
  $stdin.gets.strip
end

#mfa_serialObject



125
126
127
# File 'lib/aws_mfa_secure/base.rb', line 125

def mfa_serial
  ENV['AWS_MFA_SERIAL'] || aws_config(:mfa_serial)
end

#save_creds(credentials) ⇒ Object



60
61
62
63
64
# File 'lib/aws_mfa_secure/base.rb', line 60

def save_creds(credentials)
  FileUtils.mkdir_p(File.dirname(session_creds_path))
  IO.write(session_creds_path, JSON.pretty_generate(credentials))
  flush_cache # Clear memo cache. Not needed for brand new temp credentials, but needed when updating existing ones
end

#session_creds_pathObject



66
67
68
# File 'lib/aws_mfa_secure/base.rb', line 66

def session_creds_path
  "#{SESSIONS_PATH}/#{@aws_profile}"
end

#shell_get_session_token(options) ⇒ Object

Credentials class uses this version of get-session-token to allow the AWS Ruby SDK itself to be patched.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/aws_mfa_secure/base.rb', line 108

def shell_get_session_token(options)
  args = options.map { |k,v| "--#{k.to_s.gsub('_','-')} #{v}" }.join(' ')
  command = "aws sts get-session-token #{args} 2>&1"
  # puts "=> #{command}" # uncomment for debugging
  out = `#{command}`

  unless out.include?("Credentials")
    raise(MfaError, out.strip) # custom error
  end

  data = JSON.load(out)
  resp = data.deep_transform_keys { |k| k.underscore }
  # mimic ruby sdk resp
  credentials = Aws::STS::Types::Credentials.new(resp["credentials"])
  Aws::STS::Types::GetSessionTokenResponse.new(credentials: credentials)
end

#stsObject



129
130
131
# File 'lib/aws_mfa_secure/base.rb', line 129

def sts
  Aws::STS::Client.new
end