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

pvillard 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 e6266cb123 NIFI-15032 Fixed Key encoding for Snowflake Key Pair 
Authentication
e6266cb123 is described below

commit e6266cb123aa4246202bf89b6100b106be9108d2
Author: exceptionfactory <[email protected]>
AuthorDate: Thu Oct 2 15:04:36 2025 -0500

    NIFI-15032 Fixed Key encoding for Snowflake Key Pair Authentication
    
    - Added PEM formatting and Base64 encoding for Private Key
    
    Signed-off-by: Pierre Villard <[email protected]>
    
    This closes #10375.
---
 .../nifi-snowflake-services/pom.xml                |   5 +
 .../service/SnowflakeComputingConnectionPool.java  |  15 ++-
 .../SnowflakeComputingConnectionPoolTest.java      | 113 +++++++++++++++++++++
 3 files changed, 131 insertions(+), 2 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/pom.xml 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/pom.xml
index acff34c3db..a0ef5738c6 100644
--- 
a/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/pom.xml
+++ 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/pom.xml
@@ -79,5 +79,10 @@
             <artifactId>nifi-kerberos-user-service-api</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/main/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPool.java
 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/main/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPool.java
index e03838b64c..5547c871cd 100644
--- 
a/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/main/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPool.java
+++ 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/main/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPool.java
@@ -49,6 +49,7 @@ import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.snowflake.service.util.ConnectionUrlFormat;
 import org.apache.nifi.snowflake.service.util.ConnectionUrlFormatParameters;
 
+import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.sql.Connection;
 import java.sql.Driver;
@@ -168,6 +169,8 @@ public class SnowflakeComputingConnectionPool extends 
AbstractDBCPConnectionPool
 
     private static final String AUTHENTICATOR_SNOWFLAKE_JWT = "SNOWFLAKE_JWT";
 
+    private static final String PEM_CONTENT_FORMAT = "-----BEGIN PRIVATE 
KEY-----%n%s%n-----END PRIVATE KEY-----%n";
+
     private volatile OAuth2AccessTokenProvider accessTokenProvider;
 
     private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
List.of(
@@ -310,8 +313,7 @@ public class SnowflakeComputingConnectionPool extends 
AbstractDBCPConnectionPool
         if (privateKeyServiceProperty.isSet()) {
             final PrivateKeyService privateKeyService = 
privateKeyServiceProperty.asControllerService(PrivateKeyService.class);
             final PrivateKey privateKey = privateKeyService.getPrivateKey();
-            final byte[] privateKeyEncoded = privateKey.getEncoded();
-            final String privateKeyBase64 = 
Base64.getEncoder().encodeToString(privateKeyEncoded);
+            final String privateKeyBase64 = getPrivateKeyBase64(privateKey);
             connectionProperties.put(PRIVATE_KEY_BASE64.getPropertyKey(), 
privateKeyBase64);
             connectionProperties.put(AUTHENTICATOR.getPropertyKey(), 
AUTHENTICATOR_SNOWFLAKE_JWT);
         }
@@ -362,4 +364,13 @@ public class SnowflakeComputingConnectionPool extends 
AbstractDBCPConnectionPool
                 
context.getProperty(SNOWFLAKE_CLOUD_TYPE).evaluateAttributeExpressions().getValue()
         );
     }
+
+    private String getPrivateKeyBase64(final PrivateKey privateKey) {
+        final byte[] privateKeyEncoded = privateKey.getEncoded();
+        final Base64.Encoder encoder = Base64.getEncoder();
+        final String privateKeyEncodedBase64 = 
encoder.encodeToString(privateKeyEncoded);
+        final String pemPrivateKey = 
PEM_CONTENT_FORMAT.formatted(privateKeyEncodedBase64);
+        final byte[] pemPrivateKeyBinary = 
pemPrivateKey.getBytes(StandardCharsets.UTF_8);
+        return encoder.encodeToString(pemPrivateKeyBinary);
+    }
 }
