You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by tp...@apache.org on 2022/12/15 17:43:51 UTC

[nifi] branch main updated: NIFI-10969: Created extension point for Signer Override in AWS S3 processors

This is an automated email from the ASF dual-hosted git repository.

tpalfy 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 1e23e5146f NIFI-10969: Created extension point for Signer Override in AWS S3 processors
1e23e5146f is described below

commit 1e23e5146fab2c8f122afdf24c4ea61d3a67a32a
Author: Peter Turcsanyi <tu...@apache.org>
AuthorDate: Thu Dec 8 08:42:11 2022 +0100

    NIFI-10969: Created extension point for Signer Override in AWS S3 processors
    
    Also added Signer Override property in AWSCredentialsProviderControllerService with Custom Signer extension point.
    Made Assume Role related properties dependent on Assume Role ARN property in AWSCredentialsProviderControllerService.
    Fixed S3 IT tests.
    
    This closes #6777.
    
    Signed-off-by: Tamas Palfy <tp...@apache.org>
---
 .../apache/nifi/components/PropertyDescriptor.java | 19 ++++-
 .../apache/nifi/components/EnumAllowableValue.java | 12 +++
 .../nifi/components/TestPropertyDescriptor.java    | 22 ++++-
 .../nifi/processors/aws/AbstractAWSProcessor.java  |  9 +-
 .../processors/aws/AwsPropertyDescriptors.java     | 52 ++++++++++++
 .../factory/CredentialPropertyDescriptors.java     | 57 ++++++++++---
 .../processors/aws/s3/AbstractS3Processor.java     | 51 ++++++++++--
 .../processors/aws/signer/AwsCustomSignerUtil.java | 53 ++++++++++++
 .../nifi/processors/aws/signer/AwsSignerType.java  | 74 +++++++++++++++++
 .../strategies/AssumeRoleCredentialsStrategy.java  | 95 ++++++++++-----------
 .../AWSCredentialsProviderControllerService.java   | 10 ++-
 .../nifi/processors/aws/s3/DeleteS3Object.java     | 30 ++++++-
 .../nifi/processors/aws/s3/FetchS3Object.java      | 28 ++++++-
 .../org/apache/nifi/processors/aws/s3/ListS3.java  | 62 +++++++-------
 .../apache/nifi/processors/aws/s3/PutS3Object.java | 48 +++++++++--
 .../apache/nifi/processors/aws/s3/TagS3Object.java | 28 +++++--
 .../provider/factory/MockAWSProcessor.java         | 10 ++-
 .../factory/TestCredentialsProviderFactory.java    | 96 ++++++++++++++--------
 ...WSCredentialsProviderControllerServiceTest.java | 22 ++---
 .../apache/nifi/processors/aws/s3/ITListS3.java    | 27 ++++--
 .../nifi/processors/aws/s3/ITPutS3Object.java      | 11 +--
 .../nifi/processors/aws/s3/TestDeleteS3Object.java |  4 +-
 .../nifi/processors/aws/s3/TestFetchS3Object.java  |  4 +-
 .../nifi/processors/aws/s3/TestPutS3Object.java    | 42 +++++++++-
 .../nifi/processors/aws/s3/TestTagS3Object.java    |  4 +-
 25 files changed, 678 insertions(+), 192 deletions(-)

diff --git a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
index 6091f14dda..2e18ba9c73 100644
--- a/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
+++ b/nifi-api/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -397,8 +398,8 @@ public final class PropertyDescriptor implements Comparable<PropertyDescriptor>
 
         /**
          * Stores allowable values from an enum class.
-         * @param enumClass an enum class that implements the Allowable interface and contains a set of values
-         * @param <E> generic parameter for an enum class that implements the Allowable interface
+         * @param enumClass an enum class that implements the DescribedValue interface and contains a set of values
+         * @param <E> generic parameter for an enum class that implements the DescribedValue interface
          * @return the builder
          */
         public <E extends Enum<E> & DescribedValue> Builder allowableValues(final Class<E> enumClass) {
@@ -409,6 +410,20 @@ public final class PropertyDescriptor implements Comparable<PropertyDescriptor>
             return this;
         }
 
+        /**
+         * Stores allowable values from a set of enum values.
+         * @param enumValues a set of enum values that implements the DescribedValue interface
+         * @param <E> generic parameter for the enum values' class that implements the DescribedValue interface
+         * @return the builder
+         */
+        public <E extends Enum<E> & DescribedValue> Builder allowableValues(final EnumSet<E> enumValues) {
+            this.allowableValues = new ArrayList<>();
+            for (E enumValue : enumValues) {
+                this.allowableValues.add(new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription()));
+            }
+            return this;
+        }
+
         /**
          * @param values constrained set of values
          * @return the builder
diff --git a/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java b/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java
index 02aed04ebd..e09b9e8278 100644
--- a/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java
+++ b/nifi-api/src/test/java/org/apache/nifi/components/EnumAllowableValue.java
@@ -40,6 +40,18 @@ public enum EnumAllowableValue implements DescribedValue {
         public String getDescription() {
             return "RedDescription";
         }
+    },
+
+    BLUE {
+        @Override
+        public String getDisplayName() {
+            return "BlueDisplayName";
+        }
+
+        @Override
+        public String getDescription() {
+            return "BlueDescription";
+        }
     };
 
     @Override
diff --git a/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java b/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
index 24144d48f2..288fbcb2ea 100644
--- a/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
+++ b/nifi-api/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
@@ -25,10 +25,12 @@ import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -67,7 +69,7 @@ public class TestPropertyDescriptor {
     }
 
     @Test
-    void testAllowableValuesWithEnumValue() {
+    void testAllowableValuesWithEnumClass() {
         final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder()
                 .name("enumAllowableValueDescriptor")
                 .allowableValues(EnumAllowableValue.class)
@@ -81,6 +83,24 @@ public class TestPropertyDescriptor {
         assertEquals(expectedAllowableValues, propertyDescriptor.getAllowableValues());
     }
 
+    @Test
+    void testAllowableValuesWithEnumSet() {
+        final PropertyDescriptor propertyDescriptor = new PropertyDescriptor.Builder()
+                .name("enumAllowableValueDescriptor")
+                .allowableValues(EnumSet.of(
+                        EnumAllowableValue.GREEN,
+                        EnumAllowableValue.BLUE
+                ))
+                .build();
+
+        assertNotNull(propertyDescriptor);
+
+        final List<AllowableValue> expectedAllowableValues = Stream.of(EnumAllowableValue.GREEN, EnumAllowableValue.BLUE)
+                .map(enumValue -> new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription()))
+                .collect(Collectors.toList());
+        assertEquals(expectedAllowableValues, propertyDescriptor.getAllowableValues());
+    }
+
     @Test
     void testDependsOnWithEnumValue() {
         final PropertyDescriptor dependentPropertyDescriptor = new PropertyDescriptor.Builder()
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
index 82a67a36d8..8831c352f2 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
@@ -343,8 +343,11 @@ public abstract class AbstractAWSProcessor<ClientType extends AmazonWebServiceCl
                     // e.g. in case of https://vpce-***-***.sqs.{region}.vpce.amazonaws.com
                     String regionValue = parseRegionForVPCE(urlstr, region.getName());
                     client.setEndpoint(urlstr, client.getServiceName(), regionValue);
+                } else if (isCustomSignerConfigured(context)) {
+                    // handling endpoints with a user defined custom signer
+                    client.setEndpoint(urlstr, client.getServiceName(), region.getName());
                 } else {
-                    // handling non-vpce custom endpoints where the AWS library can parse the region out
+                    // handling other (non-vpce, no custom signer) custom endpoints where the AWS library can parse the region out
                     // e.g. https://sqs.{region}.***.***.***.gov
                     client.setEndpoint(urlstr);
                 }
@@ -353,6 +356,10 @@ public abstract class AbstractAWSProcessor<ClientType extends AmazonWebServiceCl
         return region;
     }
 
+    protected boolean isCustomSignerConfigured(final ProcessContext context) {
+        return false;
+    }
+
     /*
     Note to developer(s):
         When setting an endpoint for an AWS Client i.e. client.setEndpoint(endpointUrl),
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AwsPropertyDescriptors.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AwsPropertyDescriptors.java
new file mode 100644
index 0000000000..84eb335fb9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AwsPropertyDescriptors.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import com.amazonaws.auth.Signer;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.processor.util.StandardValidators;
+
+public final class AwsPropertyDescriptors {
+
+    private AwsPropertyDescriptors() {
+        // constant class' constructor
+    }
+
+    public static final PropertyDescriptor CUSTOM_SIGNER_CLASS_NAME = new PropertyDescriptor.Builder()
+            .name("custom-signer-class-name")
+            .displayName("Custom Signer Class Name")
+            .description(String.format("Fully qualified class name of the custom signer class. The signer must implement %s interface.", Signer.class.getName()))
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+            .build();
+
+    public static final PropertyDescriptor CUSTOM_SIGNER_MODULE_LOCATION = new PropertyDescriptor.Builder()
+            .name("custom-signer-module-location")
+            .displayName("Custom Signer Module Location")
+            .description("Comma-separated list of paths to files and/or directories which contain the custom signer's JAR file and its dependencies (if any).")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+            .identifiesExternalResource(ResourceCardinality.MULTIPLE, ResourceType.FILE, ResourceType.DIRECTORY)
+            .dynamicallyModifiesClasspath(true)
+            .build();
+
+}
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java
index 78e60f09b3..6c2fc2b8db 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java
@@ -22,11 +22,17 @@ import org.apache.nifi.components.resource.ResourceCardinality;
 import org.apache.nifi.components.resource.ResourceType;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.aws.AwsPropertyDescriptors;
 import software.amazon.awssdk.regions.Region;
 
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.List;
 
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.AWS_V4_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.CUSTOM_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.DEFAULT_SIGNER;
+
 /**
  * Shared definitions of properties that specify various AWS credentials.
  *
@@ -122,7 +128,7 @@ public class CredentialPropertyDescriptors {
             .required(false)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(false)
-            .description("The AWS Role ARN for cross account access. This is used in conjunction with role name and session timeout")
+            .description("The AWS Role ARN for cross account access. This is used in conjunction with Assume Role Session Name and other Assume Role properties.")
             .build();
 
     /**
@@ -132,10 +138,11 @@ public class CredentialPropertyDescriptors {
             .name("Assume Role Session Name")
             .displayName("Assume Role Session Name")
             .expressionLanguageSupported(ExpressionLanguageScope.NONE)
-            .required(false)
+            .required(true)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(false)
-            .description("The AWS Role Name for cross account access. This is used in conjunction with role ARN and session time out")
+            .description("The AWS Role Session Name for cross account access. This is used in conjunction with Assume Role ARN.")
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
     /**
@@ -143,11 +150,13 @@ public class CredentialPropertyDescriptors {
      */
     public static final PropertyDescriptor MAX_SESSION_TIME = new PropertyDescriptor.Builder()
             .name("Session Time")
-            .description("Session time for role based session (between 900 and 3600 seconds). This is used in conjunction with role ARN and name")
+            .displayName("Assume Role Session Time")
+            .description("Session time for role based session (between 900 and 3600 seconds). This is used in conjunction with Assume Role ARN.")
             .defaultValue("3600")
             .required(false)
             .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
             .sensitive(false)
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
     /**
@@ -160,8 +169,8 @@ public class CredentialPropertyDescriptors {
             .required(false)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(false)
-            .description("External ID for cross-account access. This is used in conjunction with role arn, " +
-                "role name, and optional session time out")
+            .description("External ID for cross-account access. This is used in conjunction with Assume Role ARN.")
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
     /**
@@ -174,7 +183,8 @@ public class CredentialPropertyDescriptors {
             .required(false)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(false)
-            .description("Proxy host for cross-account access, if needed within your environment. This will configure a proxy to request for temporary access keys into another AWS account")
+            .description("Proxy host for cross-account access, if needed within your environment. This will configure a proxy to request for temporary access keys into another AWS account.")
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
     public static final PropertyDescriptor ASSUME_ROLE_PROXY_PORT = new PropertyDescriptor.Builder()
@@ -184,12 +194,13 @@ public class CredentialPropertyDescriptors {
             .required(false)
             .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
             .sensitive(false)
-            .description("Proxy port for cross-account access, if needed within your environment. This will configure a proxy to request for temporary access keys into another AWS account")
+            .description("Proxy port for cross-account access, if needed within your environment. This will configure a proxy to request for temporary access keys into another AWS account.")
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
     public static final PropertyDescriptor ASSUME_ROLE_STS_ENDPOINT = new PropertyDescriptor.Builder()
             .name("assume-role-sts-endpoint")
-            .displayName("Assume Role STS Endpoint")
+            .displayName("Assume Role STS Endpoint Override")
             .expressionLanguageSupported(ExpressionLanguageScope.NONE)
             .required(false)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
@@ -198,17 +209,41 @@ public class CredentialPropertyDescriptors {
                     "all accounts that are not for China (Beijing) region or GovCloud. You only need to set " +
                     "this property to \"sts.cn-north-1.amazonaws.com.cn\" when you are requesting session credentials " +
                     "for services in China(Beijing) region or to \"sts.us-gov-west-1.amazonaws.com\" for GovCloud.")
+            .dependsOn(ASSUME_ROLE_ARN)
             .build();
 
-    public static final PropertyDescriptor ASSUME_ROLE_REGION = new PropertyDescriptor.Builder()
+    public static final PropertyDescriptor ASSUME_ROLE_STS_REGION = new PropertyDescriptor.Builder()
             .name("assume-role-sts-region")
-            .displayName("Region")
+            .displayName("Assume Role STS Region")
             .description("The AWS Security Token Service (STS) region")
             .dependsOn(ASSUME_ROLE_ARN)
             .allowableValues(getAvailableRegions())
             .defaultValue(createAllowableValue(Region.US_WEST_2).getValue())
             .build();
 
+    public static final PropertyDescriptor ASSUME_ROLE_STS_SIGNER_OVERRIDE = new PropertyDescriptor.Builder()
+            .name("assume-role-sts-signer-override")
+            .displayName("Assume Role STS Signer Override")
+            .description("The AWS STS library uses Signature Version 4 by default. This property allows you to plug in your own custom signer implementation.")
+            .required(false)
+            .allowableValues(EnumSet.of(
+                    DEFAULT_SIGNER,
+                    AWS_V4_SIGNER,
+                    CUSTOM_SIGNER))
+            .defaultValue(DEFAULT_SIGNER.getValue())
+            .dependsOn(ASSUME_ROLE_ARN)
+            .build();
+
+    public static final PropertyDescriptor ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AwsPropertyDescriptors.CUSTOM_SIGNER_CLASS_NAME)
+            .dependsOn(ASSUME_ROLE_STS_SIGNER_OVERRIDE, CUSTOM_SIGNER)
+            .build();
+
+    public static final PropertyDescriptor ASSUME_ROLE_STS_CUSTOM_SIGNER_MODULE_LOCATION = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AwsPropertyDescriptors.CUSTOM_SIGNER_MODULE_LOCATION)
+            .dependsOn(ASSUME_ROLE_STS_SIGNER_OVERRIDE, CUSTOM_SIGNER)
+            .build();
+
     public static AllowableValue createAllowableValue(final Region region) {
         return new AllowableValue(region.id(), region.metadata().description(), "AWS Region Code : " + region.id());
     }
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
index dc29b78148..7069cb9414 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
@@ -33,7 +33,6 @@ import com.amazonaws.services.s3.model.Grantee;
 import com.amazonaws.services.s3.model.Owner;
 import com.amazonaws.services.s3.model.Permission;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.flowfile.FlowFile;
@@ -41,11 +40,20 @@ import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.processors.aws.AbstractAWSCredentialsProviderProcessor;
+import org.apache.nifi.processors.aws.AwsPropertyDescriptors;
+import org.apache.nifi.processors.aws.signer.AwsCustomSignerUtil;
+import org.apache.nifi.processors.aws.signer.AwsSignerType;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.List;
 
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.AWS_S3_V2_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.AWS_S3_V4_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.CUSTOM_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.DEFAULT_SIGNER;
+
 public abstract class AbstractS3Processor extends AbstractAWSCredentialsProviderProcessor<AmazonS3Client> {
 
     public static final PropertyDescriptor FULL_CONTROL_USER_LIST = new PropertyDescriptor.Builder()
@@ -121,14 +129,27 @@ public abstract class AbstractS3Processor extends AbstractAWSCredentialsProvider
             .build();
     public static final PropertyDescriptor SIGNER_OVERRIDE = new PropertyDescriptor.Builder()
             .name("Signer Override")
-            .description("The AWS libraries use the default signer but this property allows you to specify a custom signer to support older S3-compatible services.")
+            .description("The AWS S3 library uses Signature Version 4 by default but this property allows you to specify the Version 2 signer to support older S3-compatible services" +
+                    " or even to plug in your own custom signer implementation.")
             .required(false)
-            .allowableValues(
-                    new AllowableValue("Default Signature", "Default Signature"),
-                    new AllowableValue("AWSS3V4SignerType", "Signature v4"),
-                    new AllowableValue("S3SignerType", "Signature v2"))
-            .defaultValue("Default Signature")
+            .allowableValues(EnumSet.of(
+                            DEFAULT_SIGNER,
+                            AWS_S3_V4_SIGNER,
+                            AWS_S3_V2_SIGNER,
+                            CUSTOM_SIGNER))
+            .defaultValue(DEFAULT_SIGNER.getValue())
+            .build();
+
+    public static final PropertyDescriptor S3_CUSTOM_SIGNER_CLASS_NAME = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AwsPropertyDescriptors.CUSTOM_SIGNER_CLASS_NAME)
+            .dependsOn(SIGNER_OVERRIDE, CUSTOM_SIGNER)
+            .build();
+
+    public static final PropertyDescriptor S3_CUSTOM_SIGNER_MODULE_LOCATION = new PropertyDescriptor.Builder()
+            .fromPropertyDescriptor(AwsPropertyDescriptors.CUSTOM_SIGNER_MODULE_LOCATION)
+            .dependsOn(SIGNER_OVERRIDE, CUSTOM_SIGNER)
             .build();
+
     public static final PropertyDescriptor ENCRYPTION_SERVICE = new PropertyDescriptor.Builder()
             .name("encryption-service")
             .displayName("Encryption Service")
@@ -201,13 +222,25 @@ public abstract class AbstractS3Processor extends AbstractAWSCredentialsProvider
     }
 
     private void initializeSignerOverride(final ProcessContext context, final ClientConfiguration config) {
-        String signer = context.getProperty(SIGNER_OVERRIDE).getValue();
+        final String signer = context.getProperty(SIGNER_OVERRIDE).getValue();
+        final AwsSignerType signerType = AwsSignerType.forValue(signer);
+
+        if (signerType == CUSTOM_SIGNER) {
+            final String signerClassName = context.getProperty(S3_CUSTOM_SIGNER_CLASS_NAME).evaluateAttributeExpressions().getValue();
 
-        if (signer != null && !signer.equals(SIGNER_OVERRIDE.getDefaultValue())) {
+            config.setSignerOverride(AwsCustomSignerUtil.registerCustomSigner(signerClassName));
+        } else if (signerType != DEFAULT_SIGNER) {
             config.setSignerOverride(signer);
         }
     }
 
+    @Override
+    protected boolean isCustomSignerConfigured(final ProcessContext context) {
+        final String signer = context.getProperty(SIGNER_OVERRIDE).getValue();
+        final AwsSignerType signerType = AwsSignerType.forValue(signer);
+        return signerType == CUSTOM_SIGNER;
+    }
+
     /**
      * Create client using AWSCredentials
      *
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsCustomSignerUtil.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsCustomSignerUtil.java
new file mode 100644
index 0000000000..5e3f6cdd5b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsCustomSignerUtil.java
@@ -0,0 +1,53 @@
+/*
+ * 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.signer;
+
+import com.amazonaws.auth.Signer;
+import com.amazonaws.auth.SignerFactory;
+import org.apache.nifi.processor.exception.ProcessException;
+
+public final class AwsCustomSignerUtil {
+
+    private AwsCustomSignerUtil() {
+        // util class' constructor
+    }
+
+    @SuppressWarnings("unchecked")
+    public static String registerCustomSigner(final String className) {
+        final Class<? extends Signer> signerClass;
+
+        try {
+            final Class<?> clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
+
+            if (Signer.class.isAssignableFrom(clazz)) {
+                signerClass = (Class<? extends Signer>) clazz;
+            } else {
+                throw new ProcessException(String.format("Cannot create signer from class %s because it does not implement %s", className, Signer.class.getName()));
+            }
+        } catch (ClassNotFoundException cnfe) {
+            throw new ProcessException("Signer class not found: " + className);
+        } catch (Exception e) {
+            throw new ProcessException("Error while creating signer from class: " + className);
+        }
+
+        String signerName = signerClass.getName();
+
+        SignerFactory.registerSigner(signerName, signerClass);
+
+        return signerName;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsSignerType.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsSignerType.java
new file mode 100644
index 0000000000..cbb655d52e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/signer/AwsSignerType.java
@@ -0,0 +1,74 @@
+/*
+ * 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.signer;
+
+import com.amazonaws.auth.SignerFactory;
+import org.apache.nifi.components.DescribedValue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum AwsSignerType implements DescribedValue {
+
+    // AWS_***_SIGNERs must follow the names in com.amazonaws.auth.SignerFactory and com.amazonaws.services.s3.AmazonS3Client
+    DEFAULT_SIGNER("Default Signature", "Default Signature"),
+    AWS_V4_SIGNER(SignerFactory.VERSION_FOUR_SIGNER, "Signature Version 4"),
+    AWS_S3_V4_SIGNER("AWSS3V4SignerType", "Signature Version 4"), // AmazonS3Client.S3_V4_SIGNER
+    AWS_S3_V2_SIGNER("S3SignerType", "Signature Version 2"), // AmazonS3Client.S3_SIGNER
+    CUSTOM_SIGNER("CustomSignerType", "Custom Signature");
+
+    private static final Map<String, AwsSignerType> LOOKUP_MAP = new HashMap<>();
+
+    static {
+        for (AwsSignerType signerType : AwsSignerType.values()) {
+            LOOKUP_MAP.put(signerType.getValue(), signerType);
+        }
+    }
+
+    private final String value;
+    private final String displayName;
+    private final String description;
+
+    AwsSignerType(String value, String displayName) {
+        this(value, displayName, null);
+    }
+
+    AwsSignerType(String value, String displayName, String description) {
+        this.value = value;
+        this.displayName = displayName;
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public static AwsSignerType forValue(final String value) {
+        return LOOKUP_MAP.get(value);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
index 5766332436..a55efd6f8c 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java
@@ -19,12 +19,13 @@ package org.apache.nifi.processors.aws.credentials.provider.factory.strategies;
 import com.amazonaws.ClientConfiguration;
 import com.amazonaws.auth.AWSCredentialsProvider;
 import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider;
-import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
 import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy;
+import org.apache.nifi.processors.aws.signer.AwsCustomSignerUtil;
+import org.apache.nifi.processors.aws.signer.AwsSignerType;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 import software.amazon.awssdk.http.apache.ApacheHttpClient;
 import software.amazon.awssdk.regions.Region;
@@ -44,9 +45,13 @@ import static org.apache.nifi.processors.aws.credentials.provider.factory.Creden
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_NAME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_ENDPOINT;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_REGION;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_SIGNER_OVERRIDE;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.MAX_SESSION_TIME;
-import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_REGION;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.CUSTOM_SIGNER;
+import static org.apache.nifi.processors.aws.signer.AwsSignerType.DEFAULT_SIGNER;
 
 
 /**
@@ -95,50 +100,30 @@ public class AssumeRoleCredentialsStrategy extends AbstractCredentialsStrategy {
     @Override
     public Collection<ValidationResult> validate(final ValidationContext validationContext,
                                                  final CredentialsStrategy primaryStrategy) {
-        final boolean assumeRoleArnIsSet = validationContext.getProperty(ASSUME_ROLE_ARN).isSet();
-        final boolean assumeRoleNameIsSet = validationContext.getProperty(ASSUME_ROLE_NAME).isSet();
-        final Integer maxSessionTime = validationContext.getProperty(MAX_SESSION_TIME).asInteger();
-        final boolean assumeRoleExternalIdIsSet = validationContext.getProperty(ASSUME_ROLE_EXTERNAL_ID).isSet();
-        final boolean assumeRoleProxyHostIsSet = validationContext.getProperty(ASSUME_ROLE_PROXY_HOST).isSet();
-        final boolean assumeRoleProxyPortIsSet = validationContext.getProperty(ASSUME_ROLE_PROXY_PORT).isSet();
-        final boolean assumeRoleSTSEndpointIsSet = validationContext.getProperty(ASSUME_ROLE_STS_ENDPOINT).isSet();
-
-        final Collection<ValidationResult> validationFailureResults  = new ArrayList<ValidationResult>();
-
-        // Both role and arn name are req if present
-        if (assumeRoleArnIsSet ^ assumeRoleNameIsSet ) {
-            validationFailureResults.add(new ValidationResult.Builder().input("Assume Role Arn and Name")
-                    .valid(false).explanation("Assume role requires both arn and name to be set").build());
-        }
+        final Collection<ValidationResult> validationFailureResults  = new ArrayList<>();
 
-        // Session time only b/w 900 to 3600 sec (see sts session class)
-        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());
-
-        // External ID should only be provided with viable Assume Role ARN and Name
-        if (assumeRoleExternalIdIsSet && (!assumeRoleArnIsSet || !assumeRoleNameIsSet)) {
-            validationFailureResults.add(new ValidationResult.Builder().input("Assume Role External ID")
-                    .valid(false)
-                    .explanation("Assume role requires both arn and name to be set with External ID")
-                    .build());
-        }
-
-        // STS Endpoint should only be provided with viable Assume Role ARN and Name
-        if (assumeRoleSTSEndpointIsSet && (!assumeRoleArnIsSet || !assumeRoleNameIsSet)) {
-            validationFailureResults.add(new ValidationResult.Builder().input("Assume Role STS Endpoint")
-                    .valid(false)
-                    .explanation("Assume role requires both arn and name to be set with STS Endpoint")
-                    .build());
-        }
+        final boolean assumeRoleArnIsSet = validationContext.getProperty(ASSUME_ROLE_ARN).isSet();
 
-        // Both proxy host and proxy port are required if present
-        if (assumeRoleProxyHostIsSet ^ assumeRoleProxyPortIsSet){
-            validationFailureResults.add(new ValidationResult.Builder().input("Assume Role Proxy Host and Port")
-                    .valid(false)
-                    .explanation("Assume role with proxy requires both host and port for the proxy to be set")
-                    .build());
+        if (assumeRoleArnIsSet) {
+            final Integer maxSessionTime = validationContext.getProperty(MAX_SESSION_TIME).asInteger();
+
+            // Session time only b/w 900 to 3600 sec (see com.amazonaws.services.securitytoken.model.AssumeRoleRequest#withDurationSeconds)
+            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());
+            }
+
+            final boolean assumeRoleProxyHostIsSet = validationContext.getProperty(ASSUME_ROLE_PROXY_HOST).isSet();
+            final boolean assumeRoleProxyPortIsSet = validationContext.getProperty(ASSUME_ROLE_PROXY_PORT).isSet();
+
+            // Both proxy host and proxy port are required if present
+            if (assumeRoleProxyHostIsSet ^ assumeRoleProxyPortIsSet) {
+                validationFailureResults.add(new ValidationResult.Builder().input("Assume Role Proxy Host and Port")
+                        .valid(false)
+                        .explanation("Assume role with proxy requires both host and port for the proxy to be set")
+                        .build());
+            }
         }
 
         return validationFailureResults;
@@ -158,7 +143,9 @@ public class AssumeRoleCredentialsStrategy extends AbstractCredentialsStrategy {
         rawMaxSessionTime = rawMaxSessionTime == null ? MAX_SESSION_TIME.getDefaultValue() : rawMaxSessionTime;
         final Integer maxSessionTime = Integer.parseInt(rawMaxSessionTime.trim());
         final String assumeRoleExternalId = properties.get(ASSUME_ROLE_EXTERNAL_ID);
+        final String assumeRoleSTSRegion = properties.get(ASSUME_ROLE_STS_REGION);
         final String assumeRoleSTSEndpoint = properties.get(ASSUME_ROLE_STS_ENDPOINT);
+        final String assumeRoleSTSSigner = properties.get(ASSUME_ROLE_STS_SIGNER_OVERRIDE);
         STSAssumeRoleSessionCredentialsProvider.Builder builder;
         ClientConfiguration config = new ClientConfiguration();
 
@@ -170,10 +157,24 @@ public class AssumeRoleCredentialsStrategy extends AbstractCredentialsStrategy {
             config.withProxyPort(assumeRoleProxyPort);
         }
 
-        AWSSecurityTokenService securityTokenService = new AWSSecurityTokenServiceClient(primaryCredentialsProvider, config);
+        final AwsSignerType assumeRoleSTSSignerType = AwsSignerType.forValue(assumeRoleSTSSigner);
+        if (assumeRoleSTSSignerType == CUSTOM_SIGNER) {
+            final String signerClassName = properties.get(ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME);
+
+            config.withSignerOverride(AwsCustomSignerUtil.registerCustomSigner(signerClassName));
+        } else if (assumeRoleSTSSignerType != DEFAULT_SIGNER) {
+            config.withSignerOverride(assumeRoleSTSSigner);
+        }
+
+        AWSSecurityTokenServiceClient securityTokenService = new AWSSecurityTokenServiceClient(primaryCredentialsProvider, config);
         if (assumeRoleSTSEndpoint != null && !assumeRoleSTSEndpoint.isEmpty()) {
-            securityTokenService.setEndpoint(assumeRoleSTSEndpoint);
+            if (assumeRoleSTSSignerType == CUSTOM_SIGNER) {
+                securityTokenService.setEndpoint(assumeRoleSTSEndpoint, securityTokenService.getServiceName(), assumeRoleSTSRegion);
+            } else {
+                securityTokenService.setEndpoint(assumeRoleSTSEndpoint);
+            }
         }
+
         builder = new STSAssumeRoleSessionCredentialsProvider
                 .Builder(assumeRoleArn, assumeRoleName)
                 .withStsClient(securityTokenService)
@@ -203,7 +204,7 @@ public class AssumeRoleCredentialsStrategy extends AbstractCredentialsStrategy {
         final Integer maxSessionTime = Integer.parseInt(rawMaxSessionTime.trim());
         final String assumeRoleExternalId = properties.get(ASSUME_ROLE_EXTERNAL_ID);
         final String assumeRoleSTSEndpoint = properties.get(ASSUME_ROLE_STS_ENDPOINT);
-        final String stsRegion = properties.get(ASSUME_ROLE_REGION);
+        final String stsRegion = properties.get(ASSUME_ROLE_STS_REGION);
 
         final StsAssumeRoleCredentialsProvider.Builder builder = StsAssumeRoleCredentialsProvider.builder();
 
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
index fa99e2cddc..b02dadbcc5 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java
@@ -45,7 +45,10 @@ import static org.apache.nifi.processors.aws.credentials.provider.factory.Creden
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_ENDPOINT;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_SIGNER_OVERRIDE;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.CREDENTIALS_FILE;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_MODULE_LOCATION;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.PROFILE_NAME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.SECRET_KEY;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS;
@@ -74,7 +77,7 @@ public class AWSCredentialsProviderControllerService extends AbstractControllerS
     public static final PropertyDescriptor ASSUME_ROLE_ARN = CredentialPropertyDescriptors.ASSUME_ROLE_ARN;
     public static final PropertyDescriptor ASSUME_ROLE_NAME = CredentialPropertyDescriptors.ASSUME_ROLE_NAME;
     public static final PropertyDescriptor MAX_SESSION_TIME = CredentialPropertyDescriptors.MAX_SESSION_TIME;
-    public static final PropertyDescriptor ASSUME_ROLE_REGION = CredentialPropertyDescriptors.ASSUME_ROLE_REGION;
+    public static final PropertyDescriptor ASSUME_ROLE_STS_REGION = CredentialPropertyDescriptors.ASSUME_ROLE_STS_REGION;
 
     private static final List<PropertyDescriptor> properties;
 
@@ -92,8 +95,11 @@ public class AWSCredentialsProviderControllerService extends AbstractControllerS
         props.add(ASSUME_ROLE_EXTERNAL_ID);
         props.add(ASSUME_ROLE_PROXY_HOST);
         props.add(ASSUME_ROLE_PROXY_PORT);
+        props.add(ASSUME_ROLE_STS_REGION);
         props.add(ASSUME_ROLE_STS_ENDPOINT);
-        props.add(ASSUME_ROLE_REGION);
+        props.add(ASSUME_ROLE_STS_SIGNER_OVERRIDE);
+        props.add(ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME);
+        props.add(ASSUME_ROLE_STS_CUSTOM_SIGNER_MODULE_LOCATION);
         properties = Collections.unmodifiableList(props);
     }
 
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/DeleteS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/DeleteS3Object.java
index b4dd500b9e..19222ac87a 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/DeleteS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/DeleteS3Object.java
@@ -64,10 +64,32 @@ public class DeleteS3Object extends AbstractS3Processor {
             .required(false)
             .build();
 
-    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(
-            Arrays.asList(KEY, BUCKET, ACCESS_KEY, SECRET_KEY, CREDENTIALS_FILE, AWS_CREDENTIALS_PROVIDER_SERVICE, REGION, TIMEOUT, VERSION_ID,
-                    FULL_CONTROL_USER_LIST, READ_USER_LIST, WRITE_USER_LIST, READ_ACL_LIST, WRITE_ACL_LIST, OWNER,
-                    SSL_CONTEXT_SERVICE, ENDPOINT_OVERRIDE, SIGNER_OVERRIDE, PROXY_CONFIGURATION_SERVICE, PROXY_HOST, PROXY_HOST_PORT, PROXY_USERNAME, PROXY_PASSWORD));
+    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(Arrays.asList(
+            KEY,
+            BUCKET,
+            ACCESS_KEY,
+            SECRET_KEY,
+            CREDENTIALS_FILE,
+            AWS_CREDENTIALS_PROVIDER_SERVICE,
+            REGION,
+            TIMEOUT,
+            VERSION_ID,
+            FULL_CONTROL_USER_LIST,
+            READ_USER_LIST,
+            WRITE_USER_LIST,
+            READ_ACL_LIST,
+            WRITE_ACL_LIST,
+            OWNER,
+            SSL_CONTEXT_SERVICE,
+            ENDPOINT_OVERRIDE,
+            SIGNER_OVERRIDE,
+            S3_CUSTOM_SIGNER_CLASS_NAME,
+            S3_CUSTOM_SIGNER_MODULE_LOCATION,
+            PROXY_CONFIGURATION_SERVICE,
+            PROXY_HOST,
+            PROXY_HOST_PORT,
+            PROXY_USERNAME,
+            PROXY_PASSWORD));
 
     @Override
     protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/FetchS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/FetchS3Object.java
index 128657bf31..cbb33e45b0 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/FetchS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/FetchS3Object.java
@@ -127,10 +127,30 @@ public class FetchS3Object extends AbstractS3Processor {
             .required(false)
             .build();
 
-    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(
-            Arrays.asList(BUCKET, KEY, REGION, ACCESS_KEY, SECRET_KEY, CREDENTIALS_FILE, AWS_CREDENTIALS_PROVIDER_SERVICE, TIMEOUT, VERSION_ID,
-                SSL_CONTEXT_SERVICE, ENDPOINT_OVERRIDE, SIGNER_OVERRIDE, ENCRYPTION_SERVICE, PROXY_CONFIGURATION_SERVICE, PROXY_HOST,
-                PROXY_HOST_PORT, PROXY_USERNAME, PROXY_PASSWORD, REQUESTER_PAYS, RANGE_START, RANGE_LENGTH));
+    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(Arrays.asList(
+            BUCKET,
+            KEY,
+            REGION,
+            ACCESS_KEY,
+            SECRET_KEY,
+            CREDENTIALS_FILE,
+            AWS_CREDENTIALS_PROVIDER_SERVICE,
+            TIMEOUT,
+            VERSION_ID,
+            SSL_CONTEXT_SERVICE,
+            ENDPOINT_OVERRIDE,
+            SIGNER_OVERRIDE,
+            S3_CUSTOM_SIGNER_CLASS_NAME,
+            S3_CUSTOM_SIGNER_MODULE_LOCATION,
+            ENCRYPTION_SERVICE,
+            PROXY_CONFIGURATION_SERVICE,
+            PROXY_HOST,
+            PROXY_HOST_PORT,
+            PROXY_USERNAME,
+            PROXY_PASSWORD,
+            REQUESTER_PAYS,
+            RANGE_START,
+            RANGE_LENGTH));
 
     @Override
     protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
index d492708606..9b31f8795a 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
@@ -284,36 +284,38 @@ public class ListS3 extends AbstractS3Processor implements VerifiableProcessor {
 
 
     public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(Arrays.asList(
-        LISTING_STRATEGY,
-        TRACKING_STATE_CACHE,
-        INITIAL_LISTING_TARGET,
-        TRACKING_TIME_WINDOW,
-        BUCKET,
-        REGION,
-        ACCESS_KEY,
-        SECRET_KEY,
-        RECORD_WRITER,
-        MIN_AGE,
-        MAX_AGE,
-        BATCH_SIZE,
-        WRITE_OBJECT_TAGS,
-        WRITE_USER_METADATA,
-        CREDENTIALS_FILE,
-        AWS_CREDENTIALS_PROVIDER_SERVICE,
-        TIMEOUT,
-        SSL_CONTEXT_SERVICE,
-        ENDPOINT_OVERRIDE,
-        SIGNER_OVERRIDE,
-        PROXY_CONFIGURATION_SERVICE,
-        PROXY_HOST,
-        PROXY_HOST_PORT,
-        PROXY_USERNAME,
-        PROXY_PASSWORD,
-        DELIMITER,
-        PREFIX,
-        USE_VERSIONS,
-        LIST_TYPE,
-        REQUESTER_PAYS));
+            LISTING_STRATEGY,
+            TRACKING_STATE_CACHE,
+            INITIAL_LISTING_TARGET,
+            TRACKING_TIME_WINDOW,
+            BUCKET,
+            REGION,
+            ACCESS_KEY,
+            SECRET_KEY,
+            RECORD_WRITER,
+            MIN_AGE,
+            MAX_AGE,
+            BATCH_SIZE,
+            WRITE_OBJECT_TAGS,
+            WRITE_USER_METADATA,
+            CREDENTIALS_FILE,
+            AWS_CREDENTIALS_PROVIDER_SERVICE,
+            TIMEOUT,
+            SSL_CONTEXT_SERVICE,
+            ENDPOINT_OVERRIDE,
+            SIGNER_OVERRIDE,
+            S3_CUSTOM_SIGNER_CLASS_NAME,
+            S3_CUSTOM_SIGNER_MODULE_LOCATION,
+            PROXY_CONFIGURATION_SERVICE,
+            PROXY_HOST,
+            PROXY_HOST_PORT,
+            PROXY_USERNAME,
+            PROXY_PASSWORD,
+            DELIMITER,
+            PREFIX,
+            USE_VERSIONS,
+            LIST_TYPE,
+            REQUESTER_PAYS));
 
     public static final Set<Relationship> relationships = Collections.singleton(REL_SUCCESS);
 
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/PutS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/PutS3Object.java
index 44dba33550..6041e19ea8 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/PutS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/PutS3Object.java
@@ -276,12 +276,48 @@ public class PutS3Object extends AbstractS3Processor {
             .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
             .build();
 
-    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(
-            Arrays.asList(KEY, BUCKET, CONTENT_TYPE, CONTENT_DISPOSITION, CACHE_CONTROL, ACCESS_KEY, SECRET_KEY, CREDENTIALS_FILE, AWS_CREDENTIALS_PROVIDER_SERVICE,
-                    OBJECT_TAGS_PREFIX, REMOVE_TAG_PREFIX, STORAGE_CLASS, REGION, TIMEOUT, EXPIRATION_RULE_ID, FULL_CONTROL_USER_LIST, READ_USER_LIST, WRITE_USER_LIST,
-                    READ_ACL_LIST, WRITE_ACL_LIST, OWNER, CANNED_ACL, SSL_CONTEXT_SERVICE, ENDPOINT_OVERRIDE, SIGNER_OVERRIDE, MULTIPART_THRESHOLD, MULTIPART_PART_SIZE,
-                    MULTIPART_S3_AGEOFF_INTERVAL, MULTIPART_S3_MAX_AGE, MULTIPART_TEMP_DIR, SERVER_SIDE_ENCRYPTION, ENCRYPTION_SERVICE, USE_CHUNKED_ENCODING,
-                    USE_PATH_STYLE_ACCESS, PROXY_CONFIGURATION_SERVICE, PROXY_HOST, PROXY_HOST_PORT, PROXY_USERNAME, PROXY_PASSWORD));
+    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(Arrays.asList(
+            KEY,
+            BUCKET,
+            CONTENT_TYPE,
+            CONTENT_DISPOSITION,
+            CACHE_CONTROL,
+            ACCESS_KEY,
+            SECRET_KEY,
+            CREDENTIALS_FILE,
+            AWS_CREDENTIALS_PROVIDER_SERVICE,
+            OBJECT_TAGS_PREFIX,
+            REMOVE_TAG_PREFIX,
+            STORAGE_CLASS,
+            REGION,
+            TIMEOUT,
+            EXPIRATION_RULE_ID,
+            FULL_CONTROL_USER_LIST,
+            READ_USER_LIST,
+            WRITE_USER_LIST,
+            READ_ACL_LIST,
+            WRITE_ACL_LIST,
+            OWNER,
+            CANNED_ACL,
+            SSL_CONTEXT_SERVICE,
+            ENDPOINT_OVERRIDE,
+            SIGNER_OVERRIDE,
+            S3_CUSTOM_SIGNER_CLASS_NAME,
+            S3_CUSTOM_SIGNER_MODULE_LOCATION,
+            MULTIPART_THRESHOLD,
+            MULTIPART_PART_SIZE,
+            MULTIPART_S3_AGEOFF_INTERVAL,
+            MULTIPART_S3_MAX_AGE,
+            MULTIPART_TEMP_DIR,
+            SERVER_SIDE_ENCRYPTION,
+            ENCRYPTION_SERVICE,
+            USE_CHUNKED_ENCODING,
+            USE_PATH_STYLE_ACCESS,
+            PROXY_CONFIGURATION_SERVICE,
+            PROXY_HOST,
+            PROXY_HOST_PORT,
+            PROXY_USERNAME,
+            PROXY_PASSWORD));
 
     final static String S3_BUCKET_KEY = "s3.bucket";
     final static String S3_OBJECT_KEY = "s3.key";
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/TagS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/TagS3Object.java
index 7ea4f53129..85036d14a0 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/TagS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/TagS3Object.java
@@ -106,11 +106,29 @@ public class TagS3Object extends AbstractS3Processor {
             .required(false)
             .build();
 
-    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(
-            Arrays.asList(KEY, BUCKET, VERSION_ID, TAG_KEY, TAG_VALUE, APPEND_TAG, ACCESS_KEY, SECRET_KEY,
-                    CREDENTIALS_FILE, AWS_CREDENTIALS_PROVIDER_SERVICE, REGION, TIMEOUT, SSL_CONTEXT_SERVICE,
-                    ENDPOINT_OVERRIDE, SIGNER_OVERRIDE, PROXY_CONFIGURATION_SERVICE, PROXY_HOST, PROXY_HOST_PORT,
-                    PROXY_USERNAME, PROXY_PASSWORD));
+    public static final List<PropertyDescriptor> properties = Collections.unmodifiableList(Arrays.asList(
+            KEY,
+            BUCKET,
+            VERSION_ID,
+            TAG_KEY,
+            TAG_VALUE,
+            APPEND_TAG,
+            ACCESS_KEY,
+            SECRET_KEY,
+            CREDENTIALS_FILE,
+            AWS_CREDENTIALS_PROVIDER_SERVICE,
+            REGION,
+            TIMEOUT,
+            SSL_CONTEXT_SERVICE,
+            ENDPOINT_OVERRIDE,
+            SIGNER_OVERRIDE,
+            S3_CUSTOM_SIGNER_CLASS_NAME,
+            S3_CUSTOM_SIGNER_MODULE_LOCATION,
+            PROXY_CONFIGURATION_SERVICE,
+            PROXY_HOST,
+            PROXY_HOST_PORT,
+            PROXY_USERNAME,
+            PROXY_PASSWORD));
 
     @Override
     protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java
index 2f9ca8a733..c99bb73954 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java
@@ -36,8 +36,11 @@ import static org.apache.nifi.processors.aws.credentials.provider.factory.Creden
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_NAME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT;
-import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_REGION;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_MODULE_LOCATION;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_REGION;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_ENDPOINT;
+import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_STS_SIGNER_OVERRIDE;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.MAX_SESSION_TIME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.PROFILE_NAME;
 import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS;
@@ -62,8 +65,11 @@ public class MockAWSProcessor extends AbstractAWSCredentialsProviderProcessor<Am
             ASSUME_ROLE_EXTERNAL_ID,
             ASSUME_ROLE_PROXY_HOST,
             ASSUME_ROLE_PROXY_PORT,
+            ASSUME_ROLE_STS_REGION,
             ASSUME_ROLE_STS_ENDPOINT,
-            ASSUME_ROLE_REGION
+            ASSUME_ROLE_STS_SIGNER_OVERRIDE,
+            ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME,
+            ASSUME_ROLE_STS_CUSTOM_SIGNER_MODULE_LOCATION
     );
 
     @Override
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java
index fc07775b4e..064a61323e 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java
@@ -16,17 +16,21 @@
  */
 package org.apache.nifi.processors.aws.credentials.provider.factory;
 
+import com.amazonaws.SignableRequest;
+import com.amazonaws.auth.AWS4Signer;
 import com.amazonaws.auth.AWSCredentials;
 import com.amazonaws.auth.AWSCredentialsProvider;
 import com.amazonaws.auth.AnonymousAWSCredentials;
 import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
 import com.amazonaws.auth.PropertiesFileCredentialsProvider;
 import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider;
+import com.amazonaws.auth.Signer;
 import com.amazonaws.auth.profile.ProfileCredentialsProvider;
 import com.amazonaws.internal.StaticCredentialsProvider;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.processors.aws.credentials.provider.PropertiesCredentialsProvider;
 import org.apache.nifi.processors.aws.s3.FetchS3Object;
+import org.apache.nifi.processors.aws.signer.AwsSignerType;
 import org.apache.nifi.util.TestRunner;
 import org.apache.nifi.util.TestRunners;
 import org.junit.jupiter.api.Test;
@@ -41,6 +45,9 @@ import java.util.Map;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 /**
  * Tests of the validation and credentials provider capabilities of CredentialsProviderFactory.
@@ -52,7 +59,7 @@ public class TestCredentialsProviderFactory {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -71,7 +78,7 @@ public class TestCredentialsProviderFactory {
         runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "true");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -100,7 +107,7 @@ public class TestCredentialsProviderFactory {
         runner.setProperty(CredentialPropertyDescriptors.SECRET_KEY, "BogusSecretKey");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -133,7 +140,7 @@ public class TestCredentialsProviderFactory {
         runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -163,7 +170,7 @@ public class TestCredentialsProviderFactory {
 
         assertThrows(IllegalStateException.class, () -> factory.getAwsCredentialsProvider(properties));
 
-        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         final Map<PropertyDescriptor, String> properties2 = runner.getProcessContext().getProperties();
         final AwsCredentialsProvider credentialsProviderV2 = factory.getAwsCredentialsProvider(properties2);
         assertNotNull(credentialsProviderV2);
@@ -171,14 +178,6 @@ public class TestCredentialsProviderFactory {
                 credentialsProviderV2.getClass(), "credentials provider should be equal");
     }
 
-    @Test
-    public void testAssumeRoleCredentialsMissingARN() throws Throwable {
-        final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
-        runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
-        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
-        runner.assertNotValid();
-    }
-
     @Test
     public void testAssumeRoleCredentialsInvalidSessionTime() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
@@ -189,29 +188,13 @@ public class TestCredentialsProviderFactory {
         runner.assertNotValid();
     }
 
-    @Test
-    public void testAssumeRoleExternalIdMissingArnAndName() throws Throwable {
-        final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
-        runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
-        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_EXTERNAL_ID, "BogusExternalId");
-        runner.assertNotValid();
-    }
-
-    @Test
-    public void testAssumeRoleSTSEndpointMissingArnAndName() throws Throwable {
-        final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
-        runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
-        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_STS_ENDPOINT, "BogusSTSEndpoint");
-        runner.assertNotValid();
-    }
-
     @Test
     public void testAnonymousCredentials() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
         runner.setProperty(CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS, "true");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -239,7 +222,7 @@ public class TestCredentialsProviderFactory {
         runner.setProperty(CredentialPropertyDescriptors.PROFILE_NAME, "BogusProfile");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -258,12 +241,12 @@ public class TestCredentialsProviderFactory {
         runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
-        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_REGION, Region.US_WEST_2.id());
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_STS_REGION, Region.US_WEST_2.id());
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST, "proxy.company.com");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT, "8080");
         runner.assertValid();
 
-        Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
         final CredentialsProviderFactory factory = new CredentialsProviderFactory();
         final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
         assertNotNull(credentialsProvider);
@@ -280,6 +263,8 @@ public class TestCredentialsProviderFactory {
     public void testAssumeRoleMissingProxyHost() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
         runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT, "8080");
         runner.assertNotValid();
     }
@@ -288,6 +273,8 @@ public class TestCredentialsProviderFactory {
     public void testAssumeRoleMissingProxyPort() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
         runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST, "proxy.company.com");
         runner.assertNotValid();
     }
@@ -296,8 +283,51 @@ public class TestCredentialsProviderFactory {
     public void testAssumeRoleInvalidProxyPort() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
         runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_HOST, "proxy.company.com");
         runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_PROXY_PORT, "notIntPort");
         runner.assertNotValid();
     }
+
+    @Test
+    public void testAssumeRoleCredentialsWithCustomSigner() {
+        final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class);
+        runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession");
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_STS_SIGNER_OVERRIDE, AwsSignerType.CUSTOM_SIGNER.getValue());
+        runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_STS_CUSTOM_SIGNER_CLASS_NAME, CustomSTSSigner.class.getName());
+        runner.assertValid();
+
+        final Map<PropertyDescriptor, String> properties = runner.getProcessContext().getProperties();
+        final CredentialsProviderFactory factory = new CredentialsProviderFactory();
+
+        final Signer signerChecker = mock(Signer.class);
+        CustomSTSSigner.setSignerChecker(signerChecker);
+
+        final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties);
+
+        try {
+            credentialsProvider.getCredentials();
+        } catch (Exception e) {
+            // Expected to fail, we are only interested in the Signer
+        }
+
+        verify(signerChecker).sign(any(), any());
+    }
+
+    public static class CustomSTSSigner extends AWS4Signer {
+
+        private static final ThreadLocal<Signer> SIGNER_CHECKER = new ThreadLocal<>();
+
+        public static void setSignerChecker(Signer signerChecker) {
+            SIGNER_CHECKER.set(signerChecker);
+        }
+
+        @Override
+        public void sign(SignableRequest<?> request, AWSCredentials credentials) {
+            SIGNER_CHECKER.get().sign(request, credentials);
+        }
+    }
 }
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
index fd2dc183b9..3730e68a25 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerServiceTest.java
@@ -80,7 +80,7 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.addControllerService("awsCredentialsProvider", serviceImpl);
         runner.setProperty(serviceImpl, AbstractAWSProcessor.ACCESS_KEY, "awsAccessKey");
         runner.setProperty(serviceImpl, AbstractAWSProcessor.SECRET_KEY, "awsSecretKey");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN, "Role");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
         runner.enableControllerService(serviceImpl);
@@ -102,7 +102,7 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.addControllerService("awsCredentialsProvider", serviceImpl);
         runner.setProperty(serviceImpl, AbstractAWSProcessor.ACCESS_KEY, "awsAccessKey");
         runner.setProperty(serviceImpl, AbstractAWSProcessor.SECRET_KEY, "awsSecretKey");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN, "Role");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.MAX_SESSION_TIME, "1000");
@@ -125,7 +125,7 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.addControllerService("awsCredentialsProvider", serviceImpl);
         runner.setProperty(serviceImpl, AbstractAWSProcessor.ACCESS_KEY, "awsAccessKey");
         runner.setProperty(serviceImpl, AbstractAWSProcessor.SECRET_KEY, "awsSecretKey");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN, "Role");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.MAX_SESSION_TIME, "900");
@@ -141,7 +141,7 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.addControllerService("awsCredentialsProvider", serviceImpl);
         runner.setProperty(serviceImpl, AbstractAWSProcessor.ACCESS_KEY, "awsAccessKey");
         runner.setProperty(serviceImpl, AbstractAWSProcessor.SECRET_KEY, "awsSecretKey");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN, "Role");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.MAX_SESSION_TIME, "900");
@@ -188,18 +188,6 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.assertNotValid(serviceImpl);
     }
 
-    @Test
-    public void testKeysCredentialsProviderWithRoleNameOnlyInvalid() throws Throwable {
-        final TestRunner runner = TestRunners.newTestRunner(FetchS3Object.class);
-        final AWSCredentialsProviderControllerService serviceImpl = new AWSCredentialsProviderControllerService();
-        runner.addControllerService("awsCredentialsProvider", serviceImpl);
-        runner.setProperty(serviceImpl, AbstractAWSProcessor.ACCESS_KEY, "awsAccessKey");
-        runner.setProperty(serviceImpl, AbstractAWSProcessor.SECRET_KEY, "awsSecretKey");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
-
-        runner.assertNotValid(serviceImpl);
-    }
-
     @Test
     public void testFileCredentialsProviderWithRole() throws Throwable {
         final TestRunner runner = TestRunners.newTestRunner(FetchS3Object.class);
@@ -207,7 +195,7 @@ public class AWSCredentialsProviderControllerServiceTest {
         runner.addControllerService("awsCredentialsProvider", serviceImpl);
         runner.setProperty(serviceImpl, AbstractAWSProcessor.CREDENTIALS_FILE,
                 "src/test/resources/mock-aws-credentials.properties");
-        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_REGION, Region.US_WEST_1.id());
+        runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_STS_REGION, Region.US_WEST_1.id());
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_ARN, "Role");
         runner.setProperty(serviceImpl, AWSCredentialsProviderControllerService.ASSUME_ROLE_NAME, "RoleName");
         runner.enableControllerService(serviceImpl);
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITListS3.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITListS3.java
index bb16e3a522..d89b3a8870 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITListS3.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITListS3.java
@@ -41,6 +41,7 @@ public class ITListS3 extends AbstractS3IT {
         putTestFile("a", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("b/c", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("d/e", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
@@ -62,6 +63,7 @@ public class ITListS3 extends AbstractS3IT {
         putTestFile("a", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("b/c", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("d/e", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
@@ -91,6 +93,7 @@ public class ITListS3 extends AbstractS3IT {
         putTestFile("a", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("b/c", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("d/e", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
@@ -111,6 +114,7 @@ public class ITListS3 extends AbstractS3IT {
         putTestFile("a", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("b/c", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("d/e", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
@@ -131,6 +135,7 @@ public class ITListS3 extends AbstractS3IT {
         putTestFile("a", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("b/c", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
         putTestFile("d/e", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME));
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
@@ -153,12 +158,13 @@ public class ITListS3 extends AbstractS3IT {
         objectTags.add(new Tag("dummytag1", "dummyvalue1"));
         objectTags.add(new Tag("dummytag2", "dummyvalue2"));
 
-        putFileWithObjectTag("b/fileWithTag", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME), objectTags);
+        putFileWithObjectTag("t/fileWithTag", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME), objectTags);
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
         runner.setProperty(ListS3.CREDENTIALS_FILE, CREDENTIALS_FILE);
-        runner.setProperty(ListS3.PREFIX, "b/");
+        runner.setProperty(ListS3.PREFIX, "t/");
         runner.setProperty(ListS3.REGION, REGION);
         runner.setProperty(ListS3.BUCKET, BUCKET_NAME);
         runner.setProperty(ListS3.WRITE_OBJECT_TAGS, "true");
@@ -169,7 +175,7 @@ public class ITListS3 extends AbstractS3IT {
 
         MockFlowFile flowFiles = runner.getFlowFilesForRelationship(ListS3.REL_SUCCESS).get(0);
 
-        flowFiles.assertAttributeEquals("filename", "b/fileWithTag");
+        flowFiles.assertAttributeEquals("filename", "t/fileWithTag");
         flowFiles.assertAttributeExists("s3.tag.dummytag1");
         flowFiles.assertAttributeExists("s3.tag.dummytag2");
         flowFiles.assertAttributeEquals("s3.tag.dummytag1", "dummyvalue1");
@@ -182,12 +188,13 @@ public class ITListS3 extends AbstractS3IT {
         userMetadata.put("dummy.metadata.1", "dummyvalue1");
         userMetadata.put("dummy.metadata.2", "dummyvalue2");
 
-        putFileWithUserMetadata("b/fileWithUserMetadata", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME), userMetadata);
+        putFileWithUserMetadata("m/fileWithUserMetadata", getFileFromResourceName(SAMPLE_FILE_RESOURCE_NAME), userMetadata);
+        waitForFilesAvailable();
 
         final TestRunner runner = TestRunners.newTestRunner(new ListS3());
 
         runner.setProperty(ListS3.CREDENTIALS_FILE, CREDENTIALS_FILE);
-        runner.setProperty(ListS3.PREFIX, "b/");
+        runner.setProperty(ListS3.PREFIX, "m/");
         runner.setProperty(ListS3.REGION, REGION);
         runner.setProperty(ListS3.BUCKET, BUCKET_NAME);
         runner.setProperty(ListS3.WRITE_USER_METADATA, "true");
@@ -198,11 +205,19 @@ public class ITListS3 extends AbstractS3IT {
 
         MockFlowFile flowFiles = runner.getFlowFilesForRelationship(ListS3.REL_SUCCESS).get(0);
 
-        flowFiles.assertAttributeEquals("filename", "b/fileWithUserMetadata");
+        flowFiles.assertAttributeEquals("filename", "m/fileWithUserMetadata");
         flowFiles.assertAttributeExists("s3.user.metadata.dummy.metadata.1");
         flowFiles.assertAttributeExists("s3.user.metadata.dummy.metadata.2");
         flowFiles.assertAttributeEquals("s3.user.metadata.dummy.metadata.1", "dummyvalue1");
         flowFiles.assertAttributeEquals("s3.user.metadata.dummy.metadata.2", "dummyvalue2");
     }
 
+    private void waitForFilesAvailable() {
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
 }
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITPutS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITPutS3Object.java
index c999a4e002..7347b08dc0 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITPutS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/ITPutS3Object.java
@@ -62,6 +62,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.SecureRandom;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -577,7 +578,7 @@ public class ITPutS3Object extends AbstractS3IT {
         runner.run();
 
         assertEquals(BUCKET_NAME, context.getProperty(PutS3Object.BUCKET).toString());
-        assertEquals(TESTKEY, context.getProperty(PutS3Object.KEY).evaluateAttributeExpressions().toString());
+        assertEquals(TESTKEY, context.getProperty(PutS3Object.KEY).evaluateAttributeExpressions(Collections.emptyMap()).toString());
         assertEquals(TEST_ENDPOINT, context.getProperty(PutS3Object.ENDPOINT_OVERRIDE).toString());
 
         String s3url = ((TestablePutS3Object)processor).testable_getClient().getResourceUrl(BUCKET_NAME, TESTKEY);
@@ -598,7 +599,7 @@ public class ITPutS3Object extends AbstractS3IT {
         runner.setProperty(PutS3Object.KEY, AbstractS3IT.SAMPLE_FILE_RESOURCE_NAME);
 
         assertEquals(BUCKET_NAME, context.getProperty(PutS3Object.BUCKET).toString());
-        assertEquals(SAMPLE_FILE_RESOURCE_NAME, context.getProperty(PutS3Object.KEY).evaluateAttributeExpressions().toString());
+        assertEquals(SAMPLE_FILE_RESOURCE_NAME, context.getProperty(PutS3Object.KEY).evaluateAttributeExpressions(Collections.emptyMap()).toString());
         assertEquals(TEST_PARTSIZE_LONG.longValue(),
                 context.getProperty(PutS3Object.MULTIPART_PART_SIZE).asDataSize(DataUnit.B).longValue());
     }
@@ -609,7 +610,7 @@ public class ITPutS3Object extends AbstractS3IT {
         final TestRunner runner = TestRunners.newTestRunner(processor);
 
         final String bucket = runner.getProcessContext().getProperty(PutS3Object.BUCKET).getValue();
-        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).getValue();
+        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).evaluateAttributeExpressions(Collections.emptyMap()).getValue();
         final String cacheKey1 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key;
         final String cacheKey2 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-v2";
         final String cacheKey3 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-v3";
@@ -682,7 +683,7 @@ public class ITPutS3Object extends AbstractS3IT {
         final TestRunner runner = TestRunners.newTestRunner(processor);
 
         final String bucket = runner.getProcessContext().getProperty(PutS3Object.BUCKET).getValue();
-        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).getValue();
+        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).evaluateAttributeExpressions(Collections.emptyMap()).getValue();
         final String cacheKey1 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-bv1";
         final String cacheKey2 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-bv2";
         final String cacheKey3 = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-bv3";
@@ -758,7 +759,7 @@ public class ITPutS3Object extends AbstractS3IT {
         final TestRunner runner = TestRunners.newTestRunner(processor);
 
         final String bucket = runner.getProcessContext().getProperty(PutS3Object.BUCKET).getValue();
-        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).getValue();
+        final String key = runner.getProcessContext().getProperty(PutS3Object.KEY).evaluateAttributeExpressions(Collections.emptyMap()).getValue();
         final String cacheKey = runner.getProcessor().getIdentifier() + "/" + bucket + "/" + key + "-sr";
 
         final List<MultipartUpload> uploadList = new ArrayList<>();
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestDeleteS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestDeleteS3Object.java
index f5c266bc43..a4cd19e56a 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestDeleteS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestDeleteS3Object.java
@@ -137,7 +137,7 @@ public class TestDeleteS3Object {
     public void testGetPropertyDescriptors() {
         DeleteS3Object processor = new DeleteS3Object();
         List<PropertyDescriptor> pd = processor.getSupportedPropertyDescriptors();
-        assertEquals(23, pd.size(), "size should be eq");
+        assertEquals(25, pd.size(), "size should be eq");
         assertTrue(pd.contains(processor.ACCESS_KEY));
         assertTrue(pd.contains(processor.AWS_CREDENTIALS_PROVIDER_SERVICE));
         assertTrue(pd.contains(processor.BUCKET));
@@ -151,6 +151,8 @@ public class TestDeleteS3Object {
         assertTrue(pd.contains(processor.REGION));
         assertTrue(pd.contains(processor.SECRET_KEY));
         assertTrue(pd.contains(processor.SIGNER_OVERRIDE));
+        assertTrue(pd.contains(processor.S3_CUSTOM_SIGNER_CLASS_NAME));
+        assertTrue(pd.contains(processor.S3_CUSTOM_SIGNER_MODULE_LOCATION));
         assertTrue(pd.contains(processor.SSL_CONTEXT_SERVICE));
         assertTrue(pd.contains(processor.TIMEOUT));
         assertTrue(pd.contains(processor.VERSION_ID));
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestFetchS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestFetchS3Object.java
index 815fcfd98d..26a010ac80 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestFetchS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestFetchS3Object.java
@@ -362,7 +362,7 @@ public class TestFetchS3Object {
     public void testGetPropertyDescriptors() {
         FetchS3Object processor = new FetchS3Object();
         List<PropertyDescriptor> pd = processor.getSupportedPropertyDescriptors();
-        assertEquals("size should be eq", 21, pd.size());
+        assertEquals("size should be eq", 23, pd.size());
         assertTrue(pd.contains(FetchS3Object.ACCESS_KEY));
         assertTrue(pd.contains(FetchS3Object.AWS_CREDENTIALS_PROVIDER_SERVICE));
         assertTrue(pd.contains(FetchS3Object.BUCKET));
@@ -372,6 +372,8 @@ public class TestFetchS3Object {
         assertTrue(pd.contains(FetchS3Object.REGION));
         assertTrue(pd.contains(FetchS3Object.SECRET_KEY));
         assertTrue(pd.contains(FetchS3Object.SIGNER_OVERRIDE));
+        assertTrue(pd.contains(FetchS3Object.S3_CUSTOM_SIGNER_CLASS_NAME));
+        assertTrue(pd.contains(FetchS3Object.S3_CUSTOM_SIGNER_MODULE_LOCATION));
         assertTrue(pd.contains(FetchS3Object.SSL_CONTEXT_SERVICE));
         assertTrue(pd.contains(FetchS3Object.TIMEOUT));
         assertTrue(pd.contains(FetchS3Object.VERSION_ID));
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestPutS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestPutS3Object.java
index ac1bac58b1..71b24af2e6 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestPutS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestPutS3Object.java
@@ -19,7 +19,11 @@ package org.apache.nifi.processors.aws.s3;
 import com.amazonaws.ClientConfiguration;
 import com.amazonaws.auth.AWSCredentialsProvider;
 import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
+import com.amazonaws.auth.Signer;
+import com.amazonaws.auth.SignerFactory;
+import com.amazonaws.auth.SignerParams;
 import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.internal.AWSS3V4Signer;
 import com.amazonaws.services.s3.model.AmazonS3Exception;
 import com.amazonaws.services.s3.model.ListMultipartUploadsRequest;
 import com.amazonaws.services.s3.model.MultipartUploadListing;
@@ -33,6 +37,7 @@ import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.flowfile.attributes.CoreAttributes;
 import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processors.aws.signer.AwsSignerType;
 import org.apache.nifi.util.MockFlowFile;
 import org.apache.nifi.util.TestRunner;
 import org.apache.nifi.util.TestRunners;
@@ -51,7 +56,10 @@ import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 
 
 public class TestPutS3Object {
@@ -62,7 +70,7 @@ public class TestPutS3Object {
 
     @BeforeEach
     public void setUp() {
-        mockS3Client = Mockito.mock(AmazonS3Client.class);
+        mockS3Client = mock(AmazonS3Client.class);
         putS3Object = new PutS3Object() {
             @Override
             protected AmazonS3Client getClient() {
@@ -116,11 +124,11 @@ public class TestPutS3Object {
         final TestRunner runner = TestRunners.newTestRunner(processor);
 
         final List<AllowableValue> allowableSignerValues = PutS3Object.SIGNER_OVERRIDE.getAllowableValues();
-        final String defaultSignerValue = PutS3Object.SIGNER_OVERRIDE.getDefaultValue();
+        final String customSignerValue = AwsSignerType.CUSTOM_SIGNER.getValue(); // Custom Signer is tested separately
 
         for (AllowableValue allowableSignerValue : allowableSignerValues) {
             String signerType = allowableSignerValue.getValue();
-            if (!signerType.equals(defaultSignerValue)) {
+            if (!signerType.equals(customSignerValue)) {
                 runner.setProperty(PutS3Object.SIGNER_OVERRIDE, signerType);
                 ProcessContext context = runner.getProcessContext();
                 assertDoesNotThrow(() -> processor.createClient(context, credentialsProvider, config));
@@ -241,7 +249,7 @@ public class TestPutS3Object {
     public void testGetPropertyDescriptors() {
         PutS3Object processor = new PutS3Object();
         List<PropertyDescriptor> pd = processor.getSupportedPropertyDescriptors();
-        assertEquals(39, pd.size(), "size should be eq");
+        assertEquals(41, pd.size(), "size should be eq");
         assertTrue(pd.contains(PutS3Object.ACCESS_KEY));
         assertTrue(pd.contains(PutS3Object.AWS_CREDENTIALS_PROVIDER_SERVICE));
         assertTrue(pd.contains(PutS3Object.BUCKET));
@@ -256,6 +264,8 @@ public class TestPutS3Object {
         assertTrue(pd.contains(PutS3Object.REGION));
         assertTrue(pd.contains(PutS3Object.SECRET_KEY));
         assertTrue(pd.contains(PutS3Object.SIGNER_OVERRIDE));
+        assertTrue(pd.contains(PutS3Object.S3_CUSTOM_SIGNER_CLASS_NAME));
+        assertTrue(pd.contains(PutS3Object.S3_CUSTOM_SIGNER_MODULE_LOCATION));
         assertTrue(pd.contains(PutS3Object.SSL_CONTEXT_SERVICE));
         assertTrue(pd.contains(PutS3Object.TIMEOUT));
         assertTrue(pd.contains(PutS3Object.EXPIRATION_RULE_ID));
@@ -282,4 +292,28 @@ public class TestPutS3Object {
         assertTrue(pd.contains(PutS3Object.MULTIPART_S3_MAX_AGE));
         assertTrue(pd.contains(PutS3Object.MULTIPART_TEMP_DIR));
     }
+
+    @Test
+    public void testCustomSigner() {
+        final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain();
+        final ClientConfiguration config = new ClientConfiguration();
+        final PutS3Object processor = new PutS3Object();
+        final TestRunner runner = TestRunners.newTestRunner(processor);
+
+        runner.setProperty(PutS3Object.SIGNER_OVERRIDE, AwsSignerType.CUSTOM_SIGNER.getValue());
+        runner.setProperty(PutS3Object.S3_CUSTOM_SIGNER_CLASS_NAME, CustomS3Signer.class.getName());
+
+        ProcessContext context = runner.getProcessContext();
+        processor.createClient(context, credentialsProvider, config);
+
+        final String signerName = config.getSignerOverride();
+        assertNotNull(signerName);
+        final Signer signer = SignerFactory.createSigner(signerName, new SignerParams("s3", "us-west-2"));
+        assertNotNull(signer);
+        assertSame(CustomS3Signer.class, signer.getClass());
+    }
+
+    public static class CustomS3Signer extends AWSS3V4Signer {
+
+    }
 }
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestTagS3Object.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestTagS3Object.java
index 0dfd9b2706..4d376a6609 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestTagS3Object.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/s3/TestTagS3Object.java
@@ -244,7 +244,7 @@ public class TestTagS3Object {
     public void testGetPropertyDescriptors() throws Exception {
         TagS3Object processor = new TagS3Object();
         List<PropertyDescriptor> pd = processor.getSupportedPropertyDescriptors();
-        assertEquals(20, pd.size(), "size should be eq");
+        assertEquals(22, pd.size(), "size should be eq");
         assertTrue(pd.contains(TagS3Object.ACCESS_KEY));
         assertTrue(pd.contains(TagS3Object.AWS_CREDENTIALS_PROVIDER_SERVICE));
         assertTrue(pd.contains(TagS3Object.BUCKET));
@@ -254,6 +254,8 @@ public class TestTagS3Object {
         assertTrue(pd.contains(TagS3Object.REGION));
         assertTrue(pd.contains(TagS3Object.SECRET_KEY));
         assertTrue(pd.contains(TagS3Object.SIGNER_OVERRIDE));
+        assertTrue(pd.contains(TagS3Object.S3_CUSTOM_SIGNER_CLASS_NAME));
+        assertTrue(pd.contains(TagS3Object.S3_CUSTOM_SIGNER_MODULE_LOCATION));
         assertTrue(pd.contains(TagS3Object.SSL_CONTEXT_SERVICE));
         assertTrue(pd.contains(TagS3Object.TIMEOUT));
         assertTrue(pd.contains(ProxyConfigurationService.PROXY_CONFIGURATION_SERVICE));