aws-sdk-core 3.46.0 → 3.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/VERSION +1 -1
  3. data/lib/aws-sdk-core.rb +7 -0
  4. data/lib/aws-sdk-core/arn.rb +77 -0
  5. data/lib/aws-sdk-core/arn_parser.rb +38 -0
  6. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +102 -0
  7. data/lib/aws-sdk-core/async_client_stubs.rb +80 -0
  8. data/lib/aws-sdk-core/binary.rb +3 -0
  9. data/lib/aws-sdk-core/binary/decode_handler.rb +9 -1
  10. data/lib/aws-sdk-core/binary/encode_handler.rb +32 -0
  11. data/lib/aws-sdk-core/binary/event_builder.rb +122 -0
  12. data/lib/aws-sdk-core/binary/event_parser.rb +48 -18
  13. data/lib/aws-sdk-core/binary/event_stream_decoder.rb +5 -2
  14. data/lib/aws-sdk-core/binary/event_stream_encoder.rb +53 -0
  15. data/lib/aws-sdk-core/client_side_monitoring/publisher.rb +9 -1
  16. data/lib/aws-sdk-core/client_stubs.rb +10 -9
  17. data/lib/aws-sdk-core/credential_provider.rb +0 -31
  18. data/lib/aws-sdk-core/credential_provider_chain.rb +79 -39
  19. data/lib/aws-sdk-core/deprecations.rb +16 -10
  20. data/lib/aws-sdk-core/ecs_credentials.rb +12 -8
  21. data/lib/aws-sdk-core/endpoint_cache.rb +14 -11
  22. data/lib/aws-sdk-core/errors.rb +94 -6
  23. data/lib/aws-sdk-core/event_emitter.rb +42 -0
  24. data/lib/aws-sdk-core/instance_profile_credentials.rb +120 -38
  25. data/lib/aws-sdk-core/json.rb +13 -14
  26. data/lib/aws-sdk-core/json/error_handler.rb +19 -2
  27. data/lib/aws-sdk-core/json/handler.rb +19 -1
  28. data/lib/aws-sdk-core/log/formatter.rb +7 -1
  29. data/lib/aws-sdk-core/log/param_filter.rb +3 -3
  30. data/lib/aws-sdk-core/pageable_response.rb +34 -20
  31. data/lib/aws-sdk-core/param_validator.rb +11 -5
  32. data/lib/aws-sdk-core/plugins/client_metrics_plugin.rb +26 -1
  33. data/lib/aws-sdk-core/plugins/endpoint_discovery.rb +1 -1
  34. data/lib/aws-sdk-core/plugins/event_stream_configuration.rb +14 -0
  35. data/lib/aws-sdk-core/plugins/invocation_id.rb +33 -0
  36. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +8 -1
  37. data/lib/aws-sdk-core/plugins/retries/client_rate_limiter.rb +137 -0
  38. data/lib/aws-sdk-core/plugins/retries/clock_skew.rb +98 -0
  39. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +142 -0
  40. data/lib/aws-sdk-core/plugins/retries/retry_quota.rb +57 -0
  41. data/lib/aws-sdk-core/plugins/retry_errors.rb +290 -106
  42. data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -2
  43. data/lib/aws-sdk-core/plugins/stub_responses.rb +20 -7
  44. data/lib/aws-sdk-core/plugins/transfer_encoding.rb +51 -0
  45. data/lib/aws-sdk-core/plugins/user_agent.rb +4 -8
  46. data/lib/aws-sdk-core/process_credentials.rb +9 -3
  47. data/lib/aws-sdk-core/shared_config.rb +95 -125
  48. data/lib/aws-sdk-core/structure.rb +1 -2
  49. data/lib/aws-sdk-core/stubbing/protocols/rest.rb +19 -0
  50. data/lib/aws-sdk-core/stubbing/stub_data.rb +13 -4
  51. data/lib/aws-sdk-core/util.rb +4 -0
  52. data/lib/aws-sdk-core/waiters/waiter.rb +2 -2
  53. data/lib/aws-sdk-core/xml/error_handler.rb +26 -3
  54. data/lib/aws-sdk-sts.rb +7 -4
  55. data/lib/aws-sdk-sts/client.rb +1109 -459
  56. data/lib/aws-sdk-sts/client_api.rb +67 -0
  57. data/lib/aws-sdk-sts/customizations.rb +2 -0
  58. data/lib/aws-sdk-sts/errors.rb +150 -0
  59. data/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb +32 -0
  60. data/lib/aws-sdk-sts/presigner.rb +67 -0
  61. data/lib/aws-sdk-sts/resource.rb +1 -0
  62. data/lib/aws-sdk-sts/types.rb +736 -176
  63. data/lib/seahorse.rb +9 -0
  64. data/lib/seahorse/client/async_base.rb +50 -0
  65. data/lib/seahorse/client/async_response.rb +62 -0
  66. data/lib/seahorse/client/base.rb +4 -2
  67. data/lib/seahorse/client/configuration.rb +4 -2
  68. data/lib/seahorse/client/events.rb +1 -1
  69. data/lib/seahorse/client/h2/connection.rb +246 -0
  70. data/lib/seahorse/client/h2/handler.rb +151 -0
  71. data/lib/seahorse/client/handler_list_entry.rb +2 -2
  72. data/lib/seahorse/client/http/async_response.rb +42 -0
  73. data/lib/seahorse/client/http/response.rb +13 -8
  74. data/lib/seahorse/client/logging/formatter.rb +4 -2
  75. data/lib/seahorse/client/net_http/connection_pool.rb +19 -20
  76. data/lib/seahorse/client/net_http/handler.rb +7 -1
  77. data/lib/seahorse/client/net_http/patches.rb +7 -1
  78. data/lib/seahorse/client/networking_error.rb +28 -0
  79. data/lib/seahorse/client/plugin.rb +5 -4
  80. data/lib/seahorse/client/plugins/content_length.rb +5 -2
  81. data/lib/seahorse/client/plugins/h2.rb +64 -0
  82. data/lib/seahorse/client/response.rb +3 -5
  83. data/lib/seahorse/model/api.rb +4 -0
  84. data/lib/seahorse/model/operation.rb +4 -0
  85. data/lib/seahorse/model/shapes.rb +2 -2
  86. metadata +43 -10
