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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +157 -0
- data/VERSION +1 -1
- data/lib/aws-defaults/default_configuration.rb +2 -1
- data/lib/aws-sdk-core/assume_role_credentials.rb +8 -8
- data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +2 -2
- data/lib/aws-sdk-core/client_stubs.rb +6 -0
- data/lib/aws-sdk-core/credential_provider_chain.rb +72 -23
- data/lib/aws-sdk-core/ecs_credentials.rb +13 -13
- data/lib/aws-sdk-core/endpoints/matchers.rb +2 -1
- data/lib/aws-sdk-core/endpoints.rb +37 -13
- data/lib/aws-sdk-core/error_handler.rb +5 -0
- data/lib/aws-sdk-core/errors.rb +3 -0
- data/lib/aws-sdk-core/event_emitter.rb +1 -1
- data/lib/aws-sdk-core/instance_profile_credentials.rb +146 -157
- data/lib/aws-sdk-core/json/error_handler.rb +14 -4
- data/lib/aws-sdk-core/login_credentials.rb +229 -0
- data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +28 -14
- data/lib/aws-sdk-core/plugins/credentials_configuration.rb +75 -59
- data/lib/aws-sdk-core/plugins/sign.rb +23 -28
- data/lib/aws-sdk-core/plugins/stub_responses.rb +6 -0
- data/lib/aws-sdk-core/plugins/user_agent.rb +4 -1
- data/lib/aws-sdk-core/refreshing_credentials.rb +8 -11
- data/lib/aws-sdk-core/rpc_v2/error_handler.rb +26 -16
- data/lib/aws-sdk-core/rpc_v2/parser.rb +8 -0
- data/lib/aws-sdk-core/shared_config.rb +30 -0
- data/lib/aws-sdk-core/sso_credentials.rb +1 -1
- data/lib/aws-sdk-core/static_token_provider.rb +1 -2
- data/lib/aws-sdk-core/token.rb +3 -3
- data/lib/aws-sdk-core/token_provider.rb +4 -0
- data/lib/aws-sdk-core/token_provider_chain.rb +2 -6
- data/lib/aws-sdk-core/util.rb +2 -1
- data/lib/aws-sdk-core/xml/error_handler.rb +3 -1
- data/lib/aws-sdk-core.rb +4 -0
- data/lib/aws-sdk-signin/client.rb +604 -0
- data/lib/aws-sdk-signin/client_api.rb +119 -0
- data/lib/aws-sdk-signin/customizations.rb +1 -0
- data/lib/aws-sdk-signin/endpoint_parameters.rb +69 -0
- data/lib/aws-sdk-signin/endpoint_provider.rb +59 -0
- data/lib/aws-sdk-signin/endpoints.rb +20 -0
- data/lib/aws-sdk-signin/errors.rb +122 -0
- data/lib/aws-sdk-signin/plugins/endpoints.rb +77 -0
- data/lib/aws-sdk-signin/resource.rb +26 -0
- data/lib/aws-sdk-signin/types.rb +299 -0
- data/lib/aws-sdk-signin.rb +63 -0
- data/lib/aws-sdk-sso/client.rb +24 -17
- data/lib/aws-sdk-sso/endpoint_parameters.rb +4 -4
- data/lib/aws-sdk-sso/endpoint_provider.rb +2 -2
- data/lib/aws-sdk-sso.rb +1 -1
- data/lib/aws-sdk-ssooidc/client.rb +43 -23
- data/lib/aws-sdk-ssooidc/client_api.rb +5 -0
- data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +4 -4
- data/lib/aws-sdk-ssooidc/errors.rb +10 -0
- data/lib/aws-sdk-ssooidc/types.rb +27 -15
- data/lib/aws-sdk-ssooidc.rb +1 -1
- data/lib/aws-sdk-sts/client.rb +159 -28
- data/lib/aws-sdk-sts/client_api.rb +74 -0
- data/lib/aws-sdk-sts/customizations.rb +0 -1
- data/lib/aws-sdk-sts/endpoint_parameters.rb +5 -5
- data/lib/aws-sdk-sts/errors.rb +64 -1
- data/lib/aws-sdk-sts/presigner.rb +2 -6
- data/lib/aws-sdk-sts/types.rb +175 -6
- data/lib/aws-sdk-sts.rb +1 -1
- data/lib/seahorse/client/h2/handler.rb +6 -1
- data/lib/seahorse/client/net_http/connection_pool.rb +2 -1
- data/lib/seahorse/client/request_context.rb +2 -2
- data/lib/seahorse/util.rb +2 -1
- 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
|
-
|
|
292
|
-
|
|
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(
|
|
17
|
+
option(
|
|
18
|
+
:profile,
|
|
18
19
|
doc_default: 'default',
|
|
19
20
|
doc_type: String,
|
|
20
|
-
docstring:
|
|
21
|
-
Used when loading credentials from the shared credentials file
|
|
22
|
-
|
|
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(
|
|
26
|
+
option(
|
|
27
|
+
:credentials,
|
|
26
28
|
required: true,
|
|
27
29
|
doc_type: 'Aws::CredentialProvider',
|
|
28
30
|
rbs_type: 'untyped',
|
|
29
|
-
docstring:
|
|
30
|
-
Your AWS credentials. This can be
|
|
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
|
-
|
|
35
|
+
* `Aws::Credentials` - Used for configuring static, non-refreshing
|
|
36
|
+
credentials.
|
|
35
37
|
|
|
36
|
-
* `Aws::SharedCredentials` - Used for loading static credentials from a
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
* `Aws::ECSCredentials` - Used for loading credentials from
|
|
56
|
+
instances running in ECS.
|
|
55
57
|
|
|
56
|
-
* `Aws::CognitoIdentityCredentials` - Used for loading credentials
|
|
57
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
50
|
+
with_metrics(signer) { @handler.call(context) }
|
|
56
51
|
end
|
|
57
52
|
|
|
58
53
|
private
|
|
59
54
|
|
|
60
|
-
def with_metrics(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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:
|
|
125
|
-
normalize_path:
|
|
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
|
|
6
|
+
# this module are expected to implement a `#refresh` method that populates
|
|
8
7
|
# the following instance variables:
|
|
9
8
|
#
|
|
10
|
-
# * `@
|
|
11
|
-
# * `@
|
|
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
|
|
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
|
-
#
|
|
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
|