Skip to content

Commit 402aed8

Browse files
authored
Adds account ID support for profile credentials provider sources (#4340)
1 parent aef0247 commit 402aed8

File tree

8 files changed

+305
-106
lines changed

8 files changed

+305
-106
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public final class ProcessCredentialsProvider
7171
private final List<String> executableCommand;
7272
private final Duration credentialRefreshThreshold;
7373
private final long processOutputLimit;
74+
private final String staticAccountId;
7475

7576
private final CachedSupplier<AwsCredentials> processCredentialCache;
7677

@@ -101,6 +102,7 @@ private ProcessCredentialsProvider(Builder builder) {
101102
this.credentialRefreshThreshold = Validate.isPositive(builder.credentialRefreshThreshold, "expirationBuffer");
102103
this.commandFromBuilder = builder.command;
103104
this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled;
105+
this.staticAccountId = builder.staticAccountId;
104106

105107
CachedSupplier.Builder<AwsCredentials> cacheBuilder = CachedSupplier.builder(this::refreshCredentials)
106108
.cachedValueName(toString());
@@ -170,19 +172,21 @@ private AwsCredentials credentials(JsonNode credentialsJson) {
170172
Validate.notEmpty(accessKeyId, "AccessKeyId cannot be empty.");
171173
Validate.notEmpty(secretAccessKey, "SecretAccessKey cannot be empty.");
172174

175+
String resolvedAccountId = accountId == null ? this.staticAccountId : accountId;
176+
173177
if (sessionToken != null) {
174178
return AwsSessionCredentials.builder()
175179
.accessKeyId(accessKeyId)
176180
.secretAccessKey(secretAccessKey)
177181
.sessionToken(sessionToken)
178182
.expirationTime(credentialExpirationTime(credentialsJson))
179-
.accountId(accountId)
183+
.accountId(resolvedAccountId)
180184
.build();
181185
}
182186
return AwsBasicCredentials.builder()
183187
.accessKeyId(accessKeyId)
184188
.secretAccessKey(secretAccessKey)
185-
.accountId(accountId)
189+
.accountId(resolvedAccountId)
186190
.build();
187191
}
188192

@@ -247,6 +251,7 @@ public static class Builder implements CopyableBuilder<Builder, ProcessCredentia
247251
private String command;
248252
private Duration credentialRefreshThreshold = Duration.ofSeconds(15);
249253
private long processOutputLimit = 64000;
254+
private String staticAccountId;
250255

251256
/**
252257
* @see #builder()
@@ -259,6 +264,7 @@ private Builder(ProcessCredentialsProvider provider) {
259264
this.command = provider.commandFromBuilder;
260265
this.credentialRefreshThreshold = provider.credentialRefreshThreshold;
261266
this.processOutputLimit = provider.processOutputLimit;
267+
this.staticAccountId = provider.staticAccountId;
262268
}
263269

264270
/**
@@ -304,6 +310,19 @@ public Builder processOutputLimit(long outputByteLimit) {
304310
return this;
305311
}
306312

313+
/**
314+
* Configure a static account id for this credentials provider. Account id for ProcessCredentialsProvider is only
315+
* relevant in a context where a service constructs endpoint URL containing an account id.
316+
* This option should ONLY be used if the provider should return credentials with account id, and the process does not
317+
* output account id. If a static account ID is configured, and the process also returns an account
318+
* id, the process output value overrides the static value. If used, the static account id MUST match the credentials
319+
* returned by the process.
320+
*/
321+
public Builder staticAccountId(String staticAccountId) {
322+
this.staticAccountId = staticAccountId;
323+
return this;
324+
}
325+
307326
public ProcessCredentialsProvider build() {
308327
return new ProcessCredentialsProvider(this);
309328
}

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,11 @@ private Optional<AwsCredentialsProvider> credentialsProvider(Set<String> childre
157157
private AwsCredentialsProvider basicProfileCredentialsProvider() {
158158
requireProperties(ProfileProperty.AWS_ACCESS_KEY_ID,
159159
ProfileProperty.AWS_SECRET_ACCESS_KEY);
160-
AwsCredentials credentials = AwsBasicCredentials.create(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID),
161-
properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY));
160+
AwsCredentials credentials = AwsBasicCredentials.builder()
161+
.accessKeyId(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID))
162+
.secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY))
163+
.accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID))
164+
.build();
162165
return StaticCredentialsProvider.create(credentials);
163166
}
164167