@@ -35,33 +35,39 @@ module Aws
35
35
  # @api private
36
36
  module Deprecations
37
37
 
38
- # @param [Symbol] method_name The name of the deprecated method.
38
+ # @param [Symbol] method The name of the deprecated method.
39
39
  #
40
40
  # @option options [String] :message The warning message to issue
41
41
  # when the deprecated method is called.
42
42
  #
43
- # @option options [Symbol] :use The name of an use
44
- # method that should be used.
43
+ # @option options [String] :use The name of a method that should be used.
45
44
  #
46
- def deprecated(method_name, options = {})
45
+ # @option options [String] :version The version that will remove the
46
+ # deprecated method.
47
+ #
48
+ def deprecated(method, options = {})
47
49
 
48
50
  deprecation_msg = options[:message] || begin
49
- msg = "DEPRECATION WARNING: called deprecated method `#{method_name}' "
50
- msg << "of an #{self}"
51
- msg << ", use #{options[:use]} instead" if options[:use]
51
+ msg = "#################### DEPRECATION WARNING ####################\n"
52
+ msg << "Called deprecated method `#{method}` of #{self}."
53
+ msg << " Use `#{options[:use]}` instead.\n" if options[:use]
54
+ if options[:version]
55
+ msg << "Method `#{method}` will be removed in #{options[:version]}."
56
+ end
57
+ msg << "\n#############################################################"
52
58
  msg
53
59
  end
54
60
 
55
- alias_method(:"deprecated_#{method_name}", method_name)
61
+ alias_method(:"deprecated_#{method}", method)
56
62
 
57
63
  warned = false # we only want to issue this warning once
58
64
 
59
- define_method(method_name) do |*args,&block|
65
+ define_method(method) do |*args, &block|
60
66
  unless warned
61
67
  warned = true
62
68
  warn(deprecation_msg + "\n" + caller.join("\n"))
63
69
  end
64
- send("deprecated_#{method_name}", *args, &block)
70
+ send("deprecated_#{method}", *args, &block)
65
71
  end
66
72
  end
67
73
 
@@ -78,14 +78,18 @@ module Aws
78
78
  # Retry loading credentials up to 3 times is the instance metadata
79
79
  # service is responding but is returning invalid JSON documents
80
80
  # in response to the GET profile credentials call.
