class Aws::InstanceProfileCredentials

Constants

METADATA_PATH_BASE

Path base for GET request for profile and credentials @api private

METADATA_TOKEN_PATH

Path for PUT request for token @api private

NETWORK_ERRORS

These are the errors we trap when attempting to talk to the instance metadata service. Any of these imply the service is not present, no responding or some other non-recoverable error. @api private

Attributes

retries[R]

@return [Integer] Number of times to retry when retrieving credentials

from the instance metadata service. Defaults to 0 when resolving from
the default credential chain ({Aws::CredentialProviderChain}).

Public Class Methods

new(options = {}) click to toggle source

@param [Hash] options @option options [Integer] :retries (1) Number of times to retry

when retrieving credentials.

@option options [String] :endpoint ('169.254.169.254') The IMDS

endpoint. This option has precedence over the :endpoint_mode.

@option options [String] :endpoint_mode ('IPv4') The endpoint mode for

the instance metadata service. This is either 'IPv4' ('169.254.169.254')
or 'IPv6' ('[fd00:ec2::254]').

@option options [String] :ip_address ('169.254.169.254') Deprecated. Use

:endpoint instead. The IP address for the endpoint.

@option options [Integer] :port (80) @option options [Float] :http_open_timeout (1) @option options [Float] :http_read_timeout (1) @option options [Numeric, Proc] :delay By default, failures are retried

with exponential back-off, i.e. `sleep(1.2 ** num_failures)`. You can
pass a number of seconds to sleep between failed attempts, or
a Proc that accepts the number of failures.

@option options [IO] :http_debug_output (nil) HTTP wire

traces are sent to this object.  You can specify something
like $stdout.

@option options [Integer] :token_ttl Time-to-Live in seconds for EC2

Metadata Token used for fetching Metadata Profile Credentials, defaults
to 21600 seconds

@option options [Callable] before_refresh Proc called before

credentials are refreshed. `before_refresh` is called
with an instance of this object when
AWS credentials are required and need to be refreshed.
Calls superclass method Aws::RefreshingCredentials::new
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 70
def initialize(options = {})
  @retries = options[:retries] || 1
  endpoint_mode = resolve_endpoint_mode(options)
  @endpoint = resolve_endpoint(options, endpoint_mode)
  @port = options[:port] || 80
  @http_open_timeout = options[:http_open_timeout] || 1
  @http_read_timeout = options[:http_read_timeout] || 1
  @http_debug_output = options[:http_debug_output]
  @backoff = backoff(options[:backoff])
  @token_ttl = options[:token_ttl] || 21_600
  @token = nil
  @no_refresh_until = nil
  @async_refresh = false
  super
end

Private Instance Methods

_metadata_disabled?() click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 229
def _metadata_disabled?
  ENV.fetch('AWS_EC2_METADATA_DISABLED', 'false').downcase == 'true'
end
backoff(backoff) click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 121
def backoff(backoff)
  case backoff
  when Proc then backoff
  when Numeric then ->(_) { sleep(backoff) }
  else ->(num_failures) { Kernel.sleep(1.2**num_failures) }
  end
end
empty_credentials?(creds) click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 301
def empty_credentials?(creds)
  !creds || !creds.access_key_id || creds.access_key_id.empty?
end
get_credentials() click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 178
def get_credentials
  # Retry loading credentials a configurable number of times if
  # the instance metadata service is not responding.
  if _metadata_disabled?
    '{}'
  else
    begin
      retry_errors(NETWORK_ERRORS, max_retries: @retries) do
        open_connection do |conn|
          # attempt to fetch token to start secure flow first
          # and rescue to failover
          begin
            retry_errors(NETWORK_ERRORS, max_retries: @retries) do
              unless token_set?
                created_time = Time.now
                token_value, ttl = http_put(
                  conn, METADATA_TOKEN_PATH, @token_ttl
                )
                @token = Token.new(token_value, ttl, created_time) if token_value && ttl
              end
            end
          rescue *NETWORK_ERRORS
            # token attempt failed, reset token
            # fallback to non-token mode
            @token = nil
          end

          token = @token.value if token_set?

          begin
            metadata = http_get(conn, METADATA_PATH_BASE, token)
            profile_name = metadata.lines.first.strip
            http_get(conn, METADATA_PATH_BASE + profile_name, token)
          rescue TokenExpiredError
            # Token has expired, reset it
            # The next retry should fetch it
            @token = nil
            raise Non200Response
          end
        end
      end
    rescue
      '{}'
    end
  end
end
http_get(connection, path, token = nil) click to toggle source

GET request fetch profile and credentials

