exceptionfactory commented on code in PR #10294:
URL: https://github.com/apache/nifi/pull/10294#discussion_r2392880828


##########
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,219 @@
+/*
+ * 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.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 (OIDC)", new PropertyDescriptor[]{

Review Comment:
   I recommend removing `(OIDC)` and just mentioning it in the description 
where applicable.
   ```suggestion
           super("Web Identity", new PropertyDescriptor[]{
   ```



##########
nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java:
##########
@@ -260,6 +263,12 @@ public class AWSCredentialsProviderControllerService 
extends AbstractControllerS
         .dynamicallyModifiesClasspath(true)
         .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)

Review Comment:
   What options are available to make this dependent on other properties? 
Perhaps the `Assume Role ARN`? It may also be helpful to introduce some type of 
strategy property where `WebIdentity` is one of several allowed values, on 
which this property would depend. That should help make it clear when this 
service is actually needed.



##########
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,219 @@
+/*
+ * 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.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 (OIDC)", 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 null;
+    }
+
+    @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);
+    }
+
+    @Override
+    public com.amazonaws.auth.AWSCredentialsProvider 
getCredentialsProvider(final PropertyContext propertyContext) {
+        throw new UnsupportedOperationException("AWS Java SDK v1 is not 
supported by Web Identity strategy");
+    }
+
+    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 webIdentityJwt = getWebIdentityToken();

Review Comment:
   Minor, but I recommend retaining `Token` in the variable name:
   ```suggestion
                   final String webIdentityToken = getWebIdentityToken();
   ```



##########
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,219 @@
+/*
+ * 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.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 (OIDC)", 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 null;
+    }
+
+    @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);
+    }
+
+    @Override
+    public com.amazonaws.auth.AWSCredentialsProvider 
getCredentialsProvider(final PropertyContext propertyContext) {
+        throw new UnsupportedOperationException("AWS Java SDK v1 is not 
supported by Web Identity strategy");
+    }
+
+    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 webIdentityJwt = getWebIdentityToken();
+
+                final AssumeRoleWithWebIdentityRequest.Builder reqBuilder = 
AssumeRoleWithWebIdentityRequest.builder()
+                        .roleArn(roleArn)
+                        .roleSessionName(roleSessionName)
+                        .webIdentityToken(webIdentityJwt);
+
+                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 Object idTokenObj = additional.get("id_token");
+                if (idTokenObj instanceof String && !((String) 
idTokenObj).isEmpty()) {
+                    return (String) idTokenObj;
+                }
+            }
+
+            final String token = accessToken.getAccessToken();
+
+            if (StringUtils.isBlank(token)) {
+                throw new IllegalStateException("No usable token found in 
AccessToken (id_token or access_token)");
+            }
+
+            return token;

Review Comment:
   I recommend refactoring this method to a single return.



##########
nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java:
##########
@@ -74,18 +75,32 @@ public class AssumeRoleCredentialsStrategy extends 
AbstractCredentialsStrategy {
     public AssumeRoleCredentialsStrategy() {
         super("Assume Role", new PropertyDescriptor[] {
                 ASSUME_ROLE_ARN,
-                ASSUME_ROLE_NAME,
-                MAX_SESSION_TIME
+                ASSUME_ROLE_NAME
         });
     }
 
+    @Override
+    public Collection<ValidationResult> validate(final ValidationContext 
validationContext,
+                                                 final CredentialsStrategy 
primaryStrategy) {
+        // 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 null;

Review Comment:
   This should return an empty collection instead of `null`.



##########
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,219 @@
+/*
+ * 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.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 (OIDC)", 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 null;
+    }
+
+    @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);
+    }
+
+    @Override
+    public com.amazonaws.auth.AWSCredentialsProvider 
getCredentialsProvider(final PropertyContext propertyContext) {
+        throw new UnsupportedOperationException("AWS Java SDK v1 is not 
supported by Web Identity strategy");
+    }
+
+    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 webIdentityJwt = getWebIdentityToken();
+
+                final AssumeRoleWithWebIdentityRequest.Builder reqBuilder = 
AssumeRoleWithWebIdentityRequest.builder()
+                        .roleArn(roleArn)
+                        .roleSessionName(roleSessionName)
+                        .webIdentityToken(webIdentityJwt);
+
+                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 Object idTokenObj = additional.get("id_token");
+                if (idTokenObj instanceof String && !((String) 
idTokenObj).isEmpty()) {

Review Comment:
   For simplicity, and clarity of behavior, I recommend simply checking of 
`idToken` is not null, and the throwing an `IllegalStateException` if the 
string itself is empty.



##########
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,219 @@
+/*
+ * 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.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 (OIDC)", 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 null;

Review Comment:
   I believe this should return an empty Collection instead of `null`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to