81
- retry_errors([JSON::ParserError, StandardError], max_retries: 3) do
82
- c = JSON.parse(get_credentials.to_s)
83
- @credentials = Credentials.new(
84
- c['AccessKeyId'],
85
- c['SecretAccessKey'],
86
- c['Token']
87
- )
88
- @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
81
+ begin
82
+ retry_errors([JSON::ParserError, StandardError], max_retries: 3) do
83
+ c = JSON.parse(get_credentials.to_s)
84
+ @credentials = Credentials.new(
85
+ c['AccessKeyId'],
86
+ c['SecretAccessKey'],
87
+ c['Token']
88
+ )
89
+ @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
90
+ end
91
+ rescue JSON::ParserError
92
+ raise Aws::Errors::MetadataParserError.new
89
93
  end
90
94
  end
91
95
 
@@ -47,8 +47,8 @@ module Aws
47
47
  @mutex.synchronize do
48
48
  # delete the least recent used endpoint when cache is full
49
49
  unless @entries.size < @max_entries
50
- old_key, _ = @entries.shift
51
- self.delete_polling_thread(old_key)
50
+ old_key, = @entries.shift
51
+ delete_polling_thread(old_key)
52
52
  end
53
53
  # delete old value if exists
54
54
  @entries.delete(key)
@@ -60,10 +60,12 @@ module Aws
60
60
  # @param [String] key
61
61
  # @return [Boolean]
62
62
  def key?(key)
63
- if @entries.key?(key) && (@entries[key].nil? || @entries[key].expired?)
64
- self.delete(key)
63
+ @mutex.synchronize do
64
+ if @entries.key?(key) && (@entries[key].nil? || @entries[key].expired?)
65
+ @entries.delete(key)
66
+ end
67
+ @entries.key?(key)
65
68
  end
66
- @entries.key?(key)
67
69
  end
68
70
 
69
71
  # checking whether an polling thread exist for the key
@@ -84,7 +86,7 @@ module Aws
84
86
  # kill the old polling thread and remove it from pool
85
87
  # @param [String] key
86
88
  def delete_polling_thread(key)
87
- Thread.kill(@pool[key]) if self.threads_key?(key)
89
+ Thread.kill(@pool[key]) if threads_key?(key)
88
90
  @pool.delete(key)
89
91
  end
90
92
 
@@ -109,7 +111,7 @@ module Aws
109
111
  if _endpoint_operation_identifier(ctx)
110
112
  parts << ctx.operation_name
111
113
  ctx.operation.input.shape.members.inject(parts) do |p, (name, ref)|
112
- p << ctx.params[name] if ref["endpointdiscoveryid"]
114
+ p << ctx.params[name] if ref['endpointdiscoveryid']
113
115
  p
114
116
  end
115
117
  end
@@ -141,7 +143,7 @@ module Aws
141
143
  # build identifier params when available
142
144
  params[:operation] = ctx.operation.name
143
145
  ctx.operation.input.shape.members.inject(params) do |p, (name, ref)|
144
- if ref["endpointdiscoveryid"]
146
+ if ref['endpointdiscoveryid']
145
147
  p[:identifiers] ||= {}
146
148
  p[:identifiers][ref.location_name] = ctx.params[name]
147
149
  end
@@ -153,19 +155,20 @@ module Aws
153
155
  endpoint_operation_name = ctx.config.api.endpoint_operation
154
156
  ctx.client.send(endpoint_operation_name, params)
155
157
  rescue Aws::Errors::ServiceError
156
- nil
158
+ nil
157
159
  end
158
160
  end
159
161
 
160
162
  def _endpoint_operation_identifier(ctx)
161
163
  return @require_identifier unless @require_identifier.nil?
164
+
162
165
  operation_name = ctx.config.api.endpoint_operation
163
166
  operation = ctx.config.api.operation(operation_name)
164
167
  @require_identifier = operation.input.shape.members.any?
165
168
  end
166
169
 
167
170
  class Endpoint
168
-
171
+
169
172
  # default endpoint cache time, 1 minute
170
173
  CACHE_PERIOD = 1
171
174
 
@@ -175,7 +178,7 @@ module Aws
175
178
  @created_time = Time.now
176
179
  end
177
180
 
178
- # [String] valid URI address (with path)
181
+ # [String] valid URI address (with path)
179
182
  attr_reader :address
180
183
 
181
184
  def expired?