# File lib/aws-sdk-core/instance_profile_credentials.rb, line 244
def http_get(connection, path, token = nil)
  headers = { 'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}" }
  headers['x-aws-ec2-metadata-token'] = token if token
  response = connection.request(Net::HTTP::Get.new(path, headers))

  case response.code.to_i
  when 200
    response.body
  when 401
    raise TokenExpiredError
  else
    raise Non200Response
  end
end
http_put(connection, path, ttl) click to toggle source

PUT request fetch token with ttl

# File lib/aws-sdk-core/instance_profile_credentials.rb, line 260
def http_put(connection, path, ttl)
  headers = {
    'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}",
    'x-aws-ec2-metadata-token-ttl-seconds' => ttl.to_s
  }
  response = connection.request(Net::HTTP::Put.new(path, headers))
  case response.code.to_i
  when 200
    [
      response.body,
      response.header['x-aws-ec2-metadata-token-ttl-seconds'].to_i
    ]
  when 400
    raise TokenRetrivalError
  when 401
    raise TokenExpiredError
  else
    raise Non200Response
  end
end
open_connection() { |http| ... } click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 233
def open_connection
  uri = URI.parse(@endpoint)
  http = Net::HTTP.new(uri.hostname || @endpoint, @port || uri.port)
  http.open_timeout = @http_open_timeout
  http.read_timeout = @http_read_timeout
  http.set_debug_output(@http_debug_output) if @http_debug_output
  http.start
  yield(http).tap { http.finish }
end
refresh() click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 129
def refresh
  if @no_refresh_until && @no_refresh_until > Time.now
    warn_expired_credentials
    return
  end

  # Retry loading credentials up to 3 times is the instance metadata
  # service is responding but is returning invalid JSON documents
  # in response to the GET profile credentials call.
  begin
    retry_errors([Aws::Json::ParseError, StandardError], max_retries: 3) do
      c = Aws::Json.load(get_credentials.to_s)
      if empty_credentials?(@credentials)
        @credentials = Credentials.new(
          c['AccessKeyId'],
          c['SecretAccessKey'],
          c['Token']
        )
        @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
        if @expiration && @expiration < Time.now
          @no_refresh_until = Time.now + refresh_offset
          warn_expired_credentials
        end
      else
        #  credentials are already set, update them only if the new ones are not empty
        if !c['AccessKeyId'] || c['AccessKeyId'].empty?
          # error getting new credentials
          @no_refresh_until = Time.now + refresh_offset
          warn_expired_credentials
        else
          @credentials = Credentials.new(
            c['AccessKeyId'],
            c['SecretAccessKey'],
            c['Token']
          )
          @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
          if @expiration && @expiration < Time.now
            @no_refresh_until = Time.now + refresh_offset
            warn_expired_credentials
          end
        end
      end

    end
  rescue Aws::Json::ParseError
    raise Aws::Errors::MetadataParserError
  end
end
refresh_offset() click to toggle source

Compute an offset for refresh with jitter

# File lib/aws-sdk-core/instance_profile_credentials.rb, line 306
def refresh_offset
  300 + rand(0..60)
end
resolve_endpoint(options, endpoint_mode) click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 102
def resolve_endpoint(options, endpoint_mode)
  value = options[:endpoint] || options[:ip_address]
  value ||= ENV['AWS_EC2_METADATA_SERVICE_ENDPOINT']
  value ||= Aws.shared_config.ec2_metadata_service_endpoint(
    profile: options[:profile]
  )

  return value if value

  case endpoint_mode.downcase
  when 'ipv4' then 'http://169.254.169.254'
  when 'ipv6' then 'http://[fd00:ec2::254]'
  else
    raise ArgumentError,
          ':endpoint_mode is not valid, expected IPv4 or IPv6, '\
          "got: #{endpoint_mode}"
  end
end
resolve_endpoint_mode(options) click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 93
def resolve_endpoint_mode(options)
  value = options[:endpoint_mode]
  value ||= ENV['AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE']
  value ||= Aws.shared_config.ec2_metadata_service_endpoint_mode(
    profile: options[:profile]
  )
  value || 'IPv4'
end
retry_errors(error_classes, options = {}) { || ... } click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 281
def retry_errors(error_classes, options = {}, &_block)
  max_retries = options[:max_retries]
  retries = 0
  begin
    yield
  rescue *error_classes
    raise unless retries < max_retries

    @backoff.call(retries)
    retries += 1
    retry
  end
end
token_set?() click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 225
def token_set?
  @token && !@token.expired?
end
warn_expired_credentials() click to toggle source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 295
def warn_expired_credentials
  warn("Attempting credential expiration extension due to a credential "\
    "service availability issue. A refresh of these credentials "\
    "will be attempted again in 5 minutes.")
end