Skip to content

Commit 2bf61bf

Browse files
authored
Refresh AWS credentials asynchronously (#2642)
1 parent 16b4e18 commit 2bf61bf

File tree

12 files changed

+72
-16
lines changed

12 files changed

+72
-16
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ Specifically, here are a few things that we would appreciate help on:
5151
The AWS SDK for Ruby is unit tested using RSpec. You can run the unit tests of the SDK after cloning the repo:
5252

5353
bundle install
54-
bundle exec rake test
54+
bundle exec rake test:spec
55+
56+
If you want to run `PURE_RUBY` tests, then `export PURE_RUBY=1` into your environment. This will skip installing
57+
packages like `oj` for instance.
5558

5659
To run integration tests, create a `integration-test-config.json` file at the root of this repository. It should
5760
contain a `"region"` and credentials. Running rake test when this file is present will enable integration tests.

gems/aws-sdk-cognitoidentity/lib/aws-sdk-cognitoidentity/customizations/cognito_identity_credentials.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def initialize(options = {})
8585
@identity_id = options.delete(:identity_id)
8686
@custom_role_arn = options.delete(:custom_role_arn)
8787
@logins = options.delete(:logins) || {}
88+
@async_refresh = false
8889

8990
if !@identity_pool_id && !@identity_id
9091
raise ArgumentError,
@@ -113,6 +114,8 @@ def identity_id
113114
private
114115

115116
def refresh
117+
@before_refresh.call(self) if @before_refresh
118+
116119
resp = @client.get_credentials_for_identity(
117120
identity_id: identity_id,
118121
custom_role_arn: @custom_role_arn

gems/aws-sdk-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Asynchronously refresh AWS credentials (#2641).
5+
46
* Issue - Add x-amz-region-set to list of headers deleted for re-sign.
57

68
3.129.1 (2022-03-10)

gems/aws-sdk-core/lib/aws-sdk-core/assume_role_credentials.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def initialize(options = {})
5454
end
5555
end
5656
@client = client_opts[:client] || STS::Client.new(client_opts)
57+
@async_refresh = true
5758
super
5859
end
5960

gems/aws-sdk-core/lib/aws-sdk-core/assume_role_web_identity_credentials.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module Aws
1717
# ...
1818
# )
1919
# For full list of parameters accepted
20-
# @see Aws::STS::Client#assume_role_with_web_identity
20+
# @see Aws::STS::Client#assume_role_with_web_identity
2121
#
2222
#
2323
# If you omit `:client` option, a new {STS::Client} object will be
@@ -48,6 +48,7 @@ def initialize(options = {})
4848
client_opts = {}
4949
@assume_role_web_identity_params = {}
5050
@token_file = options.delete(:web_identity_token_file)
51+
@async_refresh = true
5152
options.each_pair do |key, value|
5253
if self.class.assume_role_web_identity_options.include?(key)
5354
@assume_role_web_identity_params[key] = value

gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def initialize options = {}
6262
@http_read_timeout = options[:http_read_timeout] || 5
6363
@http_debug_output = options[:http_debug_output]
6464
@backoff = backoff(options[:backoff])
65+
@async_refresh = false
6566
super
6667
end
6768

gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def initialize(options = {})
7979
@token_ttl = options[:token_ttl] || 21_600
8080
@token = nil
8181
@no_refresh_until = nil
82+
@async_refresh = false
8283
super
8384
end
8485

gems/aws-sdk-core/lib/aws-sdk-core/process_credentials.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ProcessCredentials
2727
def initialize(process)
2828
@process = process
2929
@credentials = credentials_from_process(@process)
30+
@async_refresh = false
3031

3132
super
3233
end
@@ -73,9 +74,9 @@ def refresh
7374
@credentials = credentials_from_process(@process)
7475
end
7576

76-
def near_expiration?
77+
def near_expiration?(expiration_length)
7778
# are we within 5 minutes of expiration?
78-
@expiration && (Time.now.to_i + 5 * 60) > @expiration.to_i
79+
@expiration && (Time.now.to_i + expiration_length) > @expiration.to_i
7980
end
8081
end
8182
end

gems/aws-sdk-core/lib/aws-sdk-core/refreshing_credentials.rb

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ module Aws
1717
# @api private
1818
module RefreshingCredentials
1919

20+
SYNC_EXPIRATION_LENGTH = 300 # 5 minutes
21+
ASYNC_EXPIRATION_LENGTH = 600 # 10 minutes
22+
2023
def initialize(options = {})
2124
@mutex = Mutex.new
2225
@before_refresh = options.delete(:before_refresh) if Hash === options
@@ -27,13 +30,13 @@ def initialize(options = {})
2730

2831
# @return [Credentials]
2932
def credentials
30-
refresh_if_near_expiration
33+
refresh_if_near_expiration!
3134
@credentials
3235
end
3336

3437
# @return [Time,nil]
3538
def expiration
36-
refresh_if_near_expiration
39+
refresh_if_near_expiration!
3740
@expiration
3841
end
3942

@@ -49,24 +52,39 @@ def refresh!
4952

5053
private
5154

52-
# Refreshes instance metadata credentials if they are within
53-
# 5 minutes of expiration.
54-
def refresh_if_near_expiration
55-
if near_expiration?
55+
# Refreshes credentials asynchronously and synchronously.
56+
# If we are near to expiration, block while getting new credentials.
57+
# Otherwise, if we're approaching expiration, use the existing credentials
58+
# but attempt a refresh in the background.
59+
def refresh_if_near_expiration!
60+
# Note: This check is an optimization. Rather than acquire the mutex on every #refresh_if_near_expiration
61+
# call, we check before doing so, and then we check within the mutex to avoid a race condition.
62+
# See issue: https://github.com/aws/aws-sdk-ruby/issues/2641 for more info.
63+
if near_expiration?(SYNC_EXPIRATION_LENGTH)
5664
@mutex.synchronize do
57-
if near_expiration?
65+
if near_expiration?(SYNC_EXPIRATION_LENGTH)
5866
@before_refresh.call(self) if @before_refresh
59-
6067
refresh
6168
end
6269
end
70+
elsif @async_refresh && near_expiration?(ASYNC_EXPIRATION_LENGTH)
71+
unless @mutex.locked?
72+
Thread.new do
73+
@mutex.synchronize do
74+
if near_expiration?(ASYNC_EXPIRATION_LENGTH)
75+
@before_refresh.call(self) if @before_refresh
76+
refresh
77+
end
78+
end
79+
end
80+
end
6381
end
6482
end
6583

66-
def near_expiration?
84+
def near_expiration?(expiration_length)
6785
if @expiration
68-
# are we within 5 minutes of expiration?
69-
(Time.now.to_i + 5 * 60) > @expiration.to_i
86+
# Are we within expiration?
87+
(Time.now.to_i + expiration_length) > @expiration.to_i
7088
else
7189
true
7290
end

gems/aws-sdk-core/lib/aws-sdk-core/sso_credentials.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def initialize(options = {})
8686
options[:region] = @sso_region
8787
options[:credentials] = nil
8888
@client = options[:client] || Aws::SSO::Client.new(options)
89+
@async_refresh = true
8990
super
9091
end
9192

0 commit comments

Comments
 (0)