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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new a6a8441fb16 NIFI-15667 Supported plain text secrets in AWS Secrets 
Manager Parameter Provider (#10962)
a6a8441fb16 is described below

commit a6a8441fb16e68899f84352b47f1d5274bcc5760
Author: Pierre Villard <[email protected]>
AuthorDate: Tue Mar 10 19:28:23 2026 +0100

    NIFI-15667 Supported plain text secrets in AWS Secrets Manager Parameter 
Provider (#10962)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../aws/AwsSecretsManagerParameterProvider.java    | 31 ++++----
 .../additionalDetails.md                           | 16 +++++
 .../TestAwsSecretsManagerParameterProvider.java    | 84 ++++++++++++++++++++++
 3 files changed, 115 insertions(+), 16 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java
index e1371aa95b7..ff932a29836 100644
--- 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java
@@ -289,31 +289,30 @@ public class AwsSecretsManagerParameterProvider extends 
AbstractParameterProvide
                 return groups;
             }
 
-            final ObjectNode secretObject = 
parseSecret(getSecretValueResponse.secretString());
+            final String secretString = getSecretValueResponse.secretString();
+            final ObjectNode secretObject = parseSecret(secretString);
             if (secretObject == null) {
-                getLogger().debug("Secret [{}] is not in the expected JSON 
key/value format", secretName);
-                return groups;
-            }
-
-            for (final Map.Entry<String, JsonNode> field : 
secretObject.properties()) {
-                final String parameterName = field.getKey();
-                final JsonNode valueNode = field.getValue();
-                if (!valueNode.isValueNode() || valueNode.isNull()) {
-                    getLogger().debug("Secret [{}] Parameter [{}] is null or 
not a supported value type", secretName, parameterName);
-                    continue;
+                getLogger().debug("Secret [{}] is not a JSON object, treating 
as plain text parameter", secretName);
+                parameters.add(createParameter(secretName, secretString, 
tags));
+            } else {
+                for (final Map.Entry<String, JsonNode> field : 
secretObject.properties()) {
+                    final String parameterName = field.getKey();
+                    final JsonNode valueNode = field.getValue();
+                    if (!valueNode.isValueNode() || valueNode.isNull()) {
+                        getLogger().debug("Secret [{}] Parameter [{}] is null 
or not a supported value type", secretName, parameterName);
+                        continue;
+                    }
+                    parameters.add(createParameter(parameterName, 
valueNode.asText(), tags));
                 }
-                final String parameterValue = valueNode.asText();
-
-                parameters.add(createParameter(parameterName, parameterValue, 
tags));
             }
 
             groups.add(new ParameterGroup(secretName, parameters));
 
             return groups;
         } catch (final ResourceNotFoundException e) {
-            throw new IllegalStateException(String.format("Secret %s not 
found", secretName), e);
+            throw new IllegalStateException("Secret %s not 
found".formatted(secretName), e);
         } catch (final SecretsManagerException e) {
-            throw new IllegalStateException("Error retrieving secret " + 
secretName, e);
+            throw new IllegalStateException("Error retrieving secret 
%s".formatted(secretName), e);
         }
     }
 
diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider/additionalDetails.md
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider/additionalDetails.md
index 842f11ee4c8..af25c4a01e1 100644
--- 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider/additionalDetails.md
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider/additionalDetails.md
@@ -35,6 +35,22 @@ aws secretsmanager create-secret --name "\[Context\]" 
--secret-string '{ "\[Para
 In this example, \[Context\] should be the intended name of the Parameter 
Context, \[Param\] and \[Param2\] should be
 parameter names, and \[secretValue\] and \[secretValue2\] should be the values 
of each respective parameter.
 
+### Plain Text Secrets
+
+Secrets that are not stored as JSON key/value pairs are also supported. When a 
secret value is not a JSON object (for
+example, a PEM-encoded private key or any other plain text string), it is 
treated as a single parameter whose name is the
+secret name and whose value is the entire secret string. The secret name is 
also used as the Parameter Context name.
+
+For example, to store a PEM key as a secret:
+
+aws secretsmanager create-secret --name "my-private-key" --secret-string 
file://path/to/key.pem
+
+This produces a Parameter Context named "my-private-key" containing a single 
parameter also named "my-private-key" with
+the contents of the PEM file as the value.
+
+Both JSON and plain text secrets can be mixed within the same Secret Name 
Pattern. Each secret is automatically detected
+and handled according to its format.
+
 ### Configuring the Parameter Provider
 
 AWS Secrets must be explicitly matched in the "Secret Name Pattern" property 
in order for them to be fetched. This
diff --git 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/test/java/org/apache/nifi/parameter/aws/TestAwsSecretsManagerParameterProvider.java
 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/test/java/org/apache/nifi/parameter/aws/TestAwsSecretsManagerParameterProvider.java
index ab1e22c3afa..e5ffa279a8c 100644
--- 
a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/test/java/org/apache/nifi/parameter/aws/TestAwsSecretsManagerParameterProvider.java
+++ 
b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-parameter-providers/src/test/java/org/apache/nifi/parameter/aws/TestAwsSecretsManagerParameterProvider.java
@@ -310,6 +310,90 @@ public class TestAwsSecretsManagerParameterProvider {
         }
     }
 
+    @Test
+    public void testFetchPlainTextSecretEnumeration() throws 
InitializationException {
+        final String secretName = "PlainTextSecret";
+        final String plainTextContent = "Lorem ipsum dolor sit amet, 
consectetur adipiscing elit";
+
+        final SecretsManagerClient secretsManager = 
mock(SecretsManagerClient.class);
+
+        final GetSecretValueResponse response = 
GetSecretValueResponse.builder()
+                .name(secretName)
+                .secretString(plainTextContent)
+                .build();
+        
when(secretsManager.getSecretValue(argThat(matchesGetSecretValueRequest(secretName)))).thenReturn(response);
+
+        final DescribeSecretResponse describeResponse = 
DescribeSecretResponse.builder()
+                .name(secretName)
+                .build();
+        
when(secretsManager.describeSecret(argThat(matchesDescribeSecretRequest(secretName)))).thenReturn(describeResponse);
+
+        final List<ParameterGroup> parameterGroups = 
runProviderTest(secretsManager, 1,
+                ConfigVerificationResult.Outcome.SUCCESSFUL, "ENUMERATION", 
secretName);
+
+        assertEquals(1, parameterGroups.size());
+        final ParameterGroup group = parameterGroups.get(0);
+        assertEquals(secretName, group.getGroupName());
+        assertEquals(1, group.getParameters().size());
+
+        final Parameter parameter = group.getParameters().get(0);
+        assertEquals(secretName, parameter.getDescriptor().getName());
+        assertEquals(plainTextContent, parameter.getValue());
+    }
+
+    @Test
+    public void testFetchMixedJsonAndPlainTextSecretsPattern() throws 
InitializationException {
+        final String plainTextSecretName = "PlainTextSecret";
+        final String jsonSecretName = "JsonSecret";
+        final String plainTextContent = "Sed ut perspiciatis unde omnis iste 
natus error sit voluptatem";
+        final String jsonContent = "{ \"serverHost\": \"db.example.com\", 
\"serverPort\": \"5432\" }";
+
+        final SecretsManagerClient secretsManager = 
mock(SecretsManagerClient.class);
+
+        final SecretListEntry plainTextEntry = 
SecretListEntry.builder().name(plainTextSecretName).build();
+        final SecretListEntry jsonEntry = 
SecretListEntry.builder().name(jsonSecretName).build();
+
+        final ListSecretsResponse firstPage = mock(ListSecretsResponse.class);
+        when(firstPage.secretList()).thenReturn(List.of(plainTextEntry, 
jsonEntry));
+        when(firstPage.nextToken()).thenReturn(null);
+        
when(secretsManager.listSecrets(argThat(ListSecretsRequestMatcher.hasToken(null)))).thenReturn(firstPage);
+
+        final GetSecretValueResponse plainTextResponse = 
GetSecretValueResponse.builder()
+                .name(plainTextSecretName)
+                .secretString(plainTextContent)
+                .build();
+        
when(secretsManager.getSecretValue(argThat(matchesGetSecretValueRequest(plainTextSecretName)))).thenReturn(plainTextResponse);
+
+        final GetSecretValueResponse jsonResponse = 
GetSecretValueResponse.builder()
+                .name(jsonSecretName)
+                .secretString(jsonContent)
+                .build();
+        
when(secretsManager.getSecretValue(argThat(matchesGetSecretValueRequest(jsonSecretName)))).thenReturn(jsonResponse);
+
+        final List<ParameterGroup> parameterGroups = 
runProviderTest(secretsManager, 3,
+                ConfigVerificationResult.Outcome.SUCCESSFUL, "PATTERN", null);
+
+        assertEquals(2, parameterGroups.size());
+
+        final ParameterGroup plainTextGroup = parameterGroups.stream()
+                .filter(g -> plainTextSecretName.equals(g.getGroupName()))
+                .findFirst()
+                .orElseThrow();
+        assertEquals(1, plainTextGroup.getParameters().size());
+        assertEquals(plainTextSecretName, 
plainTextGroup.getParameters().get(0).getDescriptor().getName());
+        assertEquals(plainTextContent, 
plainTextGroup.getParameters().get(0).getValue());
+
+        final ParameterGroup jsonGroup = parameterGroups.stream()
+                .filter(g -> jsonSecretName.equals(g.getGroupName()))
+                .findFirst()
+                .orElseThrow();
+        assertEquals(2, jsonGroup.getParameters().size());
+        final Map<String, String> jsonParams = 
jsonGroup.getParameters().stream()
+                .collect(Collectors.toMap(p -> p.getDescriptor().getName(), 
Parameter::getValue));
+        assertEquals("db.example.com", jsonParams.get("serverHost"));
+        assertEquals("5432", jsonParams.get("serverPort"));
+    }
+
     private AwsSecretsManagerParameterProvider getParameterProvider() {
         return spy(new AwsSecretsManagerParameterProvider());
     }

Reply via email to