aws-sdk-core 3.233.0 → 3.239.2
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 +58 -0
- data/VERSION +1 -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/credential_provider_chain.rb +71 -22
- data/lib/aws-sdk-core/ecs_credentials.rb +13 -13
- data/lib/aws-sdk-core/errors.rb +3 -0
- data/lib/aws-sdk-core/instance_profile_credentials.rb +7 -7
- 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/user_agent.rb +3 -1
- data/lib/aws-sdk-core/refreshing_credentials.rb +8 -11
- data/lib/aws-sdk-core/shared_config.rb +20 -0
- data/lib/aws-sdk-core/sso_credentials.rb +1 -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 +1 -1
- data/lib/aws-sdk-sso/endpoint_parameters.rb +4 -4
- data/lib/aws-sdk-sso.rb +1 -1
- data/lib/aws-sdk-ssooidc/client.rb +1 -1
- data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +4 -4
- data/lib/aws-sdk-ssooidc.rb +1 -1
- data/lib/aws-sdk-sts/client.rb +136 -12
- data/lib/aws-sdk-sts/client_api.rb +72 -0
- data/lib/aws-sdk-sts/endpoint_parameters.rb +5 -5
- data/lib/aws-sdk-sts/errors.rb +64 -0
- 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
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f1e1429692b82c74af4f73ca4302ef10daa7cb7819a46a5571efe119d5219d3
|
|
4
|
+
data.tar.gz: cd44f64eaba5dac2b0cc8057c292098b4512db12b34250397bbaa723a142aa72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b183ab5519ff8d1df36c32dff3abe1aab2759ba4979a8ed9c1fef8d9d5feac08489c632e2313248ad4cbef327b66a9b226e7b7d0a930ff01cf1ba194f47fdaea
|
|
7
|
+
data.tar.gz: 6d64c46bd620408e61d9aac06772001816bf3bf8e674c63e3da8dfb19f167b89169fd333626650930ac7b63b0d797569ccbaffc1a936945c86b219f65cf22923
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
Unreleased Changes
|
|
2
2
|
------------------
|
|
3
3
|
|
|
4
|
+
3.239.2 (2025-11-25)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
* Issue - Fix `login_credentials` in credentials chain when config is enabled.
|
|
8
|
+
|
|
9
|
+
3.239.1 (2025-11-21)
|
|
10
|
+
------------------
|
|
11
|
+
|
|
12
|
+
* Issue - Fixed HTTP/2 connection issues when using custom ports.
|
|
13
|
+
|
|
14
|
+
3.239.0 (2025-11-20)
|
|
15
|
+
------------------
|
|
16
|
+
|
|
17
|
+
* Feature - Updated Aws::Signin::Client with the latest API changes.
|
|
18
|
+
|
|
19
|
+
* Issue - Fix region configuration for LoginCredential's Signin client.
|
|
20
|
+
|
|
21
|
+
3.238.0 (2025-11-19)
|
|
22
|
+
------------------
|
|
23
|
+
|
|
24
|
+
* Feature - Updated Aws::Signin::Client with the latest API changes.
|
|
25
|
+
|
|
26
|
+
* Feature - Updated Aws::STS::Client with the latest API changes.
|
|
27
|
+
|
|
28
|
+
* Feature - IAM now supports outbound identity federation via the STS GetWebIdentityToken API, enabling AWS workloads to securely authenticate with external services using short-lived JSON Web Tokens.
|
|
29
|
+
|
|
30
|
+
* Feature - Add `LoginCredentials` which retrieves credentials from AWS Sign-In. Support `aws-sdk-signin` alias gem.
|
|
31
|
+
|
|
32
|
+
3.237.0 (2025-11-10)
|
|
33
|
+
------------------
|
|
34
|
+
|
|
35
|
+
* Feature - Updated Aws::STS::Client with the latest API changes.
|
|
36
|
+
|
|
37
|
+
* Feature - Added GetDelegatedAccessToken API, which is not available for general use at this time.
|
|
38
|
+
|
|
39
|
+
3.236.0 (2025-10-30)
|
|
40
|
+
------------------
|
|
41
|
+
|
|
42
|
+
* Feature - Updated Aws::STS::Client with the latest API changes.
|
|
43
|
+
|
|
44
|
+
* Feature - Updated Aws::SSO::Client with the latest API changes.
|
|
45
|
+
|
|
46
|
+
* Feature - Update endpoint ruleset parameters casing
|
|
47
|
+
|
|
48
|
+
3.235.0 (2025-10-24)
|
|
49
|
+
------------------
|
|
50
|
+
|
|
51
|
+
* Feature - Updated Aws::SSOOIDC::Client with the latest API changes.
|
|
52
|
+
|
|
53
|
+
* Feature - Update endpoint ruleset parameters casing
|
|
54
|
+
|
|
55
|
+
3.234.0 (2025-10-21)
|
|
56
|
+
------------------
|
|
57
|
+
|
|
58
|
+
* Issue - Fix `request_checksum_calculation` `when_required` mode to only calculate checksums when explicitly provided by user.
|
|
59
|
+
|
|
60
|
+
* Feature - Add `CREDENTIALS_CODE` metric for `static_profile_` prefixed methods in default credential chain.
|
|
61
|
+
|
|
4
62
|
3.233.0 (2025-09-23)
|
|
5
63
|
------------------
|
|
6
64
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.239.2
|
|
@@ -7,7 +7,7 @@ module Aws
|
|
|
7
7
|
# {Aws::STS::Client#assume_role}.
|
|
8
8
|
#
|
|
9
9
|
# role_credentials = Aws::AssumeRoleCredentials.new(
|
|
10
|
-
# client: Aws::STS::Client.new(
|
|
10
|
+
# client: Aws::STS::Client.new(sts_options),
|
|
11
11
|
# role_arn: "linked::account::arn",
|
|
12
12
|
# role_session_name: "session-name"
|
|
13
13
|
# )
|
|
@@ -28,15 +28,15 @@ module Aws
|
|
|
28
28
|
# @option options [Integer] :duration_seconds
|
|
29
29
|
# @option options [String] :external_id
|
|
30
30
|
# @option options [STS::Client] :client
|
|
31
|
-
# @option options [
|
|
31
|
+
# @option options [Proc] :before_refresh A Proc called before
|
|
32
32
|
# credentials are refreshed. Useful for updating tokens.
|
|
33
|
-
#
|
|
34
|
-
# required and need to be refreshed.
|
|
35
|
-
# the following example:
|
|
33
|
+
# `:before_refresh` is called when AWS credentials are
|
|
34
|
+
# required and need to be refreshed. See the example in this doc.
|
|
36
35
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
36
|
+
# @example Tokens can be refreshed using a Proc.
|
|
37
|
+
# before_refresh = Proc.new do |assume_role_credentials|
|
|
38
|
+
# assume_role_credentials.assume_role_params['token_code'] = update_token
|
|
39
|
+
# end
|
|
40
40
|
#
|
|
41
41
|
def initialize(options = {})
|
|
42
42
|
client_opts = {}
|
|
@@ -9,11 +9,11 @@ module Aws
|
|
|
9
9
|
# {Aws::STS::Client#assume_role_with_web_identity}.
|
|
10
10
|
#
|
|
11
11
|
# role_credentials = Aws::AssumeRoleWebIdentityCredentials.new(
|
|
12
|
-
# client: Aws::STS::Client.new(
|
|
12
|
+
# client: Aws::STS::Client.new(sts_options),
|
|
13
13
|
# role_arn: "linked::account::arn",
|
|
14
14
|
# web_identity_token_file: "/path/to/token/file",
|
|
15
15
|
# role_session_name: "session-name"
|
|
16
|
-
# ...
|
|
16
|
+
# # ...
|
|
17
17
|
# )
|
|
18
18
|
# ec2 = Aws::EC2::Client.new(credentials: role_credentials)
|
|
19
19
|
#
|
|
@@ -11,7 +11,7 @@ module Aws
|
|
|
11
11
|
def resolve
|
|
12
12
|
providers.each do |method_name, options|
|
|
13
13
|
provider = send(method_name, options.merge(config: @config))
|
|
14
|
-
return provider if provider
|
|
14
|
+
return provider if provider&.set?
|
|
15
15
|
end
|
|
16
16
|
nil
|
|
17
17
|
end
|
|
@@ -25,12 +25,14 @@ module Aws
|
|
|
25
25
|
[:static_profile_sso_credentials, {}],
|
|
26
26
|
[:static_profile_assume_role_credentials, {}],
|
|
27
27
|
[:static_profile_credentials, {}],
|
|
28
|
+
[:static_profile_login_credentials, {}],
|
|
28
29
|
[:static_profile_process_credentials, {}],
|
|
29
30
|
[:env_credentials, {}],
|
|
30
31
|
[:assume_role_web_identity_credentials, {}],
|
|
31
32
|
[:sso_credentials, {}],
|
|
32
33
|
[:assume_role_credentials, {}],
|
|
33
34
|
[:shared_credentials, {}],
|
|
35
|
+
[:login_credentials, {}],
|
|
34
36
|
[:process_credentials, {}],
|
|
35
37
|
[:instance_profile_credentials, {
|
|
36
38
|
retries: @config ? @config.instance_profile_credentials_retries : 0,
|
|
@@ -54,47 +56,80 @@ module Aws
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def static_profile_assume_role_web_identity_credentials(options)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
return unless Aws.shared_config.config_enabled? && options[:config]&.profile
|
|
60
|
+
|
|
61
|
+
with_metrics('CREDENTIALS_CODE') do
|
|
62
|
+
creds = Aws.shared_config.assume_role_web_identity_credentials_from_config(
|
|
59
63
|
profile: options[:config].profile,
|
|
60
64
|
region: options[:config].region
|
|
61
65
|
)
|
|
66
|
+
return unless creds
|
|
67
|
+
|
|
68
|
+
creds.metrics << 'CREDENTIALS_CODE'
|
|
69
|
+
creds
|
|
62
70
|
end
|
|
63
71
|
end
|
|
64
72
|
|
|
65
73
|
def static_profile_sso_credentials(options)
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
return unless Aws.shared_config.config_enabled? && options[:config]&.profile
|
|
75
|
+
|
|
76
|
+
with_metrics('CREDENTIALS_CODE') do
|
|
77
|
+
creds = Aws.shared_config.sso_credentials_from_config(
|
|
68
78
|
profile: options[:config].profile
|
|
69
79
|
)
|
|
80
|
+
return unless creds
|
|
81
|
+
|
|
82
|
+
creds.metrics << 'CREDENTIALS_CODE'
|
|
83
|
+
creds
|
|
70
84
|
end
|
|
71
85
|
end
|
|
72
86
|
|
|
73
87
|
def static_profile_assume_role_credentials(options)
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
return unless Aws.shared_config.config_enabled? && options[:config]&.profile
|
|
89
|
+
|
|
90
|
+
with_metrics('CREDENTIALS_CODE') do
|
|
91
|
+
creds = assume_role_with_profile(options, options[:config].profile)
|
|
92
|
+
return unless creds
|
|
93
|
+
|
|
94
|
+
creds.metrics << 'CREDENTIALS_CODE'
|
|
95
|
+
creds
|
|
76
96
|
end
|
|
77
97
|
end
|
|
78
98
|
|
|
79
99
|
def static_profile_credentials(options)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
return unless options[:config]&.profile
|
|
101
|
+
|
|
102
|
+
creds = SharedCredentials.new(profile_name: options[:config].profile)
|
|
103
|
+
creds.metrics << 'CREDENTIALS_PROFILE'
|
|
104
|
+
creds
|
|
85
105
|
rescue Errors::NoSuchProfileError
|
|
86
106
|
nil
|
|
87
107
|
end
|
|
88
108
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
def static_profile_login_credentials(options)
|
|
110
|
+
return unless Aws.shared_config.config_enabled? && options[:config]&.profile
|
|
111
|
+
|
|
112
|
+
with_metrics('CREDENTIALS_CODE') do
|
|
113
|
+
creds = Aws.shared_config.login_credentials_from_config(
|
|
114
|
+
profile: options[:config].profile,
|
|
115
|
+
region: options[:config].region
|
|
116
|
+
)
|
|
117
|
+
return unless creds
|
|
118
|
+
|
|
119
|
+
creds.metrics << 'CREDENTIALS_CODE'
|
|
120
|
+
creds
|
|
97
121
|
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def static_profile_process_credentials(options)
|
|
125
|
+
return unless Aws.shared_config.config_enabled? && options[:config]&.profile
|
|
126
|
+
|
|
127
|
+
process_provider = Aws.shared_config.credential_process(profile: options[:config].profile)
|
|
128
|
+
return unless process_provider
|
|
129
|
+
|
|
130
|
+
creds = ProcessCredentials.new([process_provider])
|
|
131
|
+
creds.metrics.concat(%w[CREDENTIALS_PROFILE_PROCESS CREDENTIALS_CODE])
|
|
132
|
+
creds
|
|
98
133
|
rescue Errors::NoSuchProfileError
|
|
99
134
|
nil
|
|
100
135
|
end
|
|
@@ -122,7 +157,7 @@ module Aws
|
|
|
122
157
|
end
|
|
123
158
|
|
|
124
159
|
def determine_profile_name(options)
|
|
125
|
-
(options[:config]
|
|
160
|
+
(options[:config]&.profile) || ENV['AWS_PROFILE'] || ENV['AWS_DEFAULT_PROFILE'] || 'default'
|
|
126
161
|
end
|
|
127
162
|
|
|
128
163
|
def shared_credentials(options)
|
|
@@ -134,6 +169,16 @@ module Aws
|
|
|
134
169
|
nil
|
|
135
170
|
end
|
|
136
171
|
|
|
172
|
+
def login_credentials(options)
|
|
173
|
+
return unless Aws.shared_config.config_enabled?
|
|
174
|
+
|
|
175
|
+
profile_name = determine_profile_name(options)
|
|
176
|
+
region = options[:config].region if options[:config]
|
|
177
|
+
Aws.shared_config.login_credentials_from_config(profile: profile_name, region: region)
|
|
178
|
+
rescue Errors::NoSuchProfileError
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
137
182
|
def process_credentials(options)
|
|
138
183
|
profile_name = determine_profile_name(options)
|
|
139
184
|
if Aws.shared_config.config_enabled?
|
|
@@ -201,10 +246,14 @@ module Aws
|
|
|
201
246
|
profile: profile_name,
|
|
202
247
|
chain_config: @config
|
|
203
248
|
}
|
|
204
|
-
if options[:config]
|
|
249
|
+
if options[:config]&.region
|
|
205
250
|
assume_opts[:region] = options[:config].region
|
|
206
251
|
end
|
|
207
252
|
Aws.shared_config.assume_role_credentials_from_config(assume_opts)
|
|
208
253
|
end
|
|
254
|
+
|
|
255
|
+
def with_metrics(metrics, &block)
|
|
256
|
+
Aws::Plugins::UserAgent.metric(*metrics, &block)
|
|
257
|
+
end
|
|
209
258
|
end
|
|
210
259
|
end
|
|
@@ -42,26 +42,26 @@ module Aws
|
|
|
42
42
|
# @option options [Integer] :retries (5) Number of times to retry
|
|
43
43
|
# when retrieving credentials.
|
|
44
44
|
# @option options [String] :ip_address ('169.254.170.2') This value is
|
|
45
|
-
# ignored if
|
|
46
|
-
# @option options [Integer] :port (80) This value is ignored if
|
|
47
|
-
# is set and
|
|
45
|
+
# ignored if `:endpoint` is set and `:credential_path` is not set.
|
|
46
|
+
# @option options [Integer] :port (80) This value is ignored if `:endpoint`
|
|
47
|
+
# is set and `:credential_path` is not set.
|
|
48
48
|
# @option options [String] :credential_path By default, the value of the
|
|
49
|
-
# AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable.
|
|
49
|
+
# `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable.
|
|
50
50
|
# @option options [String] :endpoint The container credential endpoint.
|
|
51
|
-
# By default, this is the value of the AWS_CONTAINER_CREDENTIALS_FULL_URI
|
|
52
|
-
# environment variable. This value is ignored if
|
|
53
|
-
# ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] is set.
|
|
51
|
+
# By default, this is the value of the `AWS_CONTAINER_CREDENTIALS_FULL_URI`
|
|
52
|
+
# environment variable. This value is ignored if `:credential_path` or
|
|
53
|
+
# `ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI']` is set.
|
|
54
54
|
# @option options [Float] :http_open_timeout (5)
|
|
55
55
|
# @option options [Float] :http_read_timeout (5)
|
|
56
|
-
# @option options [
|
|
56
|
+
# @option options [IO] :http_debug_output (nil) HTTP wire
|
|
57
|
+
# traces are sent to this object. You can specify something
|
|
58
|
+
# like `$stdout`.
|
|
59
|
+
# @option options [Numeric, Proc] :backoff By default, failures are retried
|
|
57
60
|
# with exponential back-off, i.e. `sleep(1.2 ** num_failures)`. You can
|
|
58
61
|
# pass a number of seconds to sleep between failed attempts, or
|
|
59
62
|
# a Proc that accepts the number of failures.
|
|
60
|
-
# @option options [
|
|
61
|
-
#
|
|
62
|
-
# like $stdout.
|
|
63
|
-
# @option options [Callable] before_refresh Proc called before
|
|
64
|
-
# credentials are refreshed. `before_refresh` is called
|
|
63
|
+
# @option options [Proc] :before_refresh A Proc called before
|
|
64
|
+
# credentials are refreshed. `:before_refresh` is called
|
|
65
65
|
# with an instance of this object when
|
|
66
66
|
# AWS credentials are required and need to be refreshed.
|
|
67
67
|
def initialize(options = {})
|
data/lib/aws-sdk-core/errors.rb
CHANGED
|
@@ -213,6 +213,9 @@ module Aws
|
|
|
213
213
|
# Raised when SSO Token is invalid
|
|
214
214
|
class InvalidSSOToken < RuntimeError; end
|
|
215
215
|
|
|
216
|
+
# Raised when Login Token is invalid
|
|
217
|
+
class InvalidLoginToken < RuntimeError; end
|
|
218
|
+
|
|
216
219
|
# Raised when a client is unable to sign a request because
|
|
217
220
|
# the bearer token is not configured or available
|
|
218
221
|
class MissingBearerTokenError < RuntimeError
|
|
@@ -57,6 +57,9 @@ module Aws
|
|
|
57
57
|
|
|
58
58
|
# @param [Hash] options
|
|
59
59
|
# @option options [Integer] :retries (1) Number of times to retry when retrieving credentials.
|
|
60
|
+
# @option options [Numeric, Proc] :backoff By default, failures are retried with exponential back-off, i.e.
|
|
61
|
+
# `lambda { |num_failures| sleep(1.2 ** num_failures) }`. You can pass a number of seconds to sleep
|
|
62
|
+
# between failed attempts, or a Proc that accepts the number of failures.
|
|
60
63
|
# @option options [String] :endpoint ('http://169.254.169.254') The IMDS endpoint. This option has precedence
|
|
61
64
|
# over the `:endpoint_mode`.
|
|
62
65
|
# @option options [String] :endpoint_mode ('IPv4') The endpoint mode for the instance metadata service. This is
|
|
@@ -67,14 +70,11 @@ module Aws
|
|
|
67
70
|
# @option options [Integer] :port (80)
|
|
68
71
|
# @option options [Float] :http_open_timeout (1)
|
|
69
72
|
# @option options [Float] :http_read_timeout (1)
|
|
70
|
-
# @option options [Numeric, Proc] :delay By default, failures are retried with exponential back-off, i.e.
|
|
71
|
-
# `sleep(1.2 ** num_failures)`. You can pass a number of seconds to sleep between failed attempts, or a Proc
|
|
72
|
-
# that accepts the number of failures.
|
|
73
73
|
# @option options [IO] :http_debug_output (nil) HTTP wire traces are sent to this object.
|
|
74
74
|
# You can specify something like `$stdout`.
|
|
75
|
-
# @option options [Integer] :token_ttl Time-to-Live in seconds for EC2 Metadata Token used for fetching
|
|
76
|
-
# Metadata Profile Credentials
|
|
77
|
-
# @option options [
|
|
75
|
+
# @option options [Integer] :token_ttl (21600) Time-to-Live in seconds for EC2 Metadata Token used for fetching
|
|
76
|
+
# Metadata Profile Credentials.
|
|
77
|
+
# @option options [Proc] :before_refresh A Proc called before credentials are refreshed. `:before_refresh`
|
|
78
78
|
# is called with an instance of this object when AWS credentials are required and need to be refreshed.
|
|
79
79
|
def initialize(options = {})
|
|
80
80
|
@backoff = resolve_backoff(options[:backoff])
|
|
@@ -95,7 +95,7 @@ module Aws
|
|
|
95
95
|
super
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
# @return [
|
|
98
|
+
# @return [Boolean]
|
|
99
99
|
attr_reader :disable_imds_v1
|
|
100
100
|
|
|
101
101
|
# @return [Integer]
|
|
@@ -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
|