class Aws::ECSCredentials

An auto-refreshing credential provider that loads credentials from instances running in containers.

ecs_credentials = Aws::ECSCredentials.new(retries: 3)
ec2 = Aws::EC2::Client.new(credentials: ecs_credentials)

Constants

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] The number of times to retry failed attempts to

fetch credentials from the instance metadata service. Defaults to 0.

Public Class Methods

new(options = {}) click to toggle source

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

when retrieving credentials.

@option options [String] :ip_address (‘169.254.170.2’) This value is

ignored if `endpoint` is set and `credential_path` is not set.

@option options [Integer] :port (80) This value is ignored if ‘endpoint`

is set and `credential_path` is not set.

@option options [String] :credential_path By default, the value of the

AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable.

@option options [String] :endpoint The container credential endpoint.

By default, this is the value of the AWS_CONTAINER_CREDENTIALS_FULL_URI
environment variable. This value is ignored if `credential_path` or
ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] is set.

@option options [Float] :http_open_timeout (5) @option options [Float] :http_read_timeout (5) @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 [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/ecs_credentials.rb, line 67
def initialize(options = {})
  credential_path = options[:credential_path] ||
                    ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI']
  endpoint = options[:endpoint] ||
             ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI']
  initialize_uri(options, credential_path, endpoint)

  @retries = options[:retries] || 5
  @http_open_timeout = options[:http_open_timeout] || 5
  @http_read_timeout = options[:http_read_timeout] || 5
  @http_debug_output = options[:http_debug_output]
  @backoff = backoff(options[:backoff])
  @async_refresh = false
  super
end

Private Instance Methods

backoff(backoff) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 173
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
ecs_or_eks_ip?(ip_address) click to toggle source

Verify that the IP address is a link-local address from ECS or EKS. ECS container host (IPv4 ‘169.254.170.2`) EKS container host (IPv4 `169.254.170.23`, IPv6 `fd00:ec2::23`)

# File lib/aws-sdk-core/ecs_credentials.rb, line 162
def ecs_or_eks_ip?(ip_address)
  case ip_address.family
  when Socket::AF_INET
    [0xa9feaa02, 0xa9feaa17].include?(ip_address)
  when Socket::AF_INET6
    ip_address == 0xfd00_0ec2_0000_0000_0000_0000_0000_0023
  else
    false
  end
