This is an automated email from the ASF dual-hosted git repository.
exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 8450ec70fc NIFI-14956 Add STS Assume Role With WebIdentity to AWS
Credentials Provider (#10294)
8450ec70fc is described below
commit 8450ec70fc598ac1037a5d7ac8fa58d8588e39bd
Author: Pierre Villard <[email protected]>
AuthorDate: Thu Oct 30 16:19:02 2025 +0100
NIFI-14956 Add STS Assume Role With WebIdentity to AWS Credentials Provider
(#10294)
Signed-off-by: David Handermann <[email protected]>
---
.../nifi-aws-bundle/nifi-aws-processors/pom.xml | 6 +
.../strategies/AssumeRoleCredentialsStrategy.java | 29 ++-
.../strategies/WebIdentityCredentialsStrategy.java | 218 +++++++++++++++++++++
.../AWSCredentialsProviderControllerService.java | 43 +++-
...WSCredentialsProviderControllerServiceTest.java | 73 +++++++
5 files changed, 348 insertions(+), 21 deletions(-)
diff --git a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/pom.xml
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/pom.xml
index f235003ddd..454c2e01c9 100644
--- a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/pom.xml
+++ b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/pom.xml
@@ -31,6 +31,12 @@
<artifactId>nifi-utils</artifactId>
</dependency>
+ <!-- OAuth2 Access Token Provider API for Web Identity (OIDC) support
-->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-oauth2-provider-api</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-listed-entity</artifactId>
diff --git
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
index 18889b7ad9..19912d94a6 100644
---
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
+++
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
@@ -22,6 +22,7 @@ import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.context.PropertyContext;
import
org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy;
+import
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService;
import org.apache.nifi.proxy.ProxyConfiguration;
import org.apache.nifi.proxy.ProxyConfigurationService;
import org.apache.nifi.ssl.SSLContextProvider;
@@ -36,8 +37,8 @@ import
software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import javax.net.ssl.SSLContext;
import java.net.Proxy;
import java.net.URI;
-import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN;
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_EXTERNAL_ID;
@@ -48,7 +49,6 @@ import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCre
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION;
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.MAX_SESSION_TIME;
-
/**
* Supports AWS credentials via Assume Role. Assume Role is a derived
credential strategy, requiring a primary
* credential to retrieve and periodically refresh temporary credentials.
@@ -73,6 +73,10 @@ public class AssumeRoleCredentialsStrategy extends
AbstractCredentialsStrategy {
@Override
public boolean canCreateDerivedCredential(final PropertyContext
propertyContext) {
+ if
(propertyContext.getProperty(AWSCredentialsProviderControllerService.OAUTH2_ACCESS_TOKEN_PROVIDER).isSet())
{
+ return false;
+ }
+
final String assumeRoleArn =
propertyContext.getProperty(ASSUME_ROLE_ARN).getValue();
final String assumeRoleName =
propertyContext.getProperty(ASSUME_ROLE_NAME).getValue();
if (assumeRoleArn != null && !assumeRoleArn.isEmpty()
@@ -85,22 +89,11 @@ public class AssumeRoleCredentialsStrategy extends
AbstractCredentialsStrategy {
@Override
public Collection<ValidationResult> validate(final ValidationContext
validationContext,
final CredentialsStrategy
primaryStrategy) {
- final Collection<ValidationResult> validationFailureResults = new
ArrayList<>();
-
- final boolean assumeRoleArnIsSet =
validationContext.getProperty(ASSUME_ROLE_ARN).isSet();
-
- if (assumeRoleArnIsSet) {
- final Integer maxSessionTime =
validationContext.getProperty(MAX_SESSION_TIME).asInteger();
-
- // Session time only b/w 900 to 3600 sec (see
software.amazon.awssdk.services.sts.model.AssumeRoleRequest#durationSeconds)
- if (maxSessionTime < 900 || maxSessionTime > 3600) {
- validationFailureResults.add(new
ValidationResult.Builder().valid(false).input(maxSessionTime + "")
- .explanation(MAX_SESSION_TIME.getDisplayName() +
- " must be between 900 and 3600
seconds").build());
- }
- }
-
- return validationFailureResults;
+ // Assume Role participates as a derived strategy or reused property
group.
+ // Do not produce cross-strategy validation failures here;
required/missing
+ // fields are enforced by PropertyDescriptor requirements and selected
+ // strategies, and derived selection is handled separately.
+ return Collections.emptyList();
}
@Override
diff --git
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/WebIdentityCredentialsStrategy.java
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/WebIdentityCredentialsStrategy.java
new file mode 100644
index 0000000000..7f5c97170e
--- /dev/null
+++
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/WebIdentityCredentialsStrategy.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.processors.aws.credentials.provider.factory.strategies;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.oauth2.AccessToken;
+import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
+import
org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy;
+import org.apache.nifi.proxy.ProxyConfiguration;
+import org.apache.nifi.proxy.ProxyConfigurationService;
+import org.apache.nifi.ssl.SSLContextProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.http.apache.ApacheHttpClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sts.StsClient;
+import software.amazon.awssdk.services.sts.StsClientBuilder;
+import
software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
+import
software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityResponse;
+import software.amazon.awssdk.services.sts.model.Credentials;
+
+import javax.net.ssl.SSLContext;
+
+import java.net.Proxy;
+import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_PROXY_CONFIGURATION_SERVICE;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_SSL_CONTEXT_SERVICE;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_ENDPOINT;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.MAX_SESSION_TIME;
+import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.OAUTH2_ACCESS_TOKEN_PROVIDER;
+
+/**
+ * Supports AWS SDK v2 credentials using STS AssumeRoleWithWebIdentity and an
OAuth2/OIDC token
+ * provided by an {@link OAuth2AccessTokenProvider}. This is a primary
strategy for SDK v2 only.
+ */
+public class WebIdentityCredentialsStrategy extends
AbstractCredentialsStrategy implements CredentialsStrategy {
+
+ public WebIdentityCredentialsStrategy() {
+ super("Web Identity", new PropertyDescriptor[]{
+ OAUTH2_ACCESS_TOKEN_PROVIDER,
+ ASSUME_ROLE_ARN,
+ ASSUME_ROLE_NAME
+ });
+ }
+
+ @Override
+ public Collection<ValidationResult> validate(final ValidationContext
validationContext, final CredentialsStrategy primaryStrategy) {
+ // Avoid cross-strategy validation conflicts: Web Identity reuses
Assume Role properties.
+ // Controller-level validation enforces required combinations when
OAuth2 is configured.
+ return Collections.emptyList();
+ }
+
+ @Override
+ public AwsCredentialsProvider getAwsCredentialsProvider(final
PropertyContext propertyContext) {
+ final OAuth2AccessTokenProvider tokenProvider =
propertyContext.getProperty(OAUTH2_ACCESS_TOKEN_PROVIDER).asControllerService(OAuth2AccessTokenProvider.class);
+ final String roleArn =
propertyContext.getProperty(ASSUME_ROLE_ARN).getValue();
+ final String roleSessionName =
propertyContext.getProperty(ASSUME_ROLE_NAME).getValue();
+ final Integer sessionSeconds =
propertyContext.getProperty(MAX_SESSION_TIME).asInteger();
+ final String stsRegionId =
propertyContext.getProperty(ASSUME_ROLE_STS_REGION).getValue();
+ final String stsEndpoint =
propertyContext.getProperty(ASSUME_ROLE_STS_ENDPOINT).getValue();
+ final SSLContextProvider sslContextProvider =
propertyContext.getProperty(ASSUME_ROLE_SSL_CONTEXT_SERVICE).asControllerService(SSLContextProvider.class);
+ final ProxyConfigurationService proxyConfigurationService =
propertyContext.getProperty(ASSUME_ROLE_PROXY_CONFIGURATION_SERVICE).asControllerService(ProxyConfigurationService.class);
+
+ final ApacheHttpClient.Builder httpClientBuilder =
ApacheHttpClient.builder();
+
+ if (sslContextProvider != null) {
+ final SSLContext sslContext = sslContextProvider.createContext();
+ httpClientBuilder.socketFactory(new
SSLConnectionSocketFactory(sslContext));
+ }
+
+ if (proxyConfigurationService != null) {
+ final ProxyConfiguration proxyConfiguration =
proxyConfigurationService.getConfiguration();
+ if (proxyConfiguration.getProxyType() == Proxy.Type.HTTP) {
+ final
software.amazon.awssdk.http.apache.ProxyConfiguration.Builder
proxyConfigBuilder =
software.amazon.awssdk.http.apache.ProxyConfiguration.builder()
+ .endpoint(URI.create(String.format("http://%s:%s",
proxyConfiguration.getProxyServerHost(),
proxyConfiguration.getProxyServerPort())));
+
+ if (proxyConfiguration.hasCredential()) {
+
proxyConfigBuilder.username(proxyConfiguration.getProxyUserName());
+
proxyConfigBuilder.password(proxyConfiguration.getProxyUserPassword());
+ }
+
+
httpClientBuilder.proxyConfiguration(proxyConfigBuilder.build());
+ }
+ }
+
+ final StsClientBuilder stsClientBuilder =
StsClient.builder().httpClient(httpClientBuilder.build());
+
+ if (stsRegionId != null) {
+ stsClientBuilder.region(Region.of(stsRegionId));
+ }
+
+ if (stsEndpoint != null && !stsEndpoint.isEmpty()) {
+ stsClientBuilder.endpointOverride(URI.create(stsEndpoint));
+ }
+
+ final StsClient stsClient = stsClientBuilder.build();
+
+ return new WebIdentityRefreshingCredentialsProvider(stsClient,
tokenProvider, roleArn, roleSessionName, sessionSeconds);
+ }
+
+ private static final class WebIdentityRefreshingCredentialsProvider
implements AwsCredentialsProvider {
+ private static final Duration SKEW = Duration.ofSeconds(60);
+
+ private final StsClient stsClient;
+ private final OAuth2AccessTokenProvider oauth2AccessTokenProvider;
+ private final String roleArn;
+ private final String roleSessionName;
+ private final Integer sessionSeconds;
+
+ private volatile AwsSessionCredentials cached;
+ private volatile Instant expiration;
+
+ private WebIdentityRefreshingCredentialsProvider(final StsClient
stsClient,
+ final
OAuth2AccessTokenProvider oauth2AccessTokenProvider,
+ final String roleArn,
+ final String
roleSessionName,
+ final Integer
sessionSeconds) {
+ this.stsClient = Objects.requireNonNull(stsClient, "stsClient
required");
+ this.oauth2AccessTokenProvider =
Objects.requireNonNull(oauth2AccessTokenProvider, "OAuth2AccessTokenProvider
required");
+ this.roleArn = Objects.requireNonNull(roleArn, "roleArn required");
+ this.roleSessionName = Objects.requireNonNull(roleSessionName,
"roleSessionName required");
+ this.sessionSeconds = sessionSeconds;
+ }
+
+ @Override
+ public AwsCredentials resolveCredentials() {
+ final Instant now = Instant.now();
+ final AwsSessionCredentials current = cached;
+ final Instant currentExpiration = expiration;
+ if (current != null && currentExpiration != null &&
now.isBefore(currentExpiration.minus(SKEW))) {
+ return current;
+ }
+
+ synchronized (this) {
+ if (cached != null && expiration != null &&
Instant.now().isBefore(expiration.minus(SKEW))) {
+ return cached;
+ }
+
+ final String webIdentityToken = getWebIdentityToken();
+
+ final AssumeRoleWithWebIdentityRequest.Builder reqBuilder =
AssumeRoleWithWebIdentityRequest.builder()
+ .roleArn(roleArn)
+ .roleSessionName(roleSessionName)
+ .webIdentityToken(webIdentityToken);
+
+ if (sessionSeconds != null) {
+ reqBuilder.durationSeconds(sessionSeconds);
+ }
+
+ final AssumeRoleWithWebIdentityResponse resp =
stsClient.assumeRoleWithWebIdentity(reqBuilder.build());
+ final Credentials creds = resp.credentials();
+ final AwsSessionCredentials sessionCreds =
AwsSessionCredentials.create(
+ creds.accessKeyId(), creds.secretAccessKey(),
creds.sessionToken());
+
+ this.cached = sessionCreds;
+ this.expiration = creds.expiration();
+ return sessionCreds;
+ }
+ }
+
+ private String getWebIdentityToken() {
+ final AccessToken accessToken =
oauth2AccessTokenProvider.getAccessDetails();
+ if (accessToken == null) {
+ throw new IllegalStateException("OAuth2AccessTokenProvider
returned null AccessToken");
+ }
+
+ // Prefer id_token when available
+ final Map<String, Object> additional =
accessToken.getAdditionalParameters();
+ if (additional != null) {
+ final String idToken = (String) additional.get("id_token");
+ if (idToken != null) {
+ if (StringUtils.isBlank(idToken)) {
+ throw new
IllegalStateException("OAuth2AccessTokenProvider returned an empty id_token");
+ } else {
+ return idToken;
+ }
+ }
+ }
+
+ final String accessTokenValue = accessToken.getAccessToken();
+ if (StringUtils.isBlank(accessTokenValue)) {
+ throw new IllegalStateException("No usable token found in
AccessToken (id_token or access_token)");
+ } else {
+ return accessTokenValue;
+ }
+ }
+ }
+}
diff --git
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
index 840c958fb2..414207feb2 100644
---
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
+++
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
@@ -34,6 +34,7 @@ import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.migration.PropertyConfiguration;
import org.apache.nifi.migration.ProxyServiceMigration;
+import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
import org.apache.nifi.processor.util.StandardValidators;
import
org.apache.nifi.processors.aws.credentials.provider.AwsCredentialsProviderService;
import
org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy;
@@ -44,6 +45,7 @@ import
org.apache.nifi.processors.aws.credentials.provider.factory.strategies.Ex
import
org.apache.nifi.processors.aws.credentials.provider.factory.strategies.FileCredentialsStrategy;
import
org.apache.nifi.processors.aws.credentials.provider.factory.strategies.ImplicitDefaultCredentialsStrategy;
import
org.apache.nifi.processors.aws.credentials.provider.factory.strategies.NamedProfileCredentialsStrategy;
+import
org.apache.nifi.processors.aws.credentials.provider.factory.strategies.WebIdentityCredentialsStrategy;
import org.apache.nifi.proxy.ProxyConfigurationService;
import org.apache.nifi.ssl.SSLContextProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@@ -61,7 +63,8 @@ import java.util.List;
@CapabilityDescription("Defines credentials for Amazon Web Services
processors. " +
"Uses default credentials without configuration. " +
"Default credentials support EC2 instance profile/role, default user
profile, environment variables, etc. " +
- "Additional options include access key / secret key pairs, credentials
file, named profile, and assume role credentials.")
+ "Additional options include access key / secret key pairs, credentials
file, named profile, assume role credentials, " +
+ "and OAuth2 OIDC Web Identity-based temporary credentials using the
same Assume Role properties.")
@Tags({ "aws", "credentials", "provider" })
@Restricted(
restrictions = {
@@ -215,6 +218,14 @@ public class AWSCredentialsProviderControllerService
extends AbstractControllerS
.dependsOn(ASSUME_ROLE_ARN)
.build();
+ public static final PropertyDescriptor OAUTH2_ACCESS_TOKEN_PROVIDER = new
PropertyDescriptor.Builder()
+ .name("OAuth2 Access Token Provider")
+ .description("Controller Service providing OAuth2/OIDC tokens to
exchange for AWS temporary credentials using STS AssumeRoleWithWebIdentity.")
+ .identifiesControllerService(OAuth2AccessTokenProvider.class)
+ .required(false)
+ .dependsOn(ASSUME_ROLE_ARN)
+ .build();
+
private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS =
List.of(
USE_DEFAULT_CREDENTIALS,
@@ -230,13 +241,15 @@ public class AWSCredentialsProviderControllerService
extends AbstractControllerS
ASSUME_ROLE_SSL_CONTEXT_SERVICE,
ASSUME_ROLE_PROXY_CONFIGURATION_SERVICE,
ASSUME_ROLE_STS_REGION,
- ASSUME_ROLE_STS_ENDPOINT
+ ASSUME_ROLE_STS_ENDPOINT,
+ OAUTH2_ACCESS_TOKEN_PROVIDER
);
private volatile AwsCredentialsProvider credentialsProvider;
private final List<CredentialsStrategy> strategies = List.of(
// Primary Credential Strategies
+ new WebIdentityCredentialsStrategy(),
new ExplicitDefaultCredentialsStrategy(),
new AccessKeyPairCredentialsStrategy(),
new FileCredentialsStrategy(),
@@ -321,6 +334,30 @@ public class AWSCredentialsProviderControllerService
extends AbstractControllerS
}
}
+ final boolean oauth2Configured =
validationContext.getProperty(OAUTH2_ACCESS_TOKEN_PROVIDER).isSet();
+ if (oauth2Configured) {
+ final boolean roleArnSet =
validationContext.getProperty(ASSUME_ROLE_ARN).isSet();
+ final boolean roleNameSet =
validationContext.getProperty(ASSUME_ROLE_NAME).isSet();
+ if (!roleArnSet || !roleNameSet) {
+ validationFailureResults.add(new ValidationResult.Builder()
+ .subject(ASSUME_ROLE_ARN.getDisplayName())
+ .valid(false)
+ .explanation("Web Identity (OIDC) requires both '" +
ASSUME_ROLE_ARN.getDisplayName() + "' and '" +
ASSUME_ROLE_NAME.getDisplayName() + "' to be set")
+ .build());
+ }
+ }
+
+ if (validationContext.getProperty(ASSUME_ROLE_ARN).isSet()) {
+ final Integer maxSessionTime =
validationContext.getProperty(MAX_SESSION_TIME).asInteger();
+ if (maxSessionTime != null && (maxSessionTime < 900 ||
maxSessionTime > 3600)) {
+ validationFailureResults.add(new ValidationResult.Builder()
+ .subject(MAX_SESSION_TIME.getDisplayName())
+ .valid(false)
+ .explanation(MAX_SESSION_TIME.getDisplayName() + "
must be between 900 and 3600 seconds")
+ .build());
+ }
+ }
+
return validationFailureResults;
}
@@ -349,4 +386,4 @@ public class AWSCredentialsProviderControllerService
extends AbstractControllerS
public String toString() {
return "AWSCredentialsProviderControllerService[id=" + getIdentifier()
+ "]";
}
-}
\ No newline at end of file
+}
diff --git
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
index 15c08cf464..8aa9baced0 100644
---
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
+++
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
@@ -16,6 +16,9 @@
*/
package org.apache.nifi.processors.aws.credentials.provider.service;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.oauth2.AccessToken;
+import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
import
org.apache.nifi.processors.aws.credentials.provider.AwsCredentialsProviderService;
import
org.apache.nifi.processors.aws.credentials.provider.PropertiesCredentialsProvider;
import org.apache.nifi.processors.aws.s3.FetchS3Object;
@@ -31,6 +34,7 @@ import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCre
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.CREDENTIALS_FILE;
import static
org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService.SECRET_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class AWSCredentialsProviderControllerServiceTest {
@@ -332,4 +336,73 @@ public class AWSCredentialsProviderControllerServiceTest {
assertEquals(DefaultCredentialsProvider.class,
credentialsProvider.getClass(), "credentials provider should
be equal");
}
+
+ @Test
+ public void testWebIdentityPropertiesAreValidAndServiceEnables() throws
Throwable {
+ final TestRunner runner =
TestRunners.newTestRunner(FetchS3Object.class);
+ final AWSCredentialsProviderControllerService serviceImpl = new
AWSCredentialsProviderControllerService();
+
+ final MockOAuth2AccessTokenProvider tokenProvider = new
MockOAuth2AccessTokenProvider();
+ runner.addControllerService("oauth2", tokenProvider);
+ runner.enableControllerService(tokenProvider);
+
+ runner.addControllerService("awsCredentialsProvider", serviceImpl);
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.OAUTH2_ACCESS_TOKEN_PROVIDER, "oauth2");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN,
"arn:aws:iam::123456789012:role/test");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "nifi-test");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION,
Region.US_WEST_2.id());
+
+ runner.enableControllerService(serviceImpl);
+ runner.assertValid(serviceImpl);
+
+ final AwsCredentialsProviderService service =
(AwsCredentialsProviderService) runner.getProcessContext()
+
.getControllerServiceLookup().getControllerService("awsCredentialsProvider");
+ assertNotNull(service);
+
+ final AwsCredentialsProvider credentialsProvider =
service.getAwsCredentialsProvider();
+ assertNotNull(credentialsProvider);
+ assertEquals("WebIdentityRefreshingCredentialsProvider",
credentialsProvider.getClass().getSimpleName());
+ }
+
+ @Test
+ public void testWebIdentityDoesNotChainAssumeRoleDerivedProvider() throws
Throwable {
+ final TestRunner runner =
TestRunners.newTestRunner(FetchS3Object.class);
+ final AWSCredentialsProviderControllerService serviceImpl = new
AWSCredentialsProviderControllerService();
+
+ final MockOAuth2AccessTokenProvider tokenProvider = new
MockOAuth2AccessTokenProvider();
+ runner.addControllerService("oauth2", tokenProvider);
+ runner.enableControllerService(tokenProvider);
+
+ runner.addControllerService("awsCredentialsProvider", serviceImpl);
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.OAUTH2_ACCESS_TOKEN_PROVIDER, "oauth2");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN,
"arn:aws:iam::123456789012:role/test");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "nifi-test");
+ runner.setProperty(serviceImpl,
AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION,
Region.US_WEST_2.id());
+
+ runner.enableControllerService(serviceImpl);
+ runner.assertValid(serviceImpl);
+
+ final AwsCredentialsProviderService service =
(AwsCredentialsProviderService) runner.getProcessContext()
+
.getControllerServiceLookup().getControllerService("awsCredentialsProvider");
+ assertNotNull(service);
+
+ final AwsCredentialsProvider credentialsProvider =
service.getAwsCredentialsProvider();
+ assertNotNull(credentialsProvider);
+
assertFalse(StsAssumeRoleCredentialsProvider.class.isAssignableFrom(credentialsProvider.getClass()),
+ "Derived AssumeRole should not be chained when OAuth2 (Web
Identity) is configured");
+ }
+
+ private static final class MockOAuth2AccessTokenProvider extends
AbstractControllerService implements OAuth2AccessTokenProvider {
+ @Override
+ public AccessToken getAccessDetails() {
+ final AccessToken token = new AccessToken();
+ token.setAccessToken("dummy-access-token");
+ return token;
+ }
+
+ @Override
+ public void refreshAccessDetails() {
+ // no-op
+ }
+ }
}