Skip to content

Commit 0968af7

Browse files
committed
Adding retry counter to the fallback logic
1 parent f41795b commit 0968af7

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,12 @@ private enum ApiVersion {
8282

8383
private static final String EC2_METADATA_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
8484
private static final String DEFAULT_TOKEN_TTL = "21600";
85+
private static final int MAX_PROFILE_RETRIES = 3;
8586

8687
// These fields are accessed from methods called by CachedSupplier which provides thread safety through its ReentrantLock
8788
private ApiVersion apiVersion = ApiVersion.UNKNOWN;
8889
private String resolvedProfile = null;
90+
private int profileRetryCount = 0;
8991

9092
private final Clock clock;
9193
private final String endpoint;
@@ -173,6 +175,8 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
173175
Instant expiration = credentials.getExpiration().orElse(null);
174176
log.debug(() -> "Loaded credentials from IMDS with expiration time of " + expiration);
175177

178+
profileRetryCount = 0;
179+
176180
return RefreshResult.builder(credentials.getAwsCredentials())
177181
.staleTime(staleTime(expiration))
178182
.prefetchTime(prefetchTime(expiration))
@@ -181,6 +185,10 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
181185
if (e.statusCode() == 404) {
182186
log.debug(() -> "Resolved profile is no longer available. Resetting it and trying again.");
183187
resolvedProfile = null;
188+
profileRetryCount++;
189+
if (profileRetryCount > MAX_PROFILE_RETRIES) {
190+
throw SdkClientException.create("Failed to load credentials from IMDS.", e);
191+
}
184192
return refreshCredentials();
185193
}
186194
throw SdkClientException.create("Failed to load credentials from IMDS.", e);

core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderExtendedApiTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
2727
import static org.assertj.core.api.Assertions.assertThat;
2828
import static org.assertj.core.api.Assertions.assertThatThrownBy;
29+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2930

3031
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
3132
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
@@ -212,6 +213,82 @@ void resolveCredentials_withNon404ErrorOnProfileDiscovery_doesNotFallbackToLegac
212213
verify(0, getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + PROFILE_NAME)));
213214
}
214215

216+
@Test
217+
void resolveCredentials_withUnstableProfile_ReturnsCredentials() {
218+
String initialProfile = "my-profile-0007";
219+
String newProfile = "my-profile-0007-b";
220+
String credentialsJson = String.format(
221+
"{\"Code\":\"Success\"," +
222+
"\"LastUpdated\":\"2025-03-18T20:53:17.832308Z\"," +
223+
"\"Type\":\"AWS-HMAC\"," +
224+
"\"AccessKeyId\":\"ASIAIOSFODNN7EXAMPLE\"," +
225+
"\"SecretAccessKey\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"," +
226+
"\"Token\":\"AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKw...\"," +
227+
"\"Expiration\":\"2025-03-18T21:53:17.832308Z\"," +
228+
"\"UnexpectedElement7\":{\"Name\":\"ignore-me-7\"}}");
229+
230+
wireMockServer.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH))
231+
.willReturn(aResponse().withBody(TOKEN_STUB)));
232+
233+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH))
234+
.inScenario("unstable-profile")
235+
.whenScenarioStateIs("Started")
236+
.willReturn(aResponse().withBody(initialProfile))
237+
.willSetStateTo("initial-profile-discovered"));
238+
239+
240+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + initialProfile))
241+
.inScenario("unstable-profile")
242+
.whenScenarioStateIs("initial-profile-discovered")
243+
.willReturn(aResponse().withStatus(404))
244+
.willSetStateTo("profile-not-found"));
245+
246+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH))
247+
.inScenario("unstable-profile")
248+
.whenScenarioStateIs("profile-not-found")
249+
.willReturn(aResponse().withBody(newProfile))
250+
.willSetStateTo("new-profile-discovered"));
251+
252+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + newProfile))
253+
.inScenario("unstable-profile")
254+
.whenScenarioStateIs("new-profile-discovered")
255+
.willReturn(aResponse().withBody(credentialsJson)));
256+
257+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
258+
259+
AwsCredentials creds1 = provider.resolveCredentials();
260+
assertThat(creds1.accessKeyId()).isEqualTo("ASIAIOSFODNN7EXAMPLE");
261+
262+
AwsCredentials creds2 = assertDoesNotThrow(() -> provider.resolveCredentials());
263+
assertThat(creds2.accessKeyId()).isEqualTo("ASIAIOSFODNN7EXAMPLE");
264+
265+
verify(1, getRequestedFor(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + initialProfile)));
266+
verify(1, getRequestedFor(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + newProfile)));
267+
}
268+
269+
@Test
270+
void resolveCredentials_withTooManyProfileFailures_throwsException() {
271+
String profile = "unstable-profile";
272+
273+
wireMockServer.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH))
274+
.willReturn(aResponse().withBody(TOKEN_STUB)));
275+
276+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH))
277+
.willReturn(aResponse().withBody(profile)));
278+
279+
wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + profile))
280+
.willReturn(aResponse().withStatus(404)));
281+
282+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
283+
284+
assertThatThrownBy(() -> provider.resolveCredentials())
285+
.isInstanceOf(SdkClientException.class)
286+
.hasMessageContaining("Failed to load credentials from IMDS.");
287+
288+
verify(4, getRequestedFor(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH)));
289+
verify(4, getRequestedFor(urlPathEqualTo(CREDENTIALS_EXTENDED_RESOURCE_PATH + profile)));
290+
}
291+
215292
private void stubSecureCredentialsResponse(com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder responseDefinitionBuilder, boolean useExtended) {
216293
wireMockServer.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)).willReturn(aResponse().withBody(TOKEN_STUB)));
217294
String path = useExtended ? CREDENTIALS_EXTENDED_RESOURCE_PATH : CREDENTIALS_RESOURCE_PATH;

0 commit comments

Comments
 (0)