aws-sdk-core 3.224.1 → 3.240.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-defaults/default_configuration.rb +2 -1
  5. data/lib/aws-sdk-core/assume_role_credentials.rb +8 -8
  6. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +2 -2
  7. data/lib/aws-sdk-core/client_stubs.rb +6 -0
  8. data/lib/aws-sdk-core/credential_provider_chain.rb +72 -23
  9. data/lib/aws-sdk-core/ecs_credentials.rb +13 -13
  10. data/lib/aws-sdk-core/endpoints/matchers.rb +2 -1
  11. data/lib/aws-sdk-core/endpoints.rb +37 -13
  12. data/lib/aws-sdk-core/error_handler.rb +5 -0
  13. data/lib/aws-sdk-core/errors.rb +3 -0
  14. data/lib/aws-sdk-core/event_emitter.rb +1 -1
  15. data/lib/aws-sdk-core/instance_profile_credentials.rb +146 -157
  16. data/lib/aws-sdk-core/json/error_handler.rb +14 -4
  17. data/lib/aws-sdk-core/login_credentials.rb +229 -0
  18. data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +28 -14
  19. data/lib/aws-sdk-core/plugins/credentials_configuration.rb +75 -59
  20. data/lib/aws-sdk-core/plugins/sign.rb +23 -28
  21. data/lib/aws-sdk-core/plugins/stub_responses.rb +6 -0
  22. data/lib/aws-sdk-core/plugins/user_agent.rb +4 -1
  23. data/lib/aws-sdk-core/refreshing_credentials.rb +8 -11
  24. data/lib/aws-sdk-core/rpc_v2/error_handler.rb +26 -16
  25. data/lib/aws-sdk-core/rpc_v2/parser.rb +8 -0
  26. data/lib/aws-sdk-core/shared_config.rb +30 -0
  27. data/lib/aws-sdk-core/sso_credentials.rb +1 -1
  28. data/lib/aws-sdk-core/static_token_provider.rb +1 -2
  29. data/lib/aws-sdk-core/token.rb +3 -3
  30. data/lib/aws-sdk-core/token_provider.rb +4 -0
  31. data/lib/aws-sdk-core/token_provider_chain.rb +2 -6
  32. data/lib/aws-sdk-core/util.rb +2 -1
  33. data/lib/aws-sdk-core/xml/error_handler.rb +3 -1
  34. data/lib/aws-sdk-core.rb +4 -0
  35. data/lib/aws-sdk-signin/client.rb +604 -0
  36. data/lib/aws-sdk-signin/client_api.rb +119 -0
  37. data/lib/aws-sdk-signin/customizations.rb +1 -0
  38. data/lib/aws-sdk-signin/endpoint_parameters.rb +69 -0
  39. data/lib/aws-sdk-signin/endpoint_provider.rb +59 -0
  40. data/lib/aws-sdk-signin/endpoints.rb +20 -0
  41. data/lib/aws-sdk-signin/errors.rb +122 -0
  42. data/lib/aws-sdk-signin/plugins/endpoints.rb +77 -0
  43. data/lib/aws-sdk-signin/resource.rb +26 -0
  44. data/lib/aws-sdk-signin/types.rb +299 -0
  45. data/lib/aws-sdk-signin.rb +63 -0
  46. data/lib/aws-sdk-sso/client.rb +24 -17
  47. data/lib/aws-sdk-sso/endpoint_parameters.rb +4 -4
  48. data/lib/aws-sdk-sso/endpoint_provider.rb +2 -2
  49. data/lib/aws-sdk-sso.rb +1 -1
  50. data/lib/aws-sdk-ssooidc/client.rb +43 -23
  51. data/lib/aws-sdk-ssooidc/client_api.rb +5 -0
  52. data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +4 -4
  53. data/lib/aws-sdk-ssooidc/errors.rb +10 -0
  54. data/lib/aws-sdk-ssooidc/types.rb +27 -15
  55. data/lib/aws-sdk-ssooidc.rb +1 -1
  56. data/lib/aws-sdk-sts/client.rb +159 -28
  57. data/lib/aws-sdk-sts/client_api.rb +74 -0
  58. data/lib/aws-sdk-sts/customizations.rb +0 -1
  59. data/lib/aws-sdk-sts/endpoint_parameters.rb +5 -5
  60. data/lib/aws-sdk-sts/errors.rb +64 -1
  61. data/lib/aws-sdk-sts/presigner.rb +2 -6
  62. data/lib/aws-sdk-sts/types.rb +175 -6
  63. data/lib/aws-sdk-sts.rb +1 -1
  64. data/lib/seahorse/client/h2/handler.rb +6 -1
  65. data/lib/seahorse/client/net_http/connection_pool.rb +2 -1
  66. data/lib/seahorse/client/request_context.rb +2 -2
  67. data/lib/seahorse/util.rb +2 -1
  68. metadata +28 -2
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ # An auto-refreshing credential provider that retrieves credentials from
5
+ # a cached login token. This class does NOT implement the AWS Sign-In
6
+ # login flow - tokens must be generated separately by running `aws login`
7
+ # from the AWS CLI/AWS Tools for PowerShell with the correct profile.
8
+ # The {LoginCredentials} will auto-refresh the AWS credentials from AWS Sign-In.
9
+ #
10
+ # # You must first run aws login --profile your-login-profile
11
+ # login_credentials = Aws::LoginCredentials.new(login_session: 'my_login_session')
12
+ # ec2 = Aws::EC2::Client.new(credentials: login_credentials)
13
+ #
14
+ # If you omit the `:client` option, a new {Aws::Signin::Client} object will
15
+ # be constructed with additional options that were provided.
16
+ class LoginCredentials
17
+ include CredentialProvider
18
+ include RefreshingCredentials
19
+
20
+ # @option options [required, String] :login_session An opaque string
21
+ # used to determine the cache file location. This value can be found
22
+ # in the AWS config file which is set by the AWS CLI/AWS Tools for
23
+ # PowerShell automatically.
24
+ #
25
+ # @option options [Signin::Client] :client Optional `Signin::Client`.
26
+ # If not provided, a client will be constructed.
27
+ def initialize(options = {})
28
+ raise ArgumentError, 'Missing login_session' unless options[:login_session]
29
+
30
+ @login_session = options.delete(:login_session)
31
+ @client = options[:client]
32
+ unless @client
33
+ client_opts = options.reject { |key, _| CLIENT_EXCLUDE_OPTIONS.include?(key) }
34
+ @client = Signin::Client.new(client_opts.merge(credentials: nil))
35
+ end
36
+ @metrics = ['CREDENTIALS_LOGIN']
37
+ @async_refresh = true
38
+ super
39
+ end
40
+
41
+ # @return [Signin::Client]
42
+ attr_reader :client
43
+
44
+ private
45
+
46
+ def refresh
47
+ # First reload the token from disk to ensure it hasn't been refreshed externally
48
+ token_json = read_cached_token
49
+ update_creds(token_json['accessToken'])
50
+ return if @credentials && @expiration && !near_expiration?(sync_expiration_length)
51
+
52
+ # Using OpenSSL 3.6.0 may result in errors like "certificate verify failed (unable to get certificate CRL)."
53
+ # A recommended workaround is to use OpenSSL version < 3.6.0 or requiring the openssl gem with a version of at
54
+ # least 3.2.2. GitHub issue: https://github.com/openssl/openssl/issues/28752.
55
+ if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('3.6.') &&
56
+ (!Gem.loaded_specs['openssl'] || Gem.loaded_specs['openssl'].version < Gem::Version.new('3.2.2'))
57
+ warn 'WARNING: OpenSSL 3.6.x may cause certificate verify errors - use OpenSSL < 3.6.0 or openssl gem >= 3.2.2'
58
+ end
59
+
60
+ # Attempt to refresh the token
61
+ attempt_refresh(token_json)
62
+
63
+ # Raise if token is hard expired
64
+ return unless !@expiration || @expiration < Time.now
65
+
66
+ raise Errors::InvalidLoginToken,
67
+ 'Login token is invalid and failed to refresh. Please reauthenticate.'
68
+ end
69
+
70
+ def read_cached_token
71
+ cached_token = JSON.load_file(login_cache_file)
72
+ validate_cached_token(cached_token)
73
+ cached_token
74
+ rescue Errno::ENOENT, Aws::Json::ParseError
75
+ raise Errors::InvalidLoginToken,
76
+ "Failed to load a Login token for login session #{@login_session}. Please reauthenticate."
77
+ end
78
+
79
+ def login_cache_file
80
+ directory = ENV['AWS_LOGIN_CACHE_DIRECTORY'] || File.join(Dir.home, '.aws', 'login', 'cache')
81
+ login_session_sha = OpenSSL::Digest::SHA256.hexdigest(@login_session.strip.encode('utf-8'))
82
+ File.join(directory, "#{login_session_sha}.json")
83
+ end
84
+
85
+ def validate_cached_token(cached_token)
86
+ required_cached_token_fields = %w[accessToken clientId refreshToken dpopKey]
87
+ missing_fields = required_cached_token_fields.reject { |field| cached_token[field] }
88
+ unless missing_fields.empty?
89
+ raise ArgumentError, "Cached login token is missing required field(s): #{missing_fields}. " \
90
+ 'Please reauthenticate.'
91
+ end
92
+
93
+ access_token = cached_token['accessToken']
94
+ required_access_token_fields = %w[accessKeyId secretAccessKey sessionToken accountId expiresAt]
95
+ missing_fields = required_access_token_fields.reject { |field| access_token[field] }
96
+
97
+ return if missing_fields.empty?
98
+
99
+ raise ArgumentError, "Access token in cached login token is missing required field(s): #{missing_fields}. " \
100
+ 'Please reauthenticate.'
101
+ end
102
+
103
+ def update_creds(access_token)
104
+ @credentials = Credentials.new(
105
+ access_token['accessKeyId'],
106
+ access_token['secretAccessKey'],
107
+ access_token['sessionToken'],
108
+ account_id: access_token['accountId']
109
+ )
110
+ @expiration = Time.parse(access_token['expiresAt'])
111
+ end
112
+
113
+ def attempt_refresh(token_json)
114
+ resp = make_request(token_json)
115
+ parse_resp(resp.token_output, token_json)
116
+ update_creds(token_json['accessToken'])
117
+ update_token_cache(token_json)
118
+ rescue Signin::Errors::AccessDeniedException => e
119
+ case e.error
120
+ when 'TOKEN_EXPIRED'
121
+ warn 'Your session has expired. Please reauthenticate.'
122
+ when 'USER_CREDENTIALS_CHANGED'
123
+ warn 'Unable to refresh credentials because of a change in your password. ' \
124
+ 'Please reauthenticate with your new password.'
125
+ when 'INSUFFICIENT_PERMISSIONS'
126
+ warn 'Unable to refresh credentials due to insufficient permissions. ' \
127
+ 'You may be missing permission for the `CreateOAuth2Token` action.'
128
+ end
129
+ rescue StandardError => e
130
+ warn("Failed to refresh Login token for LoginCredentials: #{e.message}")
131
+ end
132
+
133
+ def make_request(token_json)
134
+ options = {
135
+ token_input: {
136
+ client_id: token_json['clientId'],
137
+ grant_type: 'refresh_token',
138
+ refresh_token: token_json['refreshToken']
139
+ }
140
+ }
141
+ req = @client.build_request(:create_o_auth_2_token, options)
142
+ endpoint_params = Aws::Signin::EndpointParameters.create(req.context.config)
143
+ endpoint = req.context.config.endpoint_provider.resolve_endpoint(endpoint_params)
144
+ endpoint = URI.join(endpoint.url, @client.config.api.operation(:create_o_auth_2_token).http_request_uri).to_s
145
+ req.context.http_request.headers['DPoP'] = dpop_proof(token_json['dpopKey'], endpoint)
146
+ req.send_request
147
+ end
148
+
149
+ def dpop_proof(dpop_key, endpoint)
150
+ # Load private key from cached token file
151
+ private_key = OpenSSL::PKey.read(dpop_key)
152
+ public_key = private_key.public_key.to_octet_string(:uncompressed)
153
+
154
+ # Construct header and payload
155
+ header = build_header(public_key[1, 32], public_key[33, 32])
156
+ payload = build_payload(endpoint)
157
+
158
+ # Base64URL encode header and payload, sign message using private key, and create header
159
+ message = build_message(header, payload)
160
+ signature = private_key.sign(OpenSSL::Digest.new('SHA256'), message)
161
+ jws_signature = der_to_jws(signature)
162
+ "#{message}.#{Base64.urlsafe_encode64(jws_signature, padding: false)}"
163
+ end
164
+
165
+ def build_header(x_bytes, y_bytes)
166
+ {
167
+ 'alg' => 'ES256', # signing algorithm
168
+ 'jwk' => {
169
+ 'crv' => 'P-256', # curve name
170
+ 'kty' => 'EC', # key type
171
+ 'x' => Base64.urlsafe_encode64(x_bytes, padding: false), # public x coordinate
172
+ 'y' => Base64.urlsafe_encode64(y_bytes, padding: false) # public y coordinate
173
+ },
174
+ 'typ' => 'dpop+jwt' # hardcoded
175
+ }
176
+ end
177
+
178
+ def build_payload(htu)
179
+ {
180
+ 'jti' => SecureRandom.uuid, # unique identifier (UUID4)
181
+ 'htm' => @client.config.api.operation(:create_o_auth_2_token).http_method, # POST
182
+ 'htu' => htu, # endpoint of the CreateOAuth2Token operation, with path
183
+ 'iat' => Time.now.utc.to_i # UTC timestamp, specified number of seconds from 1970-01-01T00:00:00Z UTC
184
+ }
185
+ end
186
+
187
+ def build_message(header, payload)
188
+ encoded_header = Base64.urlsafe_encode64(JSON.dump(header), padding: false)
189
+ encoded_payload = Base64.urlsafe_encode64(JSON.dump(payload), padding: false)
190
+ "#{encoded_header}.#{encoded_payload}"
191
+ end
192
+
193
+ # Converts DER-encoded ASN.1 signature to JWS
194
+ def der_to_jws(der_signature)
195
+ asn1 = OpenSSL::ASN1.decode(der_signature)
196
+ r = asn1.value[0].value
197
+ s = asn1.value[1].value
198
+
199
+ r_hex = r.to_s(16).rjust(64, '0')
200
+ s_hex = s.to_s(16).rjust(64, '0')
201
+
202
+ [r_hex + s_hex].pack('H*')
203
+ end
204
+
205
+ def parse_resp(resp, token_json)
206
+ access_token = token_json['accessToken']
207
+ access_token.merge!(
208
+ 'accessKeyId' => resp.access_token.access_key_id,
209
+ 'secretAccessKey' => resp.access_token.secret_access_key,
210
+ 'sessionToken' => resp.access_token.session_token,
211
+ 'expiresAt' => (Time.now.utc + resp.expires_in).to_datetime.rfc3339
212
+ )
213
+ token_json['refreshToken'] = resp.refresh_token
214
+ end
215
+
216
+ def update_token_cache(token_json)
217
+ cached_token = token_json.dup
218
+ # File.write is not atomic so use temp file and move
219
+ temp_file = Tempfile.new('temp_file')
220
+ begin
221
+ temp_file.write(Json.dump(cached_token))
222
+ temp_file.close
223
+ FileUtils.mv(temp_file.path, login_cache_file)
224
+ ensure
225
+ temp_file.unlink if File.exist?(temp_file.path) # Ensure temp file is cleaned up
226
+ end
227
+ end
228
+ end
229
+ end
@@ -190,7 +190,6 @@ module Aws
190
190
  name: "x-amz-checksum-#{algorithm.downcase}",