@@ -1,5 +1,3 @@
1
- require 'thread'
2
-
3
1
  module Aws
4
2
  module Errors
5
3
 
@@ -13,9 +11,12 @@ module Aws
13
11
 
14
12
  # @param [Seahorse::Client::RequestContext] context
15
13
  # @param [String] message
16
- def initialize(context, message)
14
+ # @param [Aws::Structure] data
15
+ def initialize(context, message, data = Aws::EmptyStructure.new)
17
16
  @code = self.class.code
17
+ @message = message if message && !message.empty?
18
18
  @context = context
19
+ @data = data
19
20
  super(message)
20
21
  end
21
22
 
@@ -26,12 +27,43 @@ module Aws
26
27
  # that triggered the remote service to return this error.
27
28
  attr_reader :context
28
29
 
30
+ # @return [Aws::Structure]
31
+ attr_reader :data
32
+
29
33
  class << self
30
34
 
31
35
  # @return [String]
32
36
  attr_accessor :code
33
37
 
34
38
  end
39
+
40
+ # @api private undocumented
41
+ def retryable?
42
+ false
43
+ end
44
+
45
+ # @api private undocumented
46
+ def throttling?
47
+ false
48
+ end
49
+ end
50
+
51
+ # Raised when InstanceProfileCredentialsProvider or
52
+ # EcsCredentialsProvider fails to parse the metadata response after retries
53
+ class MetadataParserError < RuntimeError
54
+ def initialize(*args)
55
+ msg = "Failed to parse metadata service response."
56
+ super(msg)
57
+ end
58
+ end
59
+
60
+ # Raised when a `streaming` operation has `requiresLength` trait
61
+ # enabled but request payload size/length cannot be calculated
62
+ class MissingContentLength < RuntimeError
63
+ def initialize(*args)
64
+ msg = 'Required `Content-Length` value missing for the request.'
65
+ super(msg)
66
+ end
35
67
  end
36
68
 
37
69
  # Rasied when endpoint discovery failed for operations
@@ -58,10 +90,18 @@ module Aws
58
90
 
59
91
  end
60
92
 
93
+ # Raised when attempting to #signal an event before
94
+ # making an async request
95
+ class SignalEventError < RuntimeError; end
96
+
61
97
  # Raised when EventStream Parser failed to parse
62
98
  # a raw event message
63
99
  class EventStreamParserError < RuntimeError; end
64
100
 
101
+ # Raise when EventStream Builder failed to build
102
+ # an event message with parameters provided
103
+ class EventStreamBuilderError < RuntimeError; end
104
+
65
105
  # Error event in an event stream which has event_type :error
66
106
  # error code and error message can be retrieved when available.
67
107
  #
@@ -93,6 +133,29 @@ module Aws
93
133
 
94
134
  end
95
135
 
136
+ # Raised when ARN string input doesn't follow the standard:
137
+ # https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-arns
138
+ class InvalidARNError < RuntimeError; end
139
+
140
+ # Raised when the region from the ARN string is different from the :region
141
+ # configured on the service client.
142
+ class InvalidARNRegionError < RuntimeError
143
+ def initialize(*args)
144
+ msg = 'ARN region is different from the configured client region.'
145
+ super(msg)
146
+ end
147
+ end
148
+
149
+ # Raised when the partition of the ARN region is different than the
150
+ # partition of the :region configured on the service client.
151
+ class InvalidARNPartitionError < RuntimeError
152
+ def initialize(*args)
153
+ msg = 'ARN region partition is different from the configured '\
154
+ 'client region partition.'
155
+ super(msg)
156
+ end
157
+ end
158
+
96
159
  # Various plugins perform client-side checksums of responses.
97
160
  # This error indicates a checksum failed.
98
161
  class ChecksumError < RuntimeError; end
@@ -126,6 +189,18 @@ module Aws
126
189
  end
127
190
  end
128
191
 
192
+ # Raised when :web_identity_token_file parameter is not
193
+ # provided or the file doesn't exist when initializing
194
+ # AssumeRoleWebIdentityCredentials credential provider
195
+ class MissingWebIdentityTokenFile < RuntimeError
196
+ def initialize(*args)
197
+ msg = 'Missing :web_identity_token_file parameter or'\
198
+ ' invalid file path provided for'\
199
+ ' Aws::AssumeRoleWebIdentityCredentials provider'
200
+ super(msg)
201
+ end
202
+ end
203
+
129
204
  # Raised when a credentials provider process returns a JSON