@@ -169,9 +172,12 @@ private AwsCredentialsProvider sessionProfileCredentialsProvider() {
169172
requireProperties(ProfileProperty.AWS_ACCESS_KEY_ID,
170173
ProfileProperty.AWS_SECRET_ACCESS_KEY,
171174
ProfileProperty.AWS_SESSION_TOKEN);
172-
AwsCredentials credentials = AwsSessionCredentials.create(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID),
173-
properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY),
174-
properties.get(ProfileProperty.AWS_SESSION_TOKEN));
175+
AwsCredentials credentials = AwsSessionCredentials.builder()
176+
.accessKeyId(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID))
177+
.secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY))
178+
.sessionToken(properties.get(ProfileProperty.AWS_SESSION_TOKEN))
179+
.accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID))
180+
.build();
175181
return StaticCredentialsProvider.create(credentials);
176182
}
177183

@@ -180,6 +186,7 @@ private AwsCredentialsProvider credentialProcessCredentialsProvider() {
180186

181187
return ProcessCredentialsProvider.builder()
182188
.command(properties.get(ProfileProperty.CREDENTIAL_PROCESS))
189+
.staticAccountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID))
183190
.build();
184191
}
185192

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

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
import java.time.Duration;
2828
import java.time.Instant;
2929
import java.time.temporal.ChronoUnit;
30+
import java.util.Arrays;
31+
import java.util.List;
3032
import java.util.Optional;
3133
import org.junit.jupiter.api.AfterAll;
3234
import org.junit.jupiter.api.BeforeAll;
3335
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.params.ParameterizedTest;
37+
import org.junit.jupiter.params.provider.Arguments;
38+
import org.junit.jupiter.params.provider.MethodSource;
3439
import software.amazon.awssdk.utils.DateUtils;
3540
import software.amazon.awssdk.utils.IoUtils;
3641
import software.amazon.awssdk.utils.Platform;
@@ -64,36 +69,40 @@ static void teardown() {
6469
throw new IllegalStateException("Failed to delete file: " + errorScriptLocation);
6570
}
6671
}
67-
68-
@Test
69-
void staticCredentialsCanBeLoaded() {
70-
AwsCredentials credentials =
71-
ProcessCredentialsProvider.builder()
72-
.command(String.format("%s accessKeyId secretAccessKey", scriptLocation))
73-
.build()
74-
.resolveCredentials();
75-
76-
assertThat(credentials).isNotInstanceOf(AwsSessionCredentials.class);
77-
assertThat(credentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID);
78-
assertThat(credentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY);
79-
assertThat(credentials.accountId()).isNotPresent();
80-
}
8172

