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

chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f6be0854 [KYUUBI #5709] Avoid exposing client keystore and truststore 
passwords in the JDBC URL
9f6be0854 is described below

commit 9f6be0854b59f7b96f75e36a0cd6fbc1813b8004
Author: pengqli <[email protected]>
AuthorDate: Mon Dec 11 00:01:03 2023 +0800

    [KYUUBI #5709] Avoid exposing client keystore and truststore passwords in 
the JDBC URL
    
    # :mag: Description
    ## Issue References ๐Ÿ”—
    
    This pull request fixes #5709
    
    ## Describe Your Solution ๐Ÿ”ง
    
    Add a new JDBC URL property, storePasswordPath, which points to a local JCE 
keystore file storing password aliases.If the password exists, use password. If 
it does not exist, try to use storePasswordPath. All these passwords can be 
hidden from the JDBC URL when you protect them in a local JCEKS keystore file 
and pass the JCEKS file path to the URL.
    JDBC URL:
    
`jdbc:hive2://wapdfwudp001.webex.com:30009/default;ssl=true;sslTrustStore=kyuubissl.truststore.keystore.jks;storePasswordPath=jceks://file/client_trust_creds.jceks`
    
    Hadoop credential command with trustStorePassword and keyStorePassword 
aliases like below
    hadoop credential create ssl.kyuubi.trustStorePassword -value 
mytruststorepassword -provider localjceks://file/client_trust_creds.jceks
    more info: 
https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients
    Reference PR comes from HIVE-27308
    
    ## Types of changes :bookmark:
    
    - [ ] Bugfix (non-breaking change which fixes an issue)
    - [x] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing 
functionality to change)
    
    ## Test Plan ๐Ÿงช
    
    #### Behavior Without This Pull Request :coffin:
    
    #### Behavior With This Pull Request :tada:
    
    #### Related Unit Tests
    Local test passed
    <img width="1093" alt="Screenshot 2023-11-16 at 10 43 03" 
src="https://github.com/apache/kyuubi/assets/43336508/3be87238-dfd7-4484-8945-e62780c43d9a";>
    
    ---
    
    # Checklists
    ## ๐Ÿ“ Author Self Checklist
    
    - [ ] My code follows the [style 
guidelines](https://kyuubi.readthedocs.io/en/master/contributing/code/style.html)
 of this project
    - [ ] I have performed a self-review
    - [ ] I have commented my code, particularly in hard-to-understand areas
    - [ ] I have made corresponding changes to the documentation
    - [ ] My changes generate no new warnings
    - [ ] I have added tests that prove my fix is effective or that my feature 
works
    - [ ] New and existing unit tests pass locally with my changes
    - [ ] This patch was not authored or co-authored using [Generative 
Tooling](https://www.apache.org/legal/generative-tooling.html)
    
    ## ๐Ÿ“ Committer Pre-Merge Checklist
    
    - [x] Pull request title is okay.
    - [x] No license issues.
    - [x] Milestone correctly set?
    - [ ] Test coverage is ok
    - [x] Assignees are selected.
    - [x] Minimum number of approvals
    - [x] No changes are requested
    
    **Be nice. Be informative.**
    
    Closes #5710 from dev-lpq/enhance_url_password.
    
    Closes #5709
    
    98cba7ef4 [pengqli] resolve conflicts
    1055dcc41 [pengqli] use reflection to check Hadoop classes
    c3096146d [pengqli] use reflection to check Hadoop classes
    f94f3024b [pengqli] Enhanced URL password
    
    Authored-by: pengqli <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
---
 .../kyuubi/jdbc/hive/JdbcConnectionParams.java     |  2 +
 .../apache/kyuubi/jdbc/hive/KyuubiConnection.java  | 12 ++--
 .../java/org/apache/kyuubi/jdbc/hive/Utils.java    | 72 ++++++++++++++++++++++
 3 files changed, 82 insertions(+), 4 deletions(-)

diff --git 
a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcConnectionParams.java
 
b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcConnectionParams.java
index d3c77a77f..c60f34899 100644
--- 
a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcConnectionParams.java
+++ 
b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/JdbcConnectionParams.java
@@ -116,6 +116,8 @@ public class JdbcConnectionParams {
   // Currently supports JKS keystore format
   static final String SSL_TRUST_STORE_TYPE = "JKS";
 
+  static final String SSL_STORE_PASSWORD_PATH = "storePasswordPath";
+
   static final String HIVE_VAR_PREFIX = "hivevar:";
   static final String HIVE_CONF_PREFIX = "hiveconf:";
   private String host = null;
diff --git 
a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java
 
b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java
index 028dc9b34..47de5f748 100644
--- 
a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java
+++ 
b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiConnection.java
@@ -539,7 +539,8 @@ public class KyuubiConnection implements SQLConnection, 
KyuubiLoggable {
     if (useSsl) {
       String useTwoWaySSL = sessConfMap.get(USE_TWO_WAY_SSL);
       String sslTrustStorePath = sessConfMap.get(SSL_TRUST_STORE);
-      String sslTrustStorePassword = sessConfMap.get(SSL_TRUST_STORE_PASSWORD);
+      String sslTrustStorePassword =
+          Utils.getPassword(sessConfMap, 
JdbcConnectionParams.SSL_TRUST_STORE_PASSWORD);
       KeyStore sslTrustStore;
       SSLConnectionSocketFactory socketFactory;
       SSLContext sslContext;
@@ -591,7 +592,8 @@ public class KyuubiConnection implements SQLConnection, 
KyuubiLoggable {
     if (isSslConnection()) {
       // get SSL socket
       String sslTrustStore = sessConfMap.get(SSL_TRUST_STORE);
-      String sslTrustStorePassword = sessConfMap.get(SSL_TRUST_STORE_PASSWORD);
+      String sslTrustStorePassword =
+          Utils.getPassword(sessConfMap, 
JdbcConnectionParams.SSL_TRUST_STORE_PASSWORD);
 
       if (sslTrustStore == null || sslTrustStore.isEmpty()) {
         transport = ThriftUtils.getSSLSocket(host, port, connectTimeout, 
socketTimeout);
@@ -662,7 +664,8 @@ public class KyuubiConnection implements SQLConnection, 
KyuubiLoggable {
       KeyManagerFactory keyManagerFactory =
           KeyManagerFactory.getInstance(SUNX509_ALGORITHM_STRING, 
SUNJSSE_ALGORITHM_STRING);
       String keyStorePath = sessConfMap.get(SSL_KEY_STORE);
-      String keyStorePassword = sessConfMap.get(SSL_KEY_STORE_PASSWORD);
+      String keyStorePassword =
+          Utils.getPassword(sessConfMap, 
JdbcConnectionParams.SSL_KEY_STORE_PASSWORD);
       KeyStore sslKeyStore = KeyStore.getInstance(SSL_KEY_STORE_TYPE);
 
       if (keyStorePath == null || keyStorePath.isEmpty()) {
@@ -678,7 +681,8 @@ public class KyuubiConnection implements SQLConnection, 
KyuubiLoggable {
       TrustManagerFactory trustManagerFactory =
           TrustManagerFactory.getInstance(SUNX509_ALGORITHM_STRING);
       String trustStorePath = sessConfMap.get(SSL_TRUST_STORE);
-      String trustStorePassword = sessConfMap.get(SSL_TRUST_STORE_PASSWORD);
+      String trustStorePassword =
+          Utils.getPassword(sessConfMap, 
JdbcConnectionParams.SSL_TRUST_STORE_PASSWORD);
       KeyStore sslTrustStore = KeyStore.getInstance(SSL_TRUST_STORE_TYPE);
 
       if (trustStorePath == null || trustStorePath.isEmpty()) {
diff --git 
a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java 
b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java
index 420ba43c4..3b4d4a175 100644
--- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java
+++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/Utils.java
@@ -30,6 +30,8 @@ import java.util.regex.Pattern;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TStatus;
 import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TStatusCode;
+import org.apache.kyuubi.util.reflect.DynConstructors;
+import org.apache.kyuubi.util.reflect.DynMethods;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,6 +60,11 @@ public class Utils {
   public static final String HIVE_SERVER2_RETRY_TRUE = "true";
   public static final String HIVE_SERVER2_RETRY_FALSE = "false";
 
+  public static final String HADOOP_CONFIGURATION_CLASS = 
"org.apache.hadoop.conf.Configuration";
+
+  public static final String HADOOP_SECURITY_CREDENTIAL_PATH =
+      "hadoop.security.credential.provider.path";
+
   public static final Pattern KYUUBI_OPERATION_HINT_PATTERN =
       Pattern.compile("^__kyuubi_operation_result_(.*)__=(.*)", 
Pattern.CASE_INSENSITIVE);
 
@@ -570,4 +577,69 @@ public class Utils {
     }
     return KYUUBI_CLIENT_VERSION;
   }
+
+  /**
+   * Method to get the password from the credential provider
+   *
+   * @param configuration Hadoop configuration
+   * @param providerPath provider path
+   * @param key alias name
+   * @return password
+   */
+  private static String getPasswordFromCredentialProvider(
+      Object configuration, String providerPath, String key) {
+    try {
+      if (providerPath != null) {
+        DynMethods.builder("set")
+            .impl(Class.forName(HADOOP_CONFIGURATION_CLASS), String.class, 
String.class)
+            .buildChecked()
+            .invoke(configuration, HADOOP_SECURITY_CREDENTIAL_PATH, 
providerPath);
+
+        char[] password =
+            DynMethods.builder("getPassword")
+                .impl(Class.forName(HADOOP_CONFIGURATION_CLASS), String.class)
+                .buildChecked()
+                .invoke(configuration, key);
+        if (password != null) {
+          return new String(password);
+        }
+      }
+    } catch (ClassNotFoundException exception) {
+      throw new RuntimeException(exception);
+    } catch (NoSuchMethodException exception) {
+      LOG.warn("Could not retrieve password for " + key, exception);
+      throw new RuntimeException(exception);
+    }
+    return null;
+  }
+
+  /**
+   * Method to get the password from the configuration map if available. 
Otherwise, get it from the
+   * Hadoop CredentialProvider if Hadoop classes are available
+   *
+   * @param confMap configuration map
+   * @param key param
+   * @return password
+   */
+  public static String getPassword(Map<String, String> confMap, String key) {
+    String password = confMap.get(key);
+    boolean hadoopCredentialProviderAvailable = false;
+    Object hadoopConfiguration = null;
+    if (password == null) {
+      try {
+        hadoopConfiguration =
+            
DynConstructors.builder().impl(HADOOP_CONFIGURATION_CLASS).build().newInstance();
+        hadoopCredentialProviderAvailable = true;
+      } catch (Exception exception) {
+        LOG.warn("Hadoop credential provider is unavailable", exception);
+        throw new RuntimeException(exception);
+      }
+    }
+    if (password == null && hadoopCredentialProviderAvailable) {
+      password =
+          getPasswordFromCredentialProvider(
+              hadoopConfiguration, 
confMap.get(JdbcConnectionParams.SSL_STORE_PASSWORD_PATH), key);
+    }
+    return password;
+  }
 }

Reply via email to