191
191
  request_algorithm_header: request_algorithm_header(context)
192
192
  }
193
-
194
193
  context[:http_checksum][:request_algorithm] = request_algorithm
195
194
  calculate_request_checksum(context, request_algorithm)
196
195
  end
@@ -249,6 +248,7 @@ module Aws
249
248
  return unless context.operation.http_checksum
250
249
 
251
250
  input_member = context.operation.http_checksum['requestAlgorithmMember']
251
+
252
252
  context.params[input_member.to_sym] ||= DEFAULT_CHECKSUM if input_member
253
253
  end
254
254
 
@@ -271,25 +271,39 @@ module Aws
271
271
  context.operation.http_checksum['responseAlgorithms']
272
272
  end
273
273
 
274
- def checksum_required?(context)
275
- (http_checksum = context.operation.http_checksum) &&
276
- (checksum_required = http_checksum['requestChecksumRequired']) &&
277
- (checksum_required && context.config.request_checksum_calculation == 'when_required')
278
- end
279
-
280
- def checksum_optional?(context)
281
- context.operation.http_checksum &&
282
- context.config.request_checksum_calculation != 'when_required'
283
- end
284
-
285
274
  def checksum_provided_as_header?(headers)
286
275
  headers.any? { |k, _| k.start_with?('x-amz-checksum-') }