130
205
  # payload with either invalid version number or malformed contents
131
206
  class InvalidProcessCredentialsPayload < RuntimeError; end
@@ -157,8 +232,8 @@ This is typically the result of an invalid `:region` option or a
157
232
  poorly formatted `:endpoint` option.
158
233
 
159
234
  * Avoid configuring the `:endpoint` option directly. Endpoints are constructed
160
- from the `:region`. The `:endpoint` option is reserved for connecting to
161
- non-standard test endpoints.
235
+ from the `:region`. The `:endpoint` option is reserved for certain services
236
+ or for connecting to non-standard test endpoints.
162
237
 
163
238
  * Not every service is available in every region.
164
239
 
@@ -190,6 +265,15 @@ Known AWS regions include (not specific to this service):
190
265
 
191
266
  end
192
267
 
268
+ # Raised when attempting to retry a request
269
+ # and no capacity is available to retry (See adaptive retry_mode)
270
+ class RetryCapacityNotAvailableError < RuntimeError
271
+ def initialize(*args)
272
+ msg = 'Insufficient client side capacity available to retry request.'
273
+ super(msg)
274
+ end
275
+ end
276
+
193
277
  # This module is mixed into another module, providing dynamic
194
278
  # error classes. Error classes all inherit from {ServiceError}.
195
279
  #
@@ -223,7 +307,11 @@ Known AWS regions include (not specific to this service):
223
307
  def error_class(error_code)
224
308
  constant = error_class_constant(error_code)
225
309
  if error_const_set?(constant)
226
- const_get(constant)
310
+ # modeled error class exist
311
+ # set code attribute
312
+ err_class = const_get(constant)
313
+ err_class.code = constant.to_s
314
+ err_class
227
315
  else
228
316
  set_error_constant(constant)
229
317
  end
@@ -3,8 +3,19 @@ module Aws
3
3
 
4
4
  def initialize
5
5
  @listeners = {}
6
+ @validate_event = true
7
+ @status = :sleep
8
+ @signal_queue = Queue.new
6
9
  end
7
10
 
11
+ attr_accessor :stream
12
+
13
+ attr_accessor :encoder
14
+
15
+ attr_accessor :validate_event
16
+
17
+ attr_accessor :signal_queue
18
+
8
19
  def on(type, callback)
9
20
  (@listeners[type] ||= []) << callback
10
21
  end
@@ -16,5 +27,36 @@ module Aws
16
27
  end
17
28
  end
18
29
 
30
+ def emit(type, params)
31
+ unless @stream
32
+ raise Aws::Errors::SignalEventError.new(
33
+ "Singaling events before making async request"\
34
+ " is not allowed."
35
+ )
36
+ end
37
+ if @validate_event && type != :end_stream
38
+ Aws::ParamValidator.validate!(
39
+ @encoder.rules.shape.member(type), params)
40
+ end
41
+ _ready_for_events?
42
+ @stream.data(
43
+ @encoder.encode(type, params),
44
+ end_stream: type == :end_stream
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def _ready_for_events?
51
+ return true if @status == :ready
52
+
53
+ # blocked until once initial 200 response is received
54
+ # signal will be available in @signal_queue
55
+ # and this check will no longer be blocked
56
+ @signal_queue.pop
57
+ @status = :ready
58
+ true
59
+ end
60
+
19
61
  end
20
62
  end
@@ -11,6 +11,12 @@ module Aws
11
11
  # @api private
12
12
  class Non200Response < RuntimeError; end
13
13
 
14
+ # @api private
15
+ class TokenRetrivalError < RuntimeError; end
16
+
17
+ # @api private
18
+ class TokenExpiredError < RuntimeError; end
19
+
14
20
  # These are the errors we trap when attempting to talk to the
15
21
  # instance metadata service. Any of these imply the service
16
22
  # is not present, no responding or some other non-recoverable
@@ -23,16 +29,24 @@ module Aws
23
29
  Errno::ENETUNREACH,
24
30
  SocketError,
25
31
  Timeout::Error,
26
- Non200Response,
27
- ]
32
+ Non200Response
33
+ ].freeze
34
+
35
+ # Path base for GET request for profile and credentials
36
+ # @api private
37
+ METADATA_PATH_BASE = '/latest/meta-data/iam/security-credentials/'.freeze
38
+
39
+ # Path for PUT request for token
40
+ # @api private
41
+ METADATA_TOKEN_PATH = '/latest/api/token'.freeze
28
42
 
