class Aws::InstanceProfileCredentials
An auto-refreshing credential provider that loads credentials from EC2 instances.
instance_credentials = Aws::InstanceProfileCredentials.new ec2 = Aws::EC2::Client.new(credentials: instance_credentials)
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
@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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 77 def initialize(options = {}) @retries = options[:retries] || 1 endpoint_mode = resolve_endpoint_mode(options) @endpoint = resolve_endpoint(options, endpoint_mode) @port = options[:port] || 80 @disable_imds_v1 = resolve_disable_v1(options) # Flag for if v2 flow fails, skip future attempts @imds_v1_fallback = false @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 @metrics = ['CREDENTIALS_IMDS'] super end
@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 [Boolean] :disable_imds_v1 (false) Disable the use of the
legacy EC2 Metadata Service v1.
@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.
Aws::RefreshingCredentials::new
Private Instance Methods
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 244 def _get_credentials(conn, token) 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 @imds_v1_fallback = false raise Non200Response end
token is optional - if nil, uses v1 (insecure) flow
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 260 def _metadata_disabled? ENV.fetch('AWS_EC2_METADATA_DISABLED', 'false').downcase == 'true' end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 142 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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 330 def empty_credentials?(creds) !creds || !creds.access_key_id || creds.access_key_id.empty? end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 226 def fetch_token(conn) 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 @imds_v1_fallback = true end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 198 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 fetch_token(conn) unless @imds_v1_fallback token = @token.value if token_set? # disable insecure flow if we couldn't get token # and imds v1 is disabled raise TokenRetrivalError if token.nil? && @disable_imds_v1 _get_credentials(conn, token) end end rescue => e warn("Error retrieving instance profile credentials: #{e}") '{}' end end end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 275 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
GET request fetch profile and credentials
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 291 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 else raise Non200Response end end
PUT request fetch token with ttl
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 264 def open_connection uri = URI.parse(@endpoint) http = Net::HTTP.new(uri.hostname || @endpoint, uri.port || @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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 150 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], 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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 335 def refresh_offset 300 + rand(0..60) end
Compute an offset for refresh with jitter
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 132 def resolve_disable_v1(options) value = options[:disable_imds_v1] value ||= ENV['AWS_EC2_METADATA_V1_DISABLED'] value ||= Aws.shared_config.ec2_metadata_v1_disabled( profile: options[:profile] ) value = value.to_s.downcase if value Aws::Util.str_2_bool(value) || false end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 113 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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 104 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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 310 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
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 256 def token_set? @token && !@token.expired? end
Source
# File lib/aws-sdk-core/instance_profile_credentials.rb, line 324 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