287
276
  end
288
277
 
278
+ # Determines whether a request checksum should be calculated.
279
+ # 1. **No existing checksum in header**: Skips if checksum header already present
280
+ # 2. **Operation support**: Considers model, client configuration and user input.
289
281
  def should_calculate_request_checksum?(context)
290
282
  !checksum_provided_as_header?(context.http_request.headers) &&
291
- request_algorithm_selection(context) &&
292
- (checksum_required?(context) || checksum_optional?(context))
283
+ checksum_applicable?(context)
284
+ end
285
+
286
+ # Checks if checksum calculation should proceed based on operation requirements and client settings.
287
+ # Returns true when any of these conditions are met:
288
+ # 1. http checksum's requestChecksumRequired is true
289
+ # 2. Config for request_checksum_calculation is "when_supported"
290
+ # 3. Config for request_checksum_calculation is "when_required" AND user provided checksum algorithm
291
+ def checksum_applicable?(context)
292
+ http_checksum = context.operation.http_checksum
293
+ return false unless http_checksum
294
+
295
+ return true if http_checksum['requestChecksumRequired']
296
+
297
+ return false unless (algorithm_member = http_checksum['requestAlgorithmMember'])
298
+
299
+ case context.config.request_checksum_calculation
300
+ when 'when_supported'
301
+ true
302
+ when 'when_required'
303
+ !context.params[algorithm_member.to_sym].nil?
304
+ else
305
+ false
306
+ end
293
307
  end
