1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
# frozen_string_literal: true
require 'securerandom'
module Fog
module AWS
module CredentialFetcher
INSTANCE_METADATA_HOST = "http://169.254.169.254"
INSTANCE_METADATA_TOKEN = "/latest/api/token"
INSTANCE_METADATA_PATH = "/latest/meta-data/iam/security-credentials/"
INSTANCE_METADATA_AZ = "/latest/meta-data/placement/availability-zone/"
CONTAINER_CREDENTIALS_HOST = "http://169.254.170.2"
module ServiceMethods
def fetch_credentials(options)
if options[:use_iam_profile] && Fog.mocking?
return Fog::AWS::Compute::Mock.data[:iam_role_based_creds]
end
if options[:use_iam_profile]
begin
role_data = nil
region = options[:region] || ENV["AWS_DEFAULT_REGION"]
if ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
connection = options[:connection] || Excon.new(CONTAINER_CREDENTIALS_HOST)
credential_path = options[:credential_path] || ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
role_data = connection.get(:path => credential_path, :idempotent => true, :expects => 200).body
session = Fog::JSON.decode(role_data)
if region.nil?
connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST)
token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
region = connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
end
elsif ENV["AWS_WEB_IDENTITY_TOKEN_FILE"]
params = {
:Action => "AssumeRoleWithWebIdentity",
:RoleArn => options[:role_arn] || ENV.fetch("AWS_ROLE_ARN"),
:RoleSessionName => options[:role_session_name] || ENV["AWS_ROLE_SESSION_NAME"] || "fog-aws-#{SecureRandom.hex}",
:WebIdentityToken => File.read(options[:aws_web_identity_token_file] || ENV.fetch("AWS_WEB_IDENTITY_TOKEN_FILE")),
:Version => "2011-06-15",
}
sts_endpoint =
if ENV["AWS_STS_REGIONAL_ENDPOINTS"] == "regional" && region
"https://sts.#{region}.amazonaws.com"
else
"https://sts.amazonaws.com"
end
connection = options[:connection] || Excon.new(sts_endpoint, :query => params)
document = Nokogiri::XML(connection.get(:idempotent => true, :expects => 200).body)
session = {
"AccessKeyId" => document.css("AccessKeyId").children.text,
"SecretAccessKey" => document.css("SecretAccessKey").children.text,
"Token" => document.css("SessionToken").children.text,
"Expiration" => document.css("Expiration").children.text,
}
if region.nil?
connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST)
token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
region = connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
end
else
connection = options[:connection] || Excon.new(INSTANCE_METADATA_HOST)
token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
role_name = connection.get(:path => INSTANCE_METADATA_PATH, :idempotent => true, :expects => 200, :headers => token_header).body
role_data = connection.get(:path => INSTANCE_METADATA_PATH+role_name, :idempotent => true, :expects => 200, :headers => token_header).body
session = Fog::JSON.decode(role_data)
region ||= connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
end
credentials = {}
credentials[:aws_access_key_id] = session['AccessKeyId']
credentials[:aws_secret_access_key] = session['SecretAccessKey']
credentials[:aws_session_token] = session['Token']
credentials[:aws_credentials_expire_at] = Time.xmlschema session['Expiration']
# set region by default to the one the instance is in.
credentials[:region] = region
credentials[:sts_endpoint] = sts_endpoint if sts_endpoint
#these indicate the metadata service is unavailable or has no profile setup
credentials
rescue Excon::Error => e
Fog::Logger.warning("Unable to fetch credentials: #{e.message}")
super
end
else
super
end
end
def fetch_credentials_token_header(connection, disable_imds_v2)
return nil if disable_imds_v2
token = connection.put(
:path => INSTANCE_METADATA_TOKEN,
:idempotent => true,
:expects => 200,
:retry_interval => 1,
:retry_limit => 3,
:read_timeout => 1,
:write_timeout => 1,
:connect_timeout => 1,
:headers => { "X-aws-ec2-metadata-token-ttl-seconds" => "300" }
).body
{ "X-aws-ec2-metadata-token" => token }
rescue Excon::Error
nil
end
end
module ConnectionMethods
def refresh_credentials_if_expired
refresh_credentials if credentials_expired?
end
private
# When defined, 'aws_credentials_refresh_threshold_seconds' controls
# when the credential needs to be refreshed, expressed in seconds before
# the current credential's expiration time
def credentials_refresh_threshold
@aws_credentials_refresh_threshold_seconds || 15
end
def credentials_expired?
@use_iam_profile &&
(!@aws_credentials_expire_at ||
(@aws_credentials_expire_at && Fog::Time.now > @aws_credentials_expire_at - credentials_refresh_threshold)) #new credentials become available from around 5 minutes before expiration time
end
def refresh_credentials
if @use_iam_profile
new_credentials = service.fetch_credentials :use_iam_profile => @use_iam_profile, :region => @region
if new_credentials.any?
setup_credentials new_credentials
return true
else
false
end
else
false
end
end
end
end
end
end
|