Skip to content
Open
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
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Updated

### Fixed
- Fixed `DatabaseMetaData.getURL()` exposing credentials embedded in the connection URL; secret parameters are now masked (the URL is otherwise unchanged).
- Fixed `setCatalog()` and `setSchema()` producing invalid SQL (e.g. `SET CATALOG ``name``) when the catalog or schema name was passed already wrapped in backticks. Backticks are now stripped before wrapping, and `getCatalog()`/`getSchema()` return the bare identifier name.
- Fixed metadata SQL generation for catalog, schema, and table identifiers containing backticks.
- Fixed SEA result truncation when direct results are disabled. Large, highly-compressible results that span multiple chunks were delivered inline via the old hybrid path and truncated to the first chunk. The SQL Execution path now uses an async (`0s`) wait timeout when direct results are disabled, so results are returned via external links and fetched in full.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;

Expand Down Expand Up @@ -158,7 +159,7 @@ 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 " + redactConnectionURL(url), DatabricksDriverErrorCode.CONNECTION_ERROR);
}
Matcher urlMatcher = JDBC_URL_PATTERN.matcher(url);
if (urlMatcher.find()) {
Expand Down Expand Up @@ -778,6 +779,47 @@ public String getConnectionURL() {
return connectionURL;
}

// Connection-URL params whose values are secrets and must be masked before the URL is exposed
// (DatabaseMetaData.getURL(), exceptions, logs, telemetry).
private static final Set<String> SENSITIVE_URL_PARAMS =
Stream.of(
DatabricksJdbcUrlParams.PWD,
DatabricksJdbcUrlParams.PASSWORD,
DatabricksJdbcUrlParams.CLIENT_SECRET,
DatabricksJdbcUrlParams.AUTH_ACCESS_TOKEN,
DatabricksJdbcUrlParams.OAUTH_REFRESH_TOKEN,
DatabricksJdbcUrlParams.OAUTH_REFRESH_TOKEN_2,
DatabricksJdbcUrlParams.PROXY_PWD,
DatabricksJdbcUrlParams.CF_PROXY_PWD,
DatabricksJdbcUrlParams.SSL_KEY_STORE_PASSWORD,
DatabricksJdbcUrlParams.SSL_TRUST_STORE_PASSWORD,
DatabricksJdbcUrlParams.TOKEN_CACHE_PASS_PHRASE,
DatabricksJdbcUrlParams.JWT_PASS_PHRASE)
.map(p -> p.getParamName().toLowerCase())
.collect(Collectors.toSet());

/** Masks the values of secret-bearing parameters in a JDBC connection URL. */
public static String redactConnectionURL(String url) {
if (url == null) {
return null;
}
String[] parts = url.split(URL_DELIMITER);
StringBuilder sb = new StringBuilder(url.length());
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
sb.append(URL_DELIMITER);
}
String part = parts[i];
int idx = part.indexOf(PAIR_DELIMITER);
if (idx > 0 && SENSITIVE_URL_PARAMS.contains(part.substring(0, idx).toLowerCase())) {
sb.append(part, 0, idx + 1).append(REDACTED_TOKEN);
} else {
sb.append(part);
}
}
return sb.toString();
}

@Override
public boolean checkCertificateRevocation() {
return Objects.equals(getParameter(DatabricksJdbcUrlParams.CHECK_CERTIFICATE_REVOCATION), "1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public boolean allTablesAreSelectable() throws SQLException {
@Override
public String getURL() throws SQLException {
LOGGER.debug("public String getURL()");
return this.session.getConnectionContext().getConnectionURL();
return DatabricksConnectionContext.redactConnectionURL(
this.session.getConnectionContext().getConnectionURL());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,40 @@ public void testParseInvalid() {
() -> DatabricksConnectionContext.parse(TestConstants.INVALID_URL_2, properties));
}

@Test
public void testRedactConnectionURL_masksSecretsKeepsRest() {
String url =
"jdbc:databricks://host.databricks.com:443/default;transportMode=https;ssl=1;"
+ "AuthMech=3;httpPath=/sql/1.0/warehouses/abc;UID=token;"
+ "PWD=dapiSECRET;OAuth2Secret=clientSecretVal;Auth_AccessToken=tokenVal";

String redacted = DatabricksConnectionContext.redactConnectionURL(url);

// Secrets masked.
assertFalse(redacted.contains("dapiSECRET"));
assertFalse(redacted.contains("clientSecretVal"));
assertFalse(redacted.contains("tokenVal"));
assertTrue(redacted.contains("PWD=****"));
assertTrue(redacted.contains("OAuth2Secret=****"));
assertTrue(redacted.contains("Auth_AccessToken=****"));
// Non-secret params and host preserved.
assertTrue(redacted.contains("jdbc:databricks://host.databricks.com:443/default"));
assertTrue(redacted.contains("httpPath=/sql/1.0/warehouses/abc"));
assertTrue(redacted.contains("UID=token"));
}

@Test
public void testRedactConnectionURL_caseInsensitiveKeyAndNullSafe() {
assertNull(DatabricksConnectionContext.redactConnectionURL(null));
// Key match is case-insensitive (driver lowercases param keys when parsing).
String redacted =
DatabricksConnectionContext.redactConnectionURL(
"jdbc:databricks://h:443/default;pwd=secret;ProxyPwd=proxySecret");
assertFalse(redacted.contains("secret"));
assertTrue(redacted.contains("pwd=****"));
assertTrue(redacted.contains("ProxyPwd=****"));
}

@Test
public void testParseValid() throws DatabricksSQLException {
// test provided port
Expand Down
Loading