294
308
 
295
309
  def choose_request_algorithm!(context)
@@ -14,64 +14,68 @@ module Aws
14
14
 
15
15
  option(:account_id, doc_type: String, docstring: '')
16
16
 
17
- option(:profile,
17
+ option(
18
+ :profile,
18
19
  doc_default: 'default',
19
20
  doc_type: String,
20
- docstring: <<-DOCS)
21
- Used when loading credentials from the shared credentials file
22
- at HOME/.aws/credentials. When not specified, 'default' is used.
21
+ docstring: <<~DOCS)
22
+ Used when loading credentials from the shared credentials file at `HOME/.aws/credentials`.
23
+ When not specified, 'default' is used.
23
24
  DOCS
24
25
 
25
- option(:credentials,
26
+ option(
27
+ :credentials,
26
28
  required: true,
27
29
  doc_type: 'Aws::CredentialProvider',
28
30
  rbs_type: 'untyped',
29
- docstring: <<-DOCS
30
- Your AWS credentials. This can be an instance of any one of the
31
- following classes:
31
+ docstring: <<~DOCS
32
+ Your AWS credentials used for authentication. This can be any class that includes and implements
33
+ `Aws::CredentialProvider`, or instance of any one of the following classes:
32
34
 
33
- * `Aws::Credentials` - Used for configuring static, non-refreshing
34
- credentials.
35
+ * `Aws::Credentials` - Used for configuring static, non-refreshing
36
+ credentials.
35
37
 
36
- * `Aws::SharedCredentials` - Used for loading static credentials from a
37
- shared file, such as `~/.aws/config`.
38
+ * `Aws::SharedCredentials` - Used for loading static credentials from a
39
+ shared file, such as `~/.aws/config`.
38
40
 
39
- * `Aws::AssumeRoleCredentials` - Used when you need to assume a role.
41
+ * `Aws::AssumeRoleCredentials` - Used when you need to assume a role.
40
42
 
41
- * `Aws::AssumeRoleWebIdentityCredentials` - Used when you need to
42
- assume a role after providing credentials via the web.
43
+ * `Aws::AssumeRoleWebIdentityCredentials` - Used when you need to
44
+ assume a role after providing credentials via the web.
43
45
 
44
- * `Aws::SSOCredentials` - Used for loading credentials from AWS SSO using an
45
- access token generated from `aws login`.
46
+ * `Aws::SSOCredentials` - Used for loading credentials from AWS SSO using an
47
+ access token generated from `aws login`.
46
48
 
47
- * `Aws::ProcessCredentials` - Used for loading credentials from a
48
- process that outputs to stdout.
49
+ * `Aws::ProcessCredentials` - Used for loading credentials from a
50
+ process that outputs to stdout.
49
51
 
50
- * `Aws::InstanceProfileCredentials` - Used for loading credentials
51
- from an EC2 IMDS on an EC2 instance.
52
+ * `Aws::InstanceProfileCredentials` - Used for loading credentials
53
+ from an EC2 IMDS on an EC2 instance.
52
54
 
53
- * `Aws::ECSCredentials` - Used for loading credentials from
54
- instances running in ECS.
55
+ * `Aws::ECSCredentials` - Used for loading credentials from
56
+ instances running in ECS.
55
57
 
56
- * `Aws::CognitoIdentityCredentials` - Used for loading credentials
57
- from the Cognito Identity service.
58
+ * `Aws::CognitoIdentityCredentials` - Used for loading credentials
59
+ from the Cognito Identity service.
58
60
 
59
- When `:credentials` are not configured directly, the following
60
- locations will be searched for credentials:
61
+ When `:credentials` are not configured directly, the following locations will be searched for credentials:
61
62
 
62
- * `Aws.config[:credentials]`
63
- * The `:access_key_id`, `:secret_access_key`, `:session_token`, and
64
- `:account_id` options.
65
- * ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'],
66
- ENV['AWS_SESSION_TOKEN'], and ENV['AWS_ACCOUNT_ID']
67
- * `~/.aws/credentials`
68
- * `~/.aws/config`
69
- * EC2/ECS IMDS instance profile - When used by default, the timeouts
70
- are very aggressive. Construct and pass an instance of
71
- `Aws::InstanceProfileCredentials` or `Aws::ECSCredentials` to
72
- enable retries and extended timeouts. Instance profile credential
73
- fetching can be disabled by setting ENV['AWS_EC2_METADATA_DISABLED']
74
- to true.
63
+ * `Aws.config[:credentials]`
64
+
65
+ * The `:access_key_id`, `:secret_access_key`, `:session_token`, and
66
+ `:account_id` options.
67
+
68
+ * `ENV['AWS_ACCESS_KEY_ID']`, `ENV['AWS_SECRET_ACCESS_KEY']`,
69
+ `ENV['AWS_SESSION_TOKEN']`, and `ENV['AWS_ACCOUNT_ID']`.
70
+
71
+ * `~/.aws/credentials`
72
+
73
+ * `~/.aws/config`
74
+
75
+ * EC2/ECS IMDS instance profile - When used by default, the timeouts are very aggressive.
76
+ Construct and pass an instance of `Aws::InstanceProfileCredentials` or `Aws::ECSCredentials` to
77
+ enable retries and extended timeouts. Instance profile credential fetching can be disabled by
78
+ setting `ENV['AWS_EC2_METADATA_DISABLED']` to `true`.
75
79
  DOCS
76
80
  ) do |config|
77
81
  CredentialProviderChain.new(config).resolve
@@ -81,31 +85,43 @@ locations will be searched for credentials:
81
85
 
82
86
  option(:instance_profile_credentials_timeout, 1)
83
87
 
84
- option(:token_provider,
85
- required: false,
86
- doc_type: 'Aws::TokenProvider',
87
- rbs_type: 'untyped',
88
- docstring: <<-DOCS
89
- A Bearer Token Provider. This can be an instance of any one of the
90
- following classes:
88
+ option(
89
+ :token_provider,
90
+ doc_type: 'Aws::TokenProvider',
91
+ rbs_type: 'untyped',
92
+ docstring: <<~DOCS
93
+ Your Bearer token used for authentication. This can be any class that includes and implements
94
+ `Aws::TokenProvider`, or instance of any one of the following classes:
91
95
 
92
- * `Aws::StaticTokenProvider` - Used for configuring static, non-refreshing
93
- tokens.
96
+ * `Aws::StaticTokenProvider` - Used for configuring static, non-refreshing
97
+ tokens.
94
98
 
95
- * `Aws::SSOTokenProvider` - Used for loading tokens from AWS SSO using an
96
- access token generated from `aws login`.
99
+ * `Aws::SSOTokenProvider` - Used for loading tokens from AWS SSO using an
100
+ access token generated from `aws login`.
97
101
 
98
- When `:token_provider` is not configured directly, the `Aws::TokenProviderChain`
99
- will be used to search for tokens configured for your profile in shared configuration files.
100
- DOCS
102
+ When `:token_provider` is not configured directly, the `Aws::TokenProviderChain`
103
+ will be used to search for tokens configured for your profile in shared configuration files.
104
+ DOCS
101
105
  ) do |config|
102
- if config.stub_responses
103
- StaticTokenProvider.new('token')
104
- else
105
- TokenProviderChain.new(config).resolve
106
- end
106
+ TokenProviderChain.new(config).resolve
107
107
  end
108
108
 
109
+ option(
110
+ :auth_scheme_preference,
111
+ doc_type: 'Array<String>',
112
+ rbs_type: 'Array[String]',
113
+ docstring: <<~DOCS
114
+ A list of preferred authentication schemes to use when making a request. Supported values are:
115
+ `sigv4`, `sigv4a`, `httpBearerAuth`, and `noAuth`. When set using `ENV['AWS_AUTH_SCHEME_PREFERENCE']` or in
116
+ shared config as `auth_scheme_preference`, the value should be a comma-separated list.
117
+ DOCS
118
+ ) do |config|
119
+ value =
120
+ ENV['AWS_AUTH_SCHEME_PREFERENCE'] ||
121
+ Aws.shared_config.auth_scheme_preference(profile: config.profile) ||
122
+ ''
123
+ value.gsub(' ', '').gsub("\t", '').split(',')
124
+ end
109
125
  end
110
126
  end
111
127
  end
@@ -13,9 +13,6 @@ module Aws
13
13
  option(:sigv4_region)
14
14
  option(:unsigned_operations, default: [])
15
15
 
16
- supported_auth_types = %w[sigv4 bearer sigv4-s3express sigv4a none]
17
- SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
18
-
19
16
  def add_handlers(handlers, cfg)
20
17
  operations = cfg.api.operation_names - cfg.unsigned_operations
21
18
  handlers.add(Handler, step: :sign, operations: operations)
@@ -32,7 +29,7 @@ module Aws
32
29
  }
33
30
  SignatureV4.new(auth_scheme, config, sigv4_overrides)
34
31
  when 'bearer'
35
- Bearer.new
32
+ Bearer.new(config)
36
33
  else
37
34
  NullSigner.new
38
35
  end
@@ -41,7 +38,6 @@ module Aws
41
38
  class Handler < Seahorse::Client::Handler
42
39
  def call(context)
43
40
  # Skip signing if using sigv2 signing from s3_signer in S3
44
- credentials = nil
45
41
  unless v2_signing?(context.config)
46
42
  signer = Sign.signer_for(
47
43
  context[:auth_scheme],
@@ -49,18 +45,22 @@ module Aws
49
45
  context[:sigv4_region],
50
46
  context[:sigv4_credentials]
51
47
  )
52
- credentials = signer.credentials if signer.is_a?(SignatureV4)
53
48
  signer.sign(context)
54
49
  end
55
- with_metrics(credentials) { @handler.call(context) }
50
+ with_metrics(signer) { @handler.call(context) }
56
51
  end
57
52
 
58
53
  private
59
54
 
60
- def with_metrics(credentials, &block)
61
- return block.call unless credentials&.respond_to?(:metrics)
62
-
63
- Aws::Plugins::UserAgent.metric(*credentials.metrics, &block)
55
+ def with_metrics(signer, &block)
56
+ case signer
57
+ when SignatureV4
58
+ Aws::Plugins::UserAgent.metric(*signer.credentials.metrics, &block)
59
+ when Bearer
60
+ Aws::Plugins::UserAgent.metric(*signer.token_provider.metrics, &block)
61
+ else
62
+ block.call
63
+ end
64
64
  end
65
65
 
66
66
  def v2_signing?(config)
@@ -72,21 +72,19 @@ module Aws
72
72
 
73
73
  # @api private
74
74
  class Bearer
75
- def initialize
75
+ def initialize(config)
76
+ @token_provider = config.token_provider
76
77
  end
77
78
 
79
+ attr_reader :token_provider
80
+
78
81
  def sign(context)
79
82
  if context.http_request.endpoint.scheme != 'https'
80
- raise ArgumentError,
81
- 'Unable to use bearer authorization on non https endpoint.'
83
+ raise ArgumentError, 'Unable to use bearer authorization on non https endpoint.'
82
84
  end
85
+ raise Errors::MissingBearerTokenError unless @token_provider && @token_provider.set?
83
86
 
84
- token_provider = context.config.token_provider
85
-
86
- raise Errors::MissingBearerTokenError unless token_provider&.set?
87
-
88
- context.http_request.headers['Authorization'] =
89
- "Bearer #{token_provider.token.token}"
87
+ context.http_request.headers['Authorization'] = "Bearer #{@token_provider.token.token}"
90
88
  end
91
89
 
92
90
  def presign_url(*args)
@@ -100,16 +98,11 @@ module Aws
100
98
 
101
99
  # @api private
102
100
  class SignatureV4
103
- attr_reader :signer
104
-
105
101
  def initialize(auth_scheme, config, sigv4_overrides = {})
106
102
  scheme_name = auth_scheme['name']
107
-
108
103
  unless %w[sigv4 sigv4a sigv4-s3express].include?(scheme_name)
109
- raise ArgumentError,
110
- "Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
104
+ raise ArgumentError, "Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
111
105
  end
112
-
113
106
  region = if scheme_name == 'sigv4a'
114
107
  auth_scheme['signingRegionSet'].join(',')
115
108
  else
@@ -121,8 +114,8 @@ module Aws
121
114
  region: sigv4_overrides[:region] || config.sigv4_region || region,
122
115
  credentials_provider: sigv4_overrides[:credentials] || config.credentials,
123
116
  signing_algorithm: scheme_name.to_sym,
124
- uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
125
- normalize_path: !!!auth_scheme['disableNormalizePath'],
117
+ uri_escape_path: !auth_scheme['disableDoubleEncoding'],
118
+ normalize_path: !auth_scheme['disableNormalizePath'],
126
119
  unsigned_headers: %w[content-length user-agent x-amzn-trace-id expect transfer-encoding connection]
127
120
  )
