Skip to content

Commit d8b2fcd

Browse files
eduanbMatejNedicmaciejwalkowiak
authored
Autoconfigure StsWebIdentityTokenFileCredentialsProvider (#691)
Relates to #666 Co-authored-by: matej nedic <[email protected]> Co-authored-by: Maciej Walkowiak <[email protected]>
1 parent 557fa6b commit d8b2fcd

File tree

10 files changed

+314
-22
lines changed

10 files changed

+314
-22
lines changed

docs/src/main/asciidoc/_configprops.adoc

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
|spring.cloud.aws.cloudwatch.endpoint | | Overrides the default endpoint.
55
|spring.cloud.aws.cloudwatch.region | | Overrides the default region.
66
|spring.cloud.aws.credentials.access-key | | The access key to be used with a static provider.
7-
|spring.cloud.aws.credentials.instance-profile | `+++false+++` | Configures an instance profile credentials provider with no further configuration.
7+
|spring.cloud.aws.credentials.instance-profile | | Configures an instance profile credentials provider with no further configuration.
88
|spring.cloud.aws.credentials.profile | | The AWS profile.
99
|spring.cloud.aws.credentials.secret-key | | The secret key to be used with a static provider.
10+
|spring.cloud.aws.credentials.sts.async-credentials-update | |
11+
|spring.cloud.aws.credentials.sts.role-arn | | ARN of IAM role associated with STS. If not provided this will be read from {@link software.amazon.awssdk.core.SdkSystemSetting}.
12+
|spring.cloud.aws.credentials.sts.role-session-name | | Role session name that will be used by credentials provider. By default this is read from {@link software.amazon.awssdk.core.SdkSystemSetting}.
13+
|spring.cloud.aws.credentials.sts.web-identity-token-file | | Absolute path to the web identity token file that will be used by credentials provider. By default this will be read from {@link software.amazon.awssdk.core.SdkSystemSetting}.
1014
|spring.cloud.aws.defaults-mode | | Sets the {@link DefaultsMode} that will be used to determine how certain default configuration options are resolved in the SDK. <a href= "https://aws.amazon.com/blogs/developer/introducing-smart-configuration-defaults-in-the-aws-sdk-for-java-v2/">Introducing Smart Configuration Defaults in the AWS SDK for Java v2</a>
1115
|spring.cloud.aws.dualstack-enabled | | Configure whether the SDK should use the AWS dualstack endpoint.
1216
|spring.cloud.aws.dynamodb.dax.cluster-update-interval-millis | | Interval between polling of cluster members for membership changes.
@@ -29,10 +33,10 @@
2933
|spring.cloud.aws.fips-enabled | | Configure whether the SDK should use the AWS fips endpoints.
3034
|spring.cloud.aws.parameterstore.endpoint | | Overrides the default endpoint.
3135
|spring.cloud.aws.parameterstore.region | | Overrides the default region.
32-
|spring.cloud.aws.parameterstore.reload.max-wait-for-restart | `+++2s+++` | If {@link ReloadStrategy#RESTART_CONTEXT} is configured, maximum waiting time for server restart.
33-
|spring.cloud.aws.parameterstore.reload.period | `+++1m+++` | Refresh period for {@link PollingAwsPropertySourceChangeDetector}.
36+
|spring.cloud.aws.parameterstore.reload.max-wait-for-restart | | If {@link ReloadStrategy#RESTART_CONTEXT} is configured, maximum waiting time for server restart.
37+
|spring.cloud.aws.parameterstore.reload.period | | Refresh period for {@link PollingAwsPropertySourceChangeDetector}.
3438
|spring.cloud.aws.parameterstore.reload.strategy | | Reload strategy to run when properties change.
35-
|spring.cloud.aws.region.instance-profile | `+++false+++` | Configures an instance profile region provider with no further configuration.
39+
|spring.cloud.aws.region.instance-profile | | Configures an instance profile region provider with no further configuration.
3640
|spring.cloud.aws.region.profile | | The AWS profile.
3741
|spring.cloud.aws.region.static | |
3842
|spring.cloud.aws.s3.accelerate-mode-enabled | | Option to enable using the accelerate endpoint when accessing S3. Accelerate endpoints allow faster transfer of objects by using Amazon CloudFront's globally distributed edge locations.
@@ -51,8 +55,8 @@
5155
|spring.cloud.aws.s3.use-arn-region-enabled | | If an S3 resource ARN is passed in as the target of an S3 operation that has a different region to the one the client was configured with, this flag must be set to 'true' to permit the client to make a cross-region call to the region specified in the ARN otherwise an exception will be thrown.
5256
|spring.cloud.aws.secretsmanager.endpoint | | Overrides the default endpoint.
5357
|spring.cloud.aws.secretsmanager.region | | Overrides the default region.
54-
|spring.cloud.aws.secretsmanager.reload.max-wait-for-restart | `+++2s+++` | If {@link ReloadStrategy#RESTART_CONTEXT} is configured, maximum waiting time for server restart.
55-
|spring.cloud.aws.secretsmanager.reload.period | `+++1m+++` | Refresh period for {@link PollingAwsPropertySourceChangeDetector}.
58+
|spring.cloud.aws.secretsmanager.reload.max-wait-for-restart | | If {@link ReloadStrategy#RESTART_CONTEXT} is configured, maximum waiting time for server restart.
59+
|spring.cloud.aws.secretsmanager.reload.period | | Refresh period for {@link PollingAwsPropertySourceChangeDetector}.
5660
|spring.cloud.aws.secretsmanager.reload.strategy | | Reload strategy to run when properties change.
5761
|spring.cloud.aws.ses.enabled | `+++true+++` | Enables Simple Email Service integration.
5862
|spring.cloud.aws.ses.endpoint | | Overrides the default endpoint.

docs/src/main/asciidoc/core.adoc

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ public interface AwsCredentialsProvider {
2626
}
2727
----
2828

29+
There are 3 ways in which the `AwsCredentialsProvider` in Spring Cloud AWS can be configured:
30+
31+
1. `DefaultCredentialsProvider`
32+
2. `StsWebIdentityTokenFileCredentialsProvider` - recommended for EKS
33+
3. Custom `AwsCredentialsProvider`
34+
35+
If you are having problems with configuring credentials, consider enabling debug logging for more info:
36+
37+
[source,properties]
38+
----
39+
logging.level.io.awspring.cloud=debug
40+
----
41+
42+
==== DefaultCredentialsProvider
43+
2944
By default, Spring Cloud AWS starter auto-configures a `DefaultCredentialsProvider`, which looks for AWS credentials in this order:
3045

3146
1. Java System Properties - `aws.accessKeyId` and `aws.secretAccessKey`
@@ -61,9 +76,46 @@ If it does not serve your project needs, this behavior can be changed by setting
6176

6277
|spring.cloud.aws.credentials.profile.path
6378
|`~/.aws/credentials`
64-
|The file path where the profile configuration file is located. Defaults to `~/.aws/credentials` if value is not provided
79+
|The file path where the profile configuration file is located. Defaults to `~/.aws/credentials` if a value is not provided
6580
|===
6681

82+
==== StsWebIdentityTokenFileCredentialsProvider
83+
84+
The `StsWebIdentityTokenFileCredentialsProvider` allows your application to assume an AWS IAM Role using a web identity token file, which is especially useful in Kubernetes and AWS EKS environments.
85+
86+
===== Prerequisites
87+
1. Create a role that you want to assume.
88+
2. Create a web identity token file for your application.
89+
90+
In EKS, please follow this guide to set up service accounts https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html
91+
92+
The `StsWebIdentityTokenFileCredentialsProvider` support is optional, so you need to include the following Maven dependency:
93+
[source,xml,indent=0]
94+
----
95+
<dependency>
96+
<groupId>software.amazon.awssdk</groupId>
97+
<artifactId>sts</artifactId>
98+
</dependency>
99+
----
100+
101+
102+
===== Configuring
103+
In EKS no additional configuration is required as the service account already configures the correct environment variables; however, they can be overridden.
104+
105+
STS credentials configuration supports following properties:
106+
107+
[cols="2,3,1,1"]
108+
|===
109+
| Name | Description | Required | Default value
110+
| `spring.cloud.aws.credentials.sts.role-arn` | ARN of IAM role associated with STS. | No | `null` (falls back to SDK default)
111+
| `spring.cloud.aws.credentials.sts.web-identity-token-file` | Absolute path to the web identity token file that will be used by credentials provider. | No | `null` (falls back to SDK default)
112+
| `spring.cloud.aws.credentials.sts.is-async-credentials-update` | Enables provider to asynchronously fetch credentials in the background. | No | `false`
113+
| `spring.cloud.aws.credentials.sts.role-session-name` | Role session name that will be used by credentials provider. | No | `null` (falls back to SDK default)
114+
|===
115+
116+
117+
==== Custom AwsCredentialsProvider
118+
67119
It is also possible to configure custom `AwsCredentialsProvider` bean which will prevent Spring Cloud AWS from auto-configuring credentials provider:
68120

69121
[source,java,indent=0]

spring-cloud-aws-autoconfigure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@
146146
<artifactId>spring-boot-starter-aop</artifactId>
147147
<optional>true</optional>
148148
</dependency>
149+
<dependency>
150+
<groupId>software.amazon.awssdk</groupId>
151+
<artifactId>sts</artifactId>
152+
<optional>true</optional>
153+
</dependency>
149154

150155
</dependencies>
151156
</project>

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/AbstractAwsConfigDataLocationResolver.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
*/
1616
package io.awspring.cloud.autoconfigure.config;
1717

18+
import static io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration.createCredentialsProvider;
19+
1820
import io.awspring.cloud.autoconfigure.AwsClientProperties;
1921
import io.awspring.cloud.autoconfigure.core.AwsProperties;
2022
import io.awspring.cloud.autoconfigure.core.CredentialsProperties;
21-
import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
2223
import io.awspring.cloud.autoconfigure.core.RegionProperties;
2324
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
2425
import io.awspring.cloud.core.SpringCloudClientConfiguration;
@@ -48,6 +49,7 @@
4849
*
4950
* @param <T> - the location type
5051
* @author Maciej Walkowiak
52+
* @author Eduan Bekker
5153
* @since 3.0
5254
*/
5355
public abstract class AbstractAwsConfigDataLocationResolver<T extends ConfigDataResource>
@@ -116,24 +118,25 @@ protected List<String> getCustomContexts(String keys) {
116118

117119
protected <T extends AwsClientBuilder<?, ?>> T configure(T builder, AwsClientProperties properties,
118120
BootstrapContext context) {
119-
AwsCredentialsProvider credentialsProvider;
121+
122+
AwsRegionProvider regionProvider;
120123

121124
try {
122-
credentialsProvider = context.get(AwsCredentialsProvider.class);
125+
regionProvider = context.get(AwsRegionProvider.class);
123126
}
124127
catch (IllegalStateException e) {
125-
CredentialsProperties credentialsProperties = context.get(CredentialsProperties.class);
126-
credentialsProvider = CredentialsProviderAutoConfiguration.createCredentialsProvider(credentialsProperties);
128+
RegionProperties regionProperties = context.get(RegionProperties.class);
129+
regionProvider = RegionProviderAutoConfiguration.createRegionProvider(regionProperties);
127130
}
128131

129-
AwsRegionProvider regionProvider;
132+
AwsCredentialsProvider credentialsProvider;
130133

131134
try {
132-
regionProvider = context.get(AwsRegionProvider.class);
135+
credentialsProvider = context.get(AwsCredentialsProvider.class);
133136
}
134137
catch (IllegalStateException e) {
135-
RegionProperties regionProperties = context.get(RegionProperties.class);
136-
regionProvider = RegionProviderAutoConfiguration.createRegionProvider(regionProperties);
138+
CredentialsProperties credentialsProperties = context.get(CredentialsProperties.class);
139+
credentialsProvider = createCredentialsProvider(credentialsProperties, regionProvider);
137140
}
138141

139142
AwsProperties awsProperties = context.get(AwsProperties.class);

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CredentialsProperties.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.awspring.cloud.autoconfigure.core;
1717

1818
import org.springframework.boot.context.properties.ConfigurationProperties;
19+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
1920
import org.springframework.lang.Nullable;
2021

2122
/**
@@ -55,6 +56,14 @@ public class CredentialsProperties {
5556
@Nullable
5657
private Profile profile;
5758

59+
/**
60+
* Properties used to configure STS
61+
* {@link software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider}.
62+
*/
63+
@NestedConfigurationProperty
64+
@Nullable
65+
private StsProperties sts;
66+
5867
@Nullable
5968
public String getAccessKey() {
6069
return this.accessKey;
@@ -90,4 +99,12 @@ public void setProfile(Profile profile) {
9099
this.profile = profile;
91100
}
92101

102+
@Nullable
103+
public StsProperties getSts() {
104+
return sts;
105+
}
106+
107+
public void setSts(@Nullable StsProperties sts) {
108+
this.sts = sts;
109+
}
93110
}

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/CredentialsProviderAutoConfiguration.java

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@
1818
import java.nio.file.Paths;
1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
2123
import org.springframework.boot.autoconfigure.AutoConfiguration;
2224
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2325
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2527
import org.springframework.boot.context.properties.EnableConfigurationProperties;
28+
import org.springframework.boot.context.properties.PropertyMapper;
2629
import org.springframework.context.annotation.Bean;
30+
import org.springframework.lang.Nullable;
31+
import org.springframework.util.ClassUtils;
2732
import org.springframework.util.StringUtils;
2833
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
2934
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@@ -33,31 +38,40 @@
3338
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
3439
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
3540
import software.amazon.awssdk.profiles.ProfileFile;
41+
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
42+
import software.amazon.awssdk.services.sts.StsClient;
43+
import software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider;
3644

3745
/**
3846
* {@link EnableAutoConfiguration} for {@link AwsCredentialsProvider}.
3947
*
4048
* @author Maciej Walkowiak
4149
* @author Eddú Meléndez
50+
* @author Eduan Bekker
4251
*/
4352
@AutoConfiguration
4453
@ConditionalOnClass({ AwsCredentialsProvider.class, ProfileFile.class })
54+
@ConditionalOnMissingBean(AwsCredentialsProvider.class)
4555
@EnableConfigurationProperties(CredentialsProperties.class)
4656
public class CredentialsProviderAutoConfiguration {
57+
private static final Logger LOGGER = LoggerFactory.getLogger(CredentialsProviderAutoConfiguration.class);
58+
private static final String STS_WEB_IDENTITY_TOKEN_FILE_CREDENTIALS_PROVIDER = "software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider";
4759

4860
private final CredentialsProperties properties;
61+
private final AwsRegionProvider regionProvider;
4962

50-
public CredentialsProviderAutoConfiguration(CredentialsProperties properties) {
63+
public CredentialsProviderAutoConfiguration(CredentialsProperties properties, AwsRegionProvider regionProvider) {
5164
this.properties = properties;
65+
this.regionProvider = regionProvider;
5266
}
5367

5468
@Bean
55-
@ConditionalOnMissingBean
5669
public AwsCredentialsProvider credentialsProvider() {
57-
return createCredentialsProvider(this.properties);
70+
return createCredentialsProvider(this.properties, this.regionProvider);
5871
}
5972

60-
public static AwsCredentialsProvider createCredentialsProvider(CredentialsProperties properties) {
73+
public static AwsCredentialsProvider createCredentialsProvider(CredentialsProperties properties,
74+
AwsRegionProvider regionProvider) {
6175
final List<AwsCredentialsProvider> providers = new ArrayList<>();
6276

6377
if (StringUtils.hasText(properties.getAccessKey()) && StringUtils.hasText(properties.getSecretKey())) {
@@ -73,6 +87,17 @@ public static AwsCredentialsProvider createCredentialsProvider(CredentialsProper
7387
providers.add(createProfileCredentialProvider(profile));
7488
}
7589

90+
StsProperties sts = properties.getSts();
91+
if (ClassUtils.isPresent(STS_WEB_IDENTITY_TOKEN_FILE_CREDENTIALS_PROVIDER, null)) {
92+
try {
93+
providers.add(StsCredentialsProviderFactory.create(sts, regionProvider));
94+
}
95+
catch (IllegalStateException e) {
96+
LOGGER.warn(
97+
"Skipping creating `StsCredentialsProvider`. `software.amazon.awssdk:sts` is on the classpath, but neither `spring.cloud.aws.credentials.sts` properties are configured nor `AWS_WEB_IDENTITY_TOKEN_FILE` or the javaproperty `aws.webIdentityTokenFile` is set");
98+
}
99+
}
100+
76101
if (providers.isEmpty()) {
77102
return DefaultCredentialsProvider.create();
78103
}
@@ -101,4 +126,24 @@ private static ProfileCredentialsProvider createProfileCredentialProvider(Profil
101126
return ProfileCredentialsProvider.builder().profileName(profile.getName()).profileFile(profileFile).build();
102127
}
103128

129+
/**
130+
* Wrapper class to avoid {@link NoClassDefFoundError}.
131+
*/
132+
private static class StsCredentialsProviderFactory {
133+
private static AwsCredentialsProvider create(@Nullable StsProperties stsProperties,
134+
AwsRegionProvider regionProvider) {
135+
PropertyMapper propertyMapper = PropertyMapper.get();
136+
StsWebIdentityTokenFileCredentialsProvider.Builder builder = StsWebIdentityTokenFileCredentialsProvider
137+
.builder().stsClient(StsClient.builder().region(regionProvider.getRegion()).build());
138+
139+
if (stsProperties != null) {
140+
builder.asyncCredentialUpdateEnabled(stsProperties.isAsyncCredentialsUpdate());
141+
propertyMapper.from(stsProperties::getRoleArn).whenNonNull().to(builder::roleArn);
142+
propertyMapper.from(stsProperties::getWebIdentityTokenFile).whenNonNull()
143+
.to(b -> builder.webIdentityTokenFile(Paths.get(b)));
144+
propertyMapper.from(stsProperties::getRoleSessionName).whenNonNull().to(builder::roleSessionName);
145+
}
146+
return builder.build();
147+
}
148+
}
104149
}

0 commit comments

Comments
 (0)