Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/main/java/com/databricks/client/jdbc/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.DatabricksClientType;
import com.databricks.jdbc.common.util.*;
import com.databricks.jdbc.common.util.SecurityUtil;
import com.databricks.jdbc.dbclient.IDatabricksClient;
import com.databricks.jdbc.dbclient.impl.common.SessionId;
import com.databricks.jdbc.dbclient.impl.sqlexec.DatabricksSdkClient;
Expand Down Expand Up @@ -116,7 +117,9 @@ public void closeConnection(String url, Properties info, String connectionId)
throws SQLException {
if (!acceptsURL(url)) {
throw new DatabricksSQLException(
String.format("Invalid connection Url {%s}, Can't close connection.", url),
String.format(
"Invalid connection Url {%s}, Can't close connection.",
SecurityUtil.sanitizeJdbcUrl(url)),
DatabricksDriverErrorCode.CONNECTION_ERROR);
}
IDatabricksConnectionContext connectionContext =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.databricks.jdbc.common.*;
import com.databricks.jdbc.common.SeaCircuitBreakerManager;
import com.databricks.jdbc.common.safe.DatabricksDriverFeatureFlagsContextFactory;
import com.databricks.jdbc.common.util.SecurityUtil;
import com.databricks.jdbc.common.util.ValidationUtil;
import com.databricks.jdbc.exception.DatabricksDriverException;
import com.databricks.jdbc.exception.DatabricksParsingException;
Expand Down Expand Up @@ -150,7 +151,8 @@ public static IDatabricksConnectionContext parse(String url, Properties properti
throws DatabricksSQLException {
if (!ValidationUtil.isValidJdbcUrl(url)) {
throw new DatabricksParsingException(
"Invalid url " + url, DatabricksDriverErrorCode.CONNECTION_ERROR);
"Invalid url " + SecurityUtil.sanitizeJdbcUrl(url),
DatabricksDriverErrorCode.CONNECTION_ERROR);
}
Matcher urlMatcher = JDBC_URL_PATTERN.matcher(url);
if (urlMatcher.find()) {
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/com/databricks/jdbc/common/util/SecurityUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.databricks.jdbc.common.util;

import java.util.regex.Pattern;

/**
* Security utility class for sanitizing sensitive information in logs and exception messages.
*
* <p>This class provides methods to redact credentials and other sensitive data from strings that
* might be logged or included in error messages, preventing accidental exposure of secrets in log
* files.
*/
public class SecurityUtil {

/**
* Pattern to match credential parameters in JDBC URLs.
*
* <p>Matches exact parameter names from DatabricksJdbcUrlParams that contain sensitive data:
*
* <ul>
* <li>password, pwd - Authentication passwords
* <li>OAuth2Secret - OAuth2 client secret
* <li>Auth_AccessToken - OAuth2 access token
* <li>Auth_RefreshToken, OAuthRefreshToken - OAuth2 refresh tokens
* <li>proxyuid, cfproxyuid - Proxy usernames (usernames are sensitive in proxy context)
* <li>proxypwd, cfproxypwd - Proxy passwords
* <li>Auth_JWT_Key_Passphrase - JWT key passphrase
* <li>SSLTrustStorePwd - SSL trust store password
* <li>SSLKeyStorePwd - SSL key store password
* <li>TokenCachePassPhrase - Token cache passphrase
* </ul>
*
* <p>Note: UID is not redacted as it's an identifier (e.g., email, username), not a secret like a
* password or token.
*
* <p>Pattern matches: - Parameter name (case-insensitive) - Equals sign - Value (everything until
* semicolon, ampersand, or end of string)
*/
private static final Pattern CREDENTIAL_PATTERN =
Pattern.compile(
"(password|pwd"
+ "|OAuth2Secret"
+ "|Auth_AccessToken"
+ "|Auth_RefreshToken|OAuthRefreshToken"
+ "|proxyuid|proxypwd"
+ "|cfproxyuid|cfproxypwd"
+ "|Auth_JWT_Key_Passphrase"
+ "|SSLTrustStorePwd|SSLKeyStorePwd"
+ "|TokenCachePassPhrase"
+ ")=[^;&]*",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the enums to build this pattern matching string so that we do not have to make changes at multiple places?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can we also further ensure that folks add newer connection params containing credentials to this list?

Pattern.CASE_INSENSITIVE);

/** Redaction string used to replace credential values. */
private static final String REDACTED = "***REDACTED***";

/**
* Sanitizes a JDBC URL by redacting credential parameters.
*
* <p>This method should be used whenever a JDBC URL needs to be logged or included in an
* exception message. It replaces the values of sensitive parameters with "***REDACTED***" while
* preserving the parameter names to aid in debugging.
*
* <p>Example: Input: {@code
* jdbc:databricks://host:443/default;PWD=secret123;UID=user@email.com;HttpPath=/sql/1.0} Output:
* {@code
* jdbc:databricks://host:443/default;PWD=***REDACTED***;UID=***REDACTED***;HttpPath=/sql/1.0}
*
* @param jdbcUrl the JDBC URL to sanitize, may be null
* @return sanitized URL with credentials redacted, or null if input was null
*/
public static String sanitizeJdbcUrl(String jdbcUrl) {
if (jdbcUrl == null) {
return null;
}
return CREDENTIAL_PATTERN.matcher(jdbcUrl).replaceAll("$1=" + REDACTED);
}

/**
* Sanitizes a connection string by redacting credential parameters.
*
* <p>Alias for {@link #sanitizeJdbcUrl(String)} to support different naming conventions.
*
* @param connectionString the connection string to sanitize, may be null
* @return sanitized connection string with credentials redacted, or null if input was null
*/
public static String sanitizeConnectionString(String connectionString) {
return sanitizeJdbcUrl(connectionString);
}

private SecurityUtil() {
// Utility class, prevent instantiation
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

}
3 changes: 2 additions & 1 deletion src/test/java/com/databricks/client/jdbc/LoggingTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.databricks.client.jdbc;

import com.databricks.jdbc.common.util.SecurityUtil;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
Expand Down Expand Up @@ -82,7 +83,7 @@ private static String buildJdbcUrl() {
+ ";usethriftclient="
+ useThriftClient;

logger.info("Connecting with URL: " + jdbcUrl);
logger.info("Connecting with URL: " + SecurityUtil.sanitizeJdbcUrl(jdbcUrl));

return jdbcUrl;
}
Expand Down
13 changes: 7 additions & 6 deletions src/test/java/com/databricks/client/jdbc/SSLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.*;

import com.databricks.jdbc.common.util.SecurityUtil;
import java.io.File;
import java.security.KeyStore;
import java.sql.Connection;
Expand Down Expand Up @@ -121,7 +122,7 @@ private String buildJdbcUrl(
}

private void verifyConnect(String jdbcUrl) throws Exception {
LOGGER.info("Attempting to connect with URL: " + jdbcUrl);
LOGGER.info("Attempting to connect with URL: " + SecurityUtil.sanitizeJdbcUrl(jdbcUrl));

try (Connection conn = DriverManager.getConnection(jdbcUrl, "token", patToken)) {
Statement stmt = conn.createStatement();
Expand Down Expand Up @@ -194,7 +195,7 @@ public void testWithAllowSelfSigned() {

try {
LOGGER.info("\n\n==== TEST 1: Connection with empty trust store ====");
LOGGER.info("URL: " + url1);
LOGGER.info("URL: " + SecurityUtil.sanitizeJdbcUrl(url1));
LOGGER.info("Trust store: " + System.getProperty("javax.net.ssl.trustStore"));
verifyConnect(url1);
fail("Connection with empty trust store should have failed");
Expand All @@ -210,7 +211,7 @@ public void testWithAllowSelfSigned() {

try {
LOGGER.info("\n\n==== TEST 2: Connection with non-existent trust store ====");
LOGGER.info("URL: " + url2);
LOGGER.info("URL: " + SecurityUtil.sanitizeJdbcUrl(url2));
LOGGER.info("Trust store: " + nonExistentPath);
verifyConnect(url2);
fail("Connection with non-existent trust store should have failed");
Expand All @@ -235,7 +236,7 @@ public void testWithAllowSelfSigned() {

try {
LOGGER.info("\n\n==== TEST 3: Connection with AllowSelfSignedCerts=1 ====");
LOGGER.info("URL: " + url3);
LOGGER.info("URL: " + SecurityUtil.sanitizeJdbcUrl(url3));
LOGGER.info("Trust store: " + System.getProperty("javax.net.ssl.trustStore"));
verifyConnect(url3);
LOGGER.info("Connection succeeded with AllowSelfSignedCerts=1 as expected");
Expand Down Expand Up @@ -568,7 +569,7 @@ public void testNoCustomTrustStoreWithUseSystemTrustStoreFalse() {
try {
LOGGER.info(
"\n==== Testing connection with UseSystemTrustStore=0 and no custom trust store ====");
LOGGER.info("URL: " + url);
LOGGER.info("URL: " + SecurityUtil.sanitizeJdbcUrl(url));
verifyConnect(url);
LOGGER.info("Connection succeeded using default trust store with UseSystemTrustStore=0");
} catch (Exception e) {
Expand Down Expand Up @@ -614,7 +615,7 @@ public void testCustomTrustStorePrecedence() {

try {
LOGGER.info("\n==== Testing custom trust store precedence ====");
LOGGER.info("URL: " + url);
LOGGER.info("URL: " + SecurityUtil.sanitizeJdbcUrl(url));
LOGGER.info(
"System property trust store: " + System.getProperty("javax.net.ssl.trustStore"));
LOGGER.info("Custom trust store: " + trustStorePath);
Expand Down
Loading
Loading