128
121
  rescue Aws::Sigv4::Errors::MissingCredentialsError
@@ -130,6 +123,8 @@ module Aws
130
123
  end
131
124
  end
132
125
 
126
+ attr_reader :signer
127
+
133
128
  def sign(context)
134
129
  req = context.http_request
135
130
 
@@ -29,6 +29,12 @@ requests are made, and retries are disabled.
29
29
  end
30
30
  end
31
31
 
32
+ option(:token_provider) do |config|
33
+ if config.stub_responses
34
+ StaticTokenProvider.new('stubbed-token')
35
+ end
36
+ end
37
+
32
38
  option(:stubs) { {} }
33
39
  option(:stubs_mutex) { Mutex.new }
34
40
  option(:api_requests) { [] }
@@ -54,7 +54,10 @@ module Aws
54
54
  "CREDENTIALS_HTTP" : "z",
55
55
  "CREDENTIALS_IMDS" : "0",
56
56
  "SSO_LOGIN_DEVICE" : "1",
57
- "SSO_LOGIN_AUTH" : "2"
57
+ "SSO_LOGIN_AUTH" : "2",
58
+ "BEARER_SERVICE_ENV_VARS": "3",
59
+ "CREDENTIALS_PROFILE_LOGIN": "AC",
60
+ "CREDENTIALS_LOGIN": "AD"
58
61
  }