82-
@Test
83-
void staticCredentialsWithAccountIdCanBeLoaded() {
84-
AwsCredentials credentials =
85-
ProcessCredentialsProvider.builder()
86-
.command(String.format("%s %s %s acctid=%s",
87-
scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ACCOUNT_ID))
88-
.build()
89-
.resolveCredentials();
73+
@ParameterizedTest(name = "{index} - {0}")
74+
@MethodSource("staticCredentialsValues")
75+
void staticCredentialsCanBeLoaded(String description, String staticAccountId, Optional<String> expectedValue,
76+
String cmd) {
77+
ProcessCredentialsProvider.Builder providerBuilder = ProcessCredentialsProvider.builder().command(cmd);
78+
if (staticAccountId != null) {
79+
providerBuilder.staticAccountId(staticAccountId);
80+
}
81+
AwsCredentials credentials = providerBuilder.build().resolveCredentials();
9082

83+
verifyCredentials(credentials);
9184
assertThat(credentials).isNotInstanceOf(AwsSessionCredentials.class);
92-
assertThat(credentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID);
93-
assertThat(credentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY);
94-
assertThat(credentials.accountId()).isPresent().isEqualTo(Optional.of(ACCOUNT_ID));
85+
86+
if (expectedValue.isPresent()) {
87+
assertThat(credentials.accountId()).isPresent().hasValue(expectedValue.get());
88+
} else {
89+
assertThat(credentials.accountId()).isNotPresent();
90+
}
9591
}
96-
92+
93+
private static List<Arguments> staticCredentialsValues() {
94+
return Arrays.asList(
95+
Arguments.of("when only containing access key id, secret", null, Optional.empty(),
96+
String.format("%s accessKeyId secretAccessKey", scriptLocation)),
97+
Arguments.of("when output has account id", null, Optional.of(ACCOUNT_ID),
98+
String.format("%s %s %s acctid=%s", scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ACCOUNT_ID)),
99+
Arguments.of("when output has account id, static account id configured", "staticAccountId", Optional.of(ACCOUNT_ID),
100+
String.format("%s %s %s acctid=%s", scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY, ACCOUNT_ID)),
101+
Arguments.of("when only static account id is configured", "staticAccountId", Optional.of("staticAccountId"),
102+
String.format("%s %s %s", scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY))
103+
);
104+
}
105+
97106
@Test
98107
void sessionCredentialsCanBeLoaded() {
99108
String expiration = DateUtils.formatIso8601Date(Instant.now());
@@ -122,21 +131,42 @@ void sessionCredentialsWithAccountIdCanBeLoaded() {
122131

123132
AwsCredentials credentials = credentialsProvider.resolveCredentials();
124133
verifySessionCredentials(credentials, expiration);
125-
assertThat(credentials.accountId()).isPresent().isEqualTo(Optional.of(ACCOUNT_ID));
134+
assertThat(credentials.accountId()).isPresent().hasValue(ACCOUNT_ID);
135+
}
136+
137+
@Test
138+
void sessionCredentialsWithStaticAccountIdCanBeLoaded() {
139+
String expiration = DateUtils.formatIso8601Date(Instant.now());
140+
ProcessCredentialsProvider credentialsProvider =
141+
ProcessCredentialsProvider.builder()
142+
.command(String.format("%s %s %s token=sessionToken exp=%s",
143+
scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY, expiration))
144+
.credentialRefreshThreshold(Duration.ofSeconds(1))
145+
.staticAccountId("staticAccountId")
146+
.build();
147+
148+
AwsCredentials credentials = credentialsProvider.resolveCredentials();
149+
verifySessionCredentials(credentials, expiration);
150+
assertThat(credentials.accountId()).isPresent().hasValue("staticAccountId");
126151
}
127152

128153
private void verifySessionCredentials(AwsCredentials credentials, String expiration) {
154+
verifyCredentials(credentials);
155+
129156
assertThat(credentials).isInstanceOf(AwsSessionCredentials.class);
130157
AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials;
131-
132-
assertThat(sessionCredentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID);
133-
assertThat(sessionCredentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY);
134158
assertThat(sessionCredentials.sessionToken()).isEqualTo(SESSION_TOKEN);
135-
assertThat(sessionCredentials.expirationTime()).isPresent();
136-
Instant exp = sessionCredentials.expirationTime().get();
159+
160+
assertThat(credentials.expirationTime()).isPresent();
161+
Instant exp = credentials.expirationTime().get();
137162
assertThat(exp).isCloseTo(expiration, within(1, ChronoUnit.MICROS));
138163
}
139164

165+
private void verifyCredentials(AwsCredentials credentials) {
166+
assertThat(credentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID);
167+
assertThat(credentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY);
168+
}
169+
140170
@Test
141171
void resultsAreCached() {
142172
ProcessCredentialsProvider credentialsProvider =

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.FileSystem;
2525
import java.nio.file.Files;
2626
import java.nio.file.Path;
27+
import java.util.Optional;
2728
import java.util.function.Supplier;
2829
import org.junit.jupiter.api.AfterAll;
2930
import org.junit.jupiter.api.BeforeAll;
@@ -114,6 +115,24 @@ void presentProfileReturnsCredentials() {
114115
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
115116
assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey");
116117
assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey");
118+
assertThat(credentials.accountId()).isNotPresent();
119+
});
120+
}
121+
122+
@Test
123+
void presentProfileWithAccountIdReturnsCredentialsWithAccountId() {
124+
ProfileFile file = profileFile("[default]\n"
125+
+ "aws_access_key_id = defaultAccessKey\n"
126+
+ "aws_secret_access_key = defaultSecretAccessKey\n"
127+
+ "aws_account_id = defaultAccountId");
128+
129+
ProfileCredentialsProvider provider =
130+
ProfileCredentialsProvider.builder().profileFile(file).profileName("default").build();
131+
132+
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
133+
assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey");
134+
assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey");
135+
assertThat(credentials.accountId()).isPresent().isEqualTo(Optional.of("defaultAccountId"));
117136
});
118137
}
119138

@@ -201,13 +220,15 @@ void resolveCredentials_presentProfileFileSupplier_returnsCredentials() {
201220
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
202221
assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey");
203222
assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey");
223+
assertThat(credentials.accountId()).isNotPresent();
204224
});
205225
}
206226

207227
@Test
208228
void resolveCredentials_presentSupplierProfileFile_returnsCredentials() {
209229
Supplier<ProfileFile> supplier = () -> profileFile("[default]\naws_access_key_id = defaultAccessKey\n"
210-
+ "aws_secret_access_key = defaultSecretAccessKey\n");
230+
+ "aws_secret_access_key = defaultSecretAccessKey\n"
231+
+ "aws_account_id = defaultAccountId");
211232

212233
ProfileCredentialsProvider provider =
213234
ProfileCredentialsProvider.builder()
@@ -218,6 +239,7 @@ void resolveCredentials_presentSupplierProfileFile_returnsCredentials() {
218239
assertThat(provider.resolveCredentials()).satisfies(credentials -> {
219240
assertThat(credentials.accessKeyId()).isEqualTo("defaultAccessKey");
220241
assertThat(credentials.secretAccessKey()).isEqualTo("defaultSecretAccessKey");
242+
assertThat(credentials.accountId()).isPresent();
221243
});
222244
}
223245

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

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,52 @@
1515

1616
package software.amazon.awssdk.auth.credentials;
1717

18-
import static org.junit.Assert.assertEquals;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920

20-
import org.junit.Test;
21+
import org.junit.jupiter.api.Test;
2122

2223
public class StaticCredentialsProviderTest {
24+
25+
@Test
26+
void getAwsCredentials_ReturnsSameCredentials() {
27+
AwsCredentials credentials = AwsBasicCredentials.create("akid", "skid");
28+
AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials();
29+
assertThat(actualCredentials).isEqualTo(credentials);
30+
}
31+
2332
@Test
24-
public void getAwsCredentials_ReturnsSameCredentials() throws Exception {
25-
final AwsCredentials credentials = AwsBasicCredentials.create("akid", "skid");
26-
final AwsCredentials actualCredentials =
27-
StaticCredentialsProvider.create(credentials).resolveCredentials();
28-
assertEquals(credentials, actualCredentials);
33+
void getAwsCredentialsWithAccountId_ReturnsSameCredentials() {
34+
AwsCredentials credentials = AwsBasicCredentials.builder()
35+
.accessKeyId("akid")
36+
.secretAccessKey("skid")
37+
.accountId("acctid")
38+
.build();
39+
AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials();
40+
assertThat(actualCredentials).isEqualTo(credentials);
2941
}
3042

3143
@Test
32-
public void getSessionAwsCredentials_ReturnsSameCredentials() throws Exception {
33-
final AwsSessionCredentials credentials = AwsSessionCredentials.create("akid", "skid", "token");
34-
final AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials();
35-
assertEquals(credentials, actualCredentials);
44+
void getSessionAwsCredentials_ReturnsSameCredentials() {
45+
AwsSessionCredentials credentials = AwsSessionCredentials.create("akid", "skid", "token");
46+
AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials();
47+
assertThat(actualCredentials).isEqualTo(credentials);
3648
}
3749

38-
@Test(expected = RuntimeException.class)
39-
public void nullCredentials_ThrowsIllegalArgumentException() {
40-
StaticCredentialsProvider.create(null);
50+
@Test
51+
void getSessionAwsCredentialsWithAccountId_ReturnsSameCredentials() {
52+
AwsSessionCredentials credentials = AwsSessionCredentials.builder()
53+
.accessKeyId("akid")
54+
.secretAccessKey("skid")
55+
.sessionToken("token")
56+
.accountId("acctid")
57+
.build();
58+
AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials();
59+
assertThat(actualCredentials).isEqualTo(credentials);
60+
}
61+
62+
@Test
63+
void nullCredentials_ThrowsException() {
64+
assertThatThrownBy(() -> StaticCredentialsProvider.create(null)).isInstanceOf(NullPointerException.class);
4165
}
4266
}

0 commit comments

Comments
 (0)