29
43
  # @param [Hash] options
30
- # @option options [Integer] :retries (5) Number of times to retry
44
+ # @option options [Integer] :retries (1) Number of times to retry
31
45
  # when retrieving credentials.
32
46
  # @option options [String] :ip_address ('169.254.169.254')
33
47
  # @option options [Integer] :port (80)
34
- # @option options [Float] :http_open_timeout (5)
35
- # @option options [Float] :http_read_timeout (5)
48
+ # @option options [Float] :http_open_timeout (1)
49
+ # @option options [Float] :http_read_timeout (1)
36
50
  # @option options [Numeric, Proc] :delay By default, failures are retried
37
51
  # with exponential back-off, i.e. `sleep(1.2 ** num_failures)`. You can
38
52
  # pass a number of seconds to sleep between failed attempts, or
@@ -40,19 +54,25 @@ module Aws
40
54
  # @option options [IO] :http_debug_output (nil) HTTP wire
41
55
  # traces are sent to this object. You can specify something
42
56
  # like $stdout.
43
- def initialize options = {}
44
- @retries = options[:retries] || 5
57
+ # @option options [Integer] :token_ttl Time-to-Live in seconds for EC2
58
+ # Metadata Token used for fetching Metadata Profile Credentials, defaults
59
+ # to 21600 seconds
60
+ def initialize(options = {})
61
+ @retries = options[:retries] || 1
45
62
  @ip_address = options[:ip_address] || '169.254.169.254'
46
63
  @port = options[:port] || 80
47
- @http_open_timeout = options[:http_open_timeout] || 5
48
- @http_read_timeout = options[:http_read_timeout] || 5
64
+ @http_open_timeout = options[:http_open_timeout] || 1
65
+ @http_read_timeout = options[:http_read_timeout] || 1
49
66
  @http_debug_output = options[:http_debug_output]
50
67
  @backoff = backoff(options[:backoff])
68
+ @token_ttl = options[:token_ttl] || 21_600
69
+ @token = nil
51
70
  super
52
71
  end
53
72
 
54
- # @return [Integer] The number of times to retry failed attempts to
55
- # fetch credentials from the instance metadata service. Defaults to 0.
73
+ # @return [Integer] Number of times to retry when retrieving credentials
74
+ # from the instance metadata service. Defaults to 0 when resolving from
75
+ # the default credential chain ({Aws::CredentialProviderChain}).
56
76
  attr_reader :retries
57
77
 
58
78
  private
@@ -60,8 +80,8 @@ module Aws
60
80
  def backoff(backoff)
61
81
  case backoff
62
82
  when Proc then backoff
63
- when Numeric then lambda { |_| sleep(backoff) }
64
- else lambda { |num_failures| Kernel.sleep(1.2 ** num_failures) }
83
+ when Numeric then ->(_) { sleep(backoff) }
84
+ else ->(num_failures) { Kernel.sleep(1.2**num_failures) }
65
85
  end
66
86
  end
67
87
 
@@ -69,14 +89,18 @@ module Aws
69
89
  # Retry loading credentials up to 3 times is the instance metadata
70
90
  # service is responding but is returning invalid JSON documents
71
91
  # in response to the GET profile credentials call.
72
- retry_errors([JSON::ParserError, StandardError], max_retries: 3) do
73
- c = JSON.parse(get_credentials.to_s)
74
- @credentials = Credentials.new(
75
- c['AccessKeyId'],
76
- c['SecretAccessKey'],
77
- c['Token']
78
- )
79
- @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
92
+ begin
93
+ retry_errors([JSON::ParserError, StandardError], max_retries: 3) do
94
+ c = JSON.parse(get_credentials.to_s)
95
+ @credentials = Credentials.new(
96
+ c['AccessKeyId'],
97
+ c['SecretAccessKey'],
98
+ c['Token']
99
+ )
100
+ @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
101
+ end
102
+ rescue JSON::ParserError
103
+ raise Aws::Errors::MetadataParserError
80
104
  end
81
105
  end
82
106
 
@@ -89,9 +113,27 @@ module Aws
89
113
  begin
