aws-sdk-core 3.46.0 → 3.94.0

Sign up to get free protection for your applications and to get access to all the features.
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