59
62
  METRICS
60
63
 
@@ -1,28 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aws
4
-
5
4
  # Base class used credential classes that can be refreshed. This
6
5
  # provides basic refresh logic in a thread-safe manner. Classes mixing in
7
- # this module are expected to implement a #refresh method that populates
6
+ # this module are expected to implement a `#refresh` method that populates
8
7
  # the following instance variables:
9
8
  #
10
- # * `@access_key_id`
11
- # * `@secret_access_key`
12
- # * `@session_token`
13
- # * `@expiration`
9
+ # * `@credentials` ({Credentials})
10
+ # * `@expiration` (Time)
14
11
  #
15
- # @api private
16
12
  module RefreshingCredentials
17
-
18
13
  SYNC_EXPIRATION_LENGTH = 300 # 5 minutes
19
14
  ASYNC_EXPIRATION_LENGTH = 600 # 10 minutes
20
15
 
21
16
  CLIENT_EXCLUDE_OPTIONS = Set.new([:before_refresh]).freeze
22
17
 
18
+ # @param [Hash] options
19
+ # @option options [Proc] :before_refresh A Proc called before credentials are refreshed.
20
+ # It accepts `self` as the only argument.
23
21
  def initialize(options = {})
24
22
  @mutex = Mutex.new
25
- @before_refresh = options.delete(:before_refresh) if Hash === options
23
+ @before_refresh = options.delete(:before_refresh) if options.is_a?(Hash)
26
24
 
27
25
  @before_refresh.call(self) if @before_refresh
28
26
  refresh
@@ -59,7 +57,7 @@ module Aws
59
57
  # Otherwise, if we're approaching expiration, use the existing credentials
60
58
  # but attempt a refresh in the background.
61
59
  def refresh_if_near_expiration!
62
- # Note: This check is an optimization. Rather than acquire the mutex on every #refresh_if_near_expiration
60
+ # NOTE: This check is an optimization. Rather than acquire the mutex on every #refresh_if_near_expiration
63
61
  # call, we check before doing so, and then we check within the mutex to avoid a race condition.
64
62
  # See issue: https://github.com/aws/aws-sdk-ruby/issues/2641 for more info.
65
63
  if near_expiration?(sync_expiration_length)
@@ -91,6 +89,5 @@ module Aws
91
89
  true
92
90
  end
93
91
  end
94
-
95
92
  end
96
93
  end