end
fetch_authorization_token() click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 215
def fetch_authorization_token
  if (path = ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'])
    fetch_authorization_token_file(path)
  elsif (token = ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN'])
    token
  end
end
fetch_authorization_token_file(path) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 223
def fetch_authorization_token_file(path)
  File.read(path).strip
rescue Errno::ENOENT
  raise TokenFileReadError,
        'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE is set '\
        "but the file doesn't exist: #{path}"
end
get_credentials() click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 199
def get_credentials
  # Retry loading credentials a configurable number of times if
  # the instance metadata service is not responding.

  retry_errors(NETWORK_ERRORS, max_retries: @retries) do
    open_connection do |conn|
      http_get(conn, @credential_path)
    end
  end
rescue TokenFileReadError, InvalidTokenError
  raise
rescue StandardError => e
  warn("Error retrieving ECS Credentials: #{e.message}")
  '{}'
end
http_get(connection, path) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 249
def http_get(connection, path)
  request = Net::HTTP::Get.new(path)
  set_authorization_token(request)
  response = connection.request(request)
  raise Non200Response unless response.code.to_i == 200

  response.body
end
initialize_full_uri(endpoint) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 109
def initialize_full_uri(endpoint)
  uri = URI.parse(endpoint)
  validate_full_uri_scheme!(uri)
  validate_full_uri!(uri)
  @host = uri.hostname
  @port = uri.port
  @scheme = uri.scheme
  @credential_path = uri.request_uri
end
initialize_relative_uri(options, path) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 102
def initialize_relative_uri(options, path)
  @host = options[:ip_address] || '169.254.170.2'
  @port = options[:port] || 80
  @scheme = 'http'
  @credential_path = path
end
initialize_uri(options, credential_path, endpoint) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 89
def initialize_uri(options, credential_path, endpoint)
  if credential_path
    initialize_relative_uri(options, credential_path)
  # Use FULL_URI/endpoint only if RELATIVE_URI/path is not set
  elsif endpoint
    initialize_full_uri(endpoint)
  else
    raise ArgumentError,
          'Cannot instantiate an ECS Credential Provider '\
          'without a credential path or endpoint.'
  end
end
ip_loopback?(ip_address) click to toggle source

loopback? method is available in Ruby 2.5+ Replicate the logic here. loopback (IPv4 127.0.0.0/8, IPv6 ::1/128)

# File lib/aws-sdk-core/ecs_credentials.rb, line 148
def ip_loopback?(ip_address)
  case ip_address.family
  when Socket::AF_INET
    ip_address & 0xff000000 == 0x7f000000
  when Socket::AF_INET6
    ip_address == 1
  else
    false
  end
end
open_connection() { |http| ... } click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 239
def open_connection
  http = Net::HTTP.new(@host, @port, nil)
  http.open_timeout = @http_open_timeout
  http.read_timeout = @http_read_timeout
  http.set_debug_output(@http_debug_output) if @http_debug_output
  http.use_ssl = @scheme == 'https'
  http.start
  yield(http).tap { http.finish }
end
refresh() click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 181
def refresh
  # 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.

  retry_errors([Aws::Json::ParseError, StandardError], max_retries: 3) do
    c = Aws::Json.load(get_credentials.to_s)
    @credentials = Credentials.new(
      c['AccessKeyId'],
      c['SecretAccessKey'],
      c['Token']
    )
    @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
  end
rescue Aws::Json::ParseError
  raise Aws::Errors::MetadataParserError
end
retry_errors(error_classes, options = {}) { || ... } click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 265
def retry_errors(error_classes, options = {})
  max_retries = options[:max_retries]
  retries = 0
  begin
    yield
  rescue TokenFileReadError, InvalidTokenError
    raise
  rescue *error_classes => _e
    raise unless retries < max_retries

    @backoff.call(retries)
    retries += 1
    retry
  end
end
set_authorization_token(request) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 258
def set_authorization_token(request)
  if (authorization_token = fetch_authorization_token)
    validate_authorization_token!(authorization_token)
    request['Authorization'] = authorization_token
  end
end
valid_ip_address?(ip_address) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 141
def valid_ip_address?(ip_address)
  ip_loopback?(ip_address) || ecs_or_eks_ip?(ip_address)
end
validate_authorization_token!(token) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 231
def validate_authorization_token!(token)
  return unless token.include?("\r\n")

  raise InvalidTokenError,
        'Invalid Authorization token: token contains '\
        'a newline and carriage return character.'
end
validate_full_uri!(full_uri) click to toggle source

Validate that the full URI is using a loopback address if scheme is http.

# File lib/aws-sdk-core/ecs_credentials.rb, line 126
def validate_full_uri!(full_uri)
  return unless full_uri.scheme == 'http'

  begin
    return if valid_ip_address?(IPAddr.new(full_uri.host))
  rescue IPAddr::InvalidAddressError
    addresses = Resolv.getaddresses(full_uri.host)
    return if addresses.all? { |addr| valid_ip_address?(IPAddr.new(addr)) }
  end

  raise ArgumentError,
        'AWS_CONTAINER_CREDENTIALS_FULL_URI must use a local loopback '\
        'or an ECS or EKS link-local address when using the http scheme.'
end
validate_full_uri_scheme!(full_uri) click to toggle source
# File lib/aws-sdk-core/ecs_credentials.rb, line 119
def validate_full_uri_scheme!(full_uri)
  return if full_uri.is_a?(URI::HTTP) || full_uri.is_a?(URI::HTTPS)

  raise ArgumentError, "'#{full_uri}' must be a valid HTTP or HTTPS URI"
end