90
114
  retry_errors(NETWORK_ERRORS, max_retries: @retries) do
91
115
  open_connection do |conn|
92
- path = '/latest/meta-data/iam/security-credentials/'
93
- profile_name = http_get(conn, path).lines.first.strip
94
- http_get(conn, path + profile_name)
116
+ # attempt to fetch token to start secure flow first
117
+ # and rescue to failover
118
+ begin
119
+ retry_errors(NETWORK_ERRORS, max_retries: @retries) do
120
+ unless token_set?
121
+ token_value, ttl = http_put(
122
+ conn, METADATA_TOKEN_PATH, @token_ttl
123
+ )
124
+ @token = Token.new(token_value, ttl) if token_value && ttl
125
+ end
126
+ end
127
+ rescue *NETWORK_ERRORS
128
+ # token attempt failed, reset token
129
+ # fallback to non-token mode
130
+ @token = nil
131
+ end
132
+
133
+ token = @token.value if token_set?
134
+ metadata = http_get(conn, METADATA_PATH_BASE, token)
135
+ profile_name = metadata.lines.first.strip
136
+ http_get(conn, METADATA_PATH_BASE + profile_name, token)
95
137
  end
96
138
  end
97
139
  rescue
@@ -100,9 +142,12 @@ module Aws
100
142
  end
101
143
  end
102
144
 
145
+ def token_set?
146
+ @token && !@token.expired?
147
+ end
148
+
103
149
  def _metadata_disabled?
104
- flag = ENV["AWS_EC2_METADATA_DISABLED"]
105
- !flag.nil? && flag.downcase == "true"
150
+ ENV.fetch('AWS_EC2_METADATA_DISABLED', 'false').downcase == 'true'
106
151
  end
107
152
 
108
153
  def open_connection
@@ -114,30 +159,67 @@ module Aws
114
159
  yield(http).tap { http.finish }
115
160
  end
116
161
 
117
- def http_get(connection, path)
118
- response = connection.request(Net::HTTP::Get.new(path, {"User-Agent" => "aws-sdk-ruby3/#{CORE_GEM_VERSION}"}))
119
- if response.code.to_i == 200
120
- response.body
162
+ # GET request fetch profile and credentials
163
+ def http_get(connection, path, token = nil)
164
+ headers = { 'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}" }
165
+ headers['x-aws-ec2-metadata-token'] = token if token
166
+ response = connection.request(Net::HTTP::Get.new(path, headers))
167
+ raise Non200Response unless response.code.to_i == 200
168
+
169
+ response.body
170
+ end
171
+
172
+ # PUT request fetch token with ttl
173
+ def http_put(connection, path, ttl)
174
+ headers = {
175
+ 'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}",
176
+ 'x-aws-ec2-metadata-token-ttl-seconds' => ttl.to_s
177
+ }
178
+ response = connection.request(Net::HTTP::Put.new(path, headers))
179
+ case response.code.to_i
180
+ when 200
181
+ [
182
+ response.body,
183
+ response.header['x-aws-ec2-metadata-token-ttl-seconds'].to_i
184
+ ]
185
+ when 400
186
+ raise TokenRetrivalError
187
+ when 401
188
+ raise TokenExpiredError
121
189
  else
122
190
  raise Non200Response
123
191
  end
124
192
  end
125
193
 
126
- def retry_errors(error_classes, options = {}, &block)
194
+ def retry_errors(error_classes, options = {}, &_block)
127
195
  max_retries = options[:max_retries]
128
196
  retries = 0
129
197
  begin
130
198
  yield
131
199
  rescue *error_classes
132
- if retries < max_retries
133
- @backoff.call(retries)
134
- retries += 1
135
- retry
136
- else
137
- raise
138
- end
200
+ raise unless retries < max_retries
201
+
202
+ @backoff.call(retries)
203
+ retries += 1
204
+ retry
139
205
  end
140
206
  end
141
207
 
208
+ # @api private
209
+ # Token used to fetch IMDS profile and credentials
210
+ class Token
211
+ def initialize(value, ttl)
212
+ @ttl = ttl
213
+ @value = value
214
+ @created_time = Time.now
215
+ end
216
+
217
+ # [String] token value
218
+ attr_reader :value
219
+
220
+ def expired?
221
+ Time.now - @created_time > @ttl
222
+ end
223
+ end
142
224
  end
143
225
  end