Skip to content

Commit 25d82cd

Browse files
committed
Adding ec2InstanceProfileName configuration to specify IMDS instance profile
1 parent edfe8d5 commit 25d82cd

File tree

6 files changed

+398
-8
lines changed

6 files changed

+398
-8
lines changed

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

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static java.time.temporal.ChronoUnit.MINUTES;
1919
import static software.amazon.awssdk.utils.ComparableUtils.maximum;
2020
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
21+
import static software.amazon.awssdk.utils.StringUtils.isBlank;
2122
import static software.amazon.awssdk.utils.cache.CachedSupplier.StaleValueBehavior.ALLOW;
2223

2324
import java.net.URI;
@@ -103,6 +104,8 @@ private enum ApiVersion {
103104
private final Supplier<ProfileFile> profileFile;
104105

105106
private final String profileName;
107+
108+
private final String ec2InstanceProfileName;
106109

107110
private final Duration staleTime;
108111

@@ -118,6 +121,13 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {
118121
.orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile()));
119122
this.profileName = Optional.ofNullable(builder.profileName)
120123
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
124+
this.ec2InstanceProfileName = builder.ec2InstanceProfileName;
125+
126+
if (isBlank(ec2InstanceProfileName) && ec2InstanceProfileName != null) {
127+
throw SdkClientException.builder()
128+
.message("ec2InstanceProfileName cannot be blank")
129+
.build();
130+
}
121131

122132
this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME);
123133
this.configProvider =
@@ -173,6 +183,11 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
173183

174184
try {
175185
LoadedCredentials credentials = httpCredentialsLoader.loadCredentials(createEndpointProvider());
186+
187+
if (apiVersion == ApiVersion.UNKNOWN) {
188+
apiVersion = ApiVersion.EXTENDED;
189+
}
190+
176191
Instant expiration = credentials.getExpiration().orElse(null);
177192
log.debug(() -> "Loaded credentials from IMDS with expiration time of " + expiration);
178193

@@ -186,19 +201,25 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
186201
} catch (Ec2MetadataClientException e) {
187202
if (e.statusCode() == 404) {
188203
log.debug(() -> "Resolved profile is no longer available. Resetting it and trying again.");
189-
resolvedProfile = null;
190204

191205
if (apiVersion == ApiVersion.UNKNOWN) {
192206
apiVersion = ApiVersion.LEGACY;
193207
return refreshCredentials();
208+
} else if (ec2InstanceProfileName == null && configProvider.ec2InstanceProfileName() == null) {
209+
// Resolved profile name is invalid, reset it and try again
210+
resolvedProfile = null;
211+
212+
profileRetryCount++;
213+
if (profileRetryCount <= MAX_PROFILE_RETRIES) {
214+
log.debug(() -> "Profile name not found, retrying fetching the profile name again.");
215+
return refreshCredentials();
216+
}
217+
} else {
218+
throw SdkClientException.builder()
219+
.message("Invalid profile name")
220+
.cause(e)
221+
.build();
194222
}
195-
196-
profileRetryCount++;
197-
if (profileRetryCount <= MAX_PROFILE_RETRIES) {
198-
log.debug(() -> "Profile name not found, retrying fetching the profile name again.");
199-
return refreshCredentials();
200-
}
201-
throw SdkClientException.create(FAILED_TO_LOAD_CREDENTIALS_ERROR, e);
202223
}
203224
throw SdkClientException.create(FAILED_TO_LOAD_CREDENTIALS_ERROR, e);
204225
} catch (RuntimeException e) {
@@ -328,6 +349,15 @@ private boolean isInsecureFallbackDisabled() {
328349
}
329350

330351
private String[] getSecurityCredentials(String imdsHostname, String metadataToken) {
352+
if (ec2InstanceProfileName != null) {
353+
return new String[]{ec2InstanceProfileName};
354+
}
355+
356+
String configuredProfileName = this.configProvider.ec2InstanceProfileName();
357+
if (configuredProfileName != null) {
358+
return new String[]{configuredProfileName};
359+
}
360+
331361
if (resolvedProfile != null) {
332362
return new String[]{resolvedProfile};
333363
}
@@ -383,6 +413,19 @@ public Builder toBuilder() {
383413
*/
384414
public interface Builder extends HttpCredentialsProvider.Builder<InstanceProfileCredentialsProvider, Builder>,
385415
CopyableBuilder<Builder, InstanceProfileCredentialsProvider> {
416+
/**
417+
* Configure the EC2 instance profile name to use for retrieving credentials.
418+
*
419+
* <p>When this is set, the provider will skip fetching the list of available instance profiles
420+
* and use this name directly. This can improve performance by reducing the number of calls to IMDS.
421+
*
422+
* <p>By default, this is not set and the provider will discover the instance profile name from IMDS.
423+
*
424+
* @param ec2InstanceProfileName The EC2 instance profile name to use
425+
* @return This builder for method chaining
426+
*/
427+
Builder ec2InstanceProfileName(String ec2InstanceProfileName);
428+
386429
/**
387430
* Configure the profile file used for loading IMDS-related configuration, like the endpoint mode (IPv4 vs IPv6).
388431
*
@@ -434,6 +477,7 @@ static final class BuilderImpl implements Builder {
434477
private String asyncThreadName;
435478
private Supplier<ProfileFile> profileFile;
436479
private String profileName;
480+
private String ec2InstanceProfileName;
437481
private Duration staleTime;
438482

439483
private BuilderImpl() {
@@ -447,6 +491,7 @@ private BuilderImpl(InstanceProfileCredentialsProvider provider) {
447491
this.asyncThreadName = provider.asyncThreadName;
448492
this.profileFile = provider.profileFile;
449493
this.profileName = provider.profileName;
494+
this.ec2InstanceProfileName = provider.ec2InstanceProfileName;
450495
this.staleTime = provider.staleTime;
451496
}
452497

@@ -515,6 +560,17 @@ public Builder profileName(String profileName) {
515560
public void setProfileName(String profileName) {
516561
profileName(profileName);
517562
}
563+
564+
@Override
565+
public Builder ec2InstanceProfileName(String ec2InstanceProfileName) {
566+
this.ec2InstanceProfileName = ec2InstanceProfileName;
567+
return this;
568+
}
569+
570+
public void setEc2InstanceProfileName(String ec2InstanceProfileName) {
571+
ec2InstanceProfileName(ec2InstanceProfileName);
572+
}
573+
518574

519575
@Override
520576
public Builder staleTime(Duration duration) {

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ public final class Ec2MetadataConfigProvider {
4646

4747
private final Lazy<Boolean> metadataV1Disabled;
4848
private final Lazy<Long> serviceTimeout;
49+
private final Lazy<String> ec2InstanceProfileName;
4950

5051
private Ec2MetadataConfigProvider(Builder builder) {
5152
this.profileFile = builder.profileFile;
5253
this.profileName = builder.profileName;
5354
this.metadataV1Disabled = new Lazy<>(this::resolveMetadataV1Disabled);
5455
this.serviceTimeout = new Lazy<>(this::resolveServiceTimeout);
56+
this.ec2InstanceProfileName = new Lazy<>(this::resolveEc2InstanceProfileName);
5557
}
5658

5759
public enum EndpointMode {
@@ -127,6 +129,14 @@ public boolean isMetadataV1Disabled() {
127129
public long serviceTimeout() {
128130
return serviceTimeout.getValue();
129131
}
132+
133+
/**
134+
* Resolves the EC2 Instance Profile Name to use.
135+
* @return the EC2 Instance Profile Name or null if not specified.
136+
*/
137+
public String ec2InstanceProfileName() {
138+
return ec2InstanceProfileName.getValue();
139+
}
130140

131141
// Internal resolution logic for Metadata V1 disabled
132142
private boolean resolveMetadataV1Disabled() {
@@ -146,6 +156,14 @@ private long resolveServiceTimeout() {
146156
.orElseGet(() -> parseTimeoutValue(SdkSystemSetting.AWS_METADATA_SERVICE_TIMEOUT.defaultValue()));
147157
}
148158

159+
private String resolveEc2InstanceProfileName() {
160+
return OptionalUtils.firstPresent(
161+
fromSystemSettingsEc2InstanceProfileName(),
162+
() -> fromProfileFileEc2InstanceProfileName(profileFile, profileName)
163+
)
164+
.orElse(null);
165+
}
166+
149167
// System settings resolution for Metadata V1 disabled
150168
private static Optional<Boolean> fromSystemSettingsMetadataV1Disabled() {
151169
return SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.getBooleanValue();
@@ -171,6 +189,22 @@ private static Optional<Long> fromProfileFileServiceTimeout(Supplier<ProfileFile
171189
.flatMap(p -> p.property(ProfileProperty.METADATA_SERVICE_TIMEOUT))
172190
.map(Ec2MetadataConfigProvider::parseTimeoutValue);
173191
}
192+
193+
// System settings resolution for EC2 Instance Profile Name
194+
private static Optional<String> fromSystemSettingsEc2InstanceProfileName() {
195+
return SdkSystemSetting.AWS_EC2_INSTANCE_PROFILE_NAME.getNonDefaultStringValue();
196+
}
197+
198+
// Profile file resolution for EC2 Instance Profile Name
199+
private static Optional<String> fromProfileFileEc2InstanceProfileName(Supplier<ProfileFile> profileFile, String profileName) {
200+
try {
201+
return profileFile.get()
202+
.profile(profileName)
203+
.flatMap(p -> p.property(ProfileProperty.EC2_INSTANCE_PROFILE_NAME));
204+
} catch (Exception e) {
205+
return Optional.empty();
206+
}
207+
}
174208

175209
// Parses a timeout value from a string to milliseconds
176210
private static long parseTimeoutValue(String timeoutValue) {

0 commit comments

Comments
 (0)