diff --git 
a/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/test/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPoolTest.java
 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/test/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPoolTest.java
new file mode 100644
index 0000000000..671dc46fcf
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-snowflake-bundle/nifi-snowflake-services/src/test/java/org/apache/nifi/snowflake/service/SnowflakeComputingConnectionPoolTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.snowflake.service;
+
+import net.snowflake.client.core.SFSessionProperty;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.key.service.api.PrivateKeyService;
+import org.apache.nifi.util.MockConfigurationContext;
+import org.apache.nifi.util.MockProcessContext;
+import org.apache.nifi.util.NoOpProcessor;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class SnowflakeComputingConnectionPoolTest {
+
+    private static final String PRIVATE_KEY_SERVICE_ID = 
PrivateKeyService.class.getSimpleName();
+
+    private static final String RSA_ALGORITHM = "RSA";
+
+    private static final String SNOWFLAKE_JWT = "SNOWFLAKE_JWT";
+
+    private static final String PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
+
+    private static final String PEM_HEADER_BASE64 = 
Base64.getEncoder().encodeToString(PEM_HEADER.getBytes(StandardCharsets.UTF_8));
+
+    private static final String PEM_CONTENT_FORMAT = "%s%n%s%n-----END PRIVATE 
KEY-----%n";
+
+    private static PrivateKey privateKey;
+
+    private static String pemPrivateKeyBase64;
+
+    @Mock
+    private PrivateKeyService privateKeyService;
+
+    private SnowflakeComputingConnectionPool pool;
+
+    @BeforeAll
+    static void setPrivateKey() throws GeneralSecurityException {
+        final KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance(RSA_ALGORITHM);
+        final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        privateKey = keyPair.getPrivate();
+        final byte[] privateKeyEncoded = privateKey.getEncoded();
+        final String privateKeyEncodedBase64 = 
Base64.getEncoder().encodeToString(privateKeyEncoded);
+        final String pemPrivateKey = PEM_CONTENT_FORMAT.formatted(PEM_HEADER, 
privateKeyEncodedBase64);
+        pemPrivateKeyBase64 = 
Base64.getEncoder().encodeToString(pemPrivateKey.getBytes(StandardCharsets.UTF_8));
+    }
+
+    @BeforeEach
+    void setPool() {
+        pool = new SnowflakeComputingConnectionPool();
+    }
+
+    @Test
+    void testGetConnectionPropertiesPrivateKeyService() {
+        final Map<PropertyDescriptor, String> properties = new HashMap<>();
+        properties.put(SnowflakeComputingConnectionPool.PRIVATE_KEY_SERVICE, 
PRIVATE_KEY_SERVICE_ID);
+
+        final MockProcessContext controllerServiceLookup = new 
MockProcessContext(new NoOpProcessor());
+        
when(privateKeyService.getIdentifier()).thenReturn(PRIVATE_KEY_SERVICE_ID);
+        controllerServiceLookup.addControllerService(privateKeyService, 
Map.of(), null);
+
+        final Map<String, String> environmentVariables = Map.of();
+        final MockConfigurationContext context = new 
MockConfigurationContext(properties, controllerServiceLookup, 
environmentVariables);
+
+        when(privateKeyService.getPrivateKey()).thenReturn(privateKey);
+        final Map<String, String> connectionProperties = 
pool.getConnectionProperties(context);
+        assertNotNull(connectionProperties);
+        assertFalse(connectionProperties.isEmpty());
+
+        final String authenticator = 
connectionProperties.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey());
+        assertEquals(SNOWFLAKE_JWT, authenticator);
+
+        final String privateKeyBase64 = 
connectionProperties.get(SFSessionProperty.PRIVATE_KEY_BASE64.getPropertyKey());
+        assertNotNull(privateKeyBase64);
+
+        assertTrue(privateKeyBase64.startsWith(PEM_HEADER_BASE64), "PEM Header 
encoded with Bas64 not found");
+        assertEquals(pemPrivateKeyBase64, privateKeyBase64);
+    }
+}

Reply via email to