Skip to content

Commit 8b1e4df

Browse files
authored
Add revocation checking, trust anchor validation, and trust store type detection (PR 3 of 3) (#787)
* add core ssl config params * core SSL functionality * adds comprehensive ssl tests and ci integration * core SSL functionality * fix error order * address comments and adds more comprehensive tests * adds specific exceptions * streamline exception handling * fmt * merge * break down tasks * modify test * modify test * modify yaml * modify yaml * modify yaml * modify yaml * modify yaml * modify yaml * merge * redo changes * address comments * changes post merge * changes post merge * changes post merge * add databricks cert to custom truststore * address comments * address comments * address comments * address comments * fmt * fmt * comment * undo comment * res conflicts * fmt * fmt * modifies changelog * addresses comments * post final manual testing
1 parent e41db81 commit 8b1e4df

File tree

6 files changed

+847
-33
lines changed

6 files changed

+847
-33
lines changed

.github/workflows/sslTesting.yml

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ jobs:
3939
java-version: "21"
4040
distribution: "adopt"
4141

42+
- name: Set Environment Variables
43+
env:
44+
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}
45+
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
46+
DATABRICKS_HTTP_PATH: ${{ secrets.DATABRICKS_HTTP_PATH }}
47+
HTTP_PROXY_URL: "http://localhost:3128"
48+
HTTPS_PROXY_URL: "https://localhost:3129"
49+
TRUSTSTORE_PATH: "/tmp/ssl-certs/test-truststore.jks"
50+
TRUSTSTORE_PASSWORD: "changeit"
51+
run: |
52+
echo "DATABRICKS_TOKEN=${DATABRICKS_TOKEN}" >> $GITHUB_ENV
53+
echo "DATABRICKS_HOST=${DATABRICKS_HOST}" >> $GITHUB_ENV
54+
echo "DATABRICKS_HTTP_PATH=${DATABRICKS_HTTP_PATH}" >> $GITHUB_ENV
55+
echo "HTTP_PROXY_URL=${HTTP_PROXY_URL}" >> $GITHUB_ENV
56+
echo "HTTPS_PROXY_URL=${HTTPS_PROXY_URL}" >> $GITHUB_ENV
57+
echo "TRUSTSTORE_PATH=${TRUSTSTORE_PATH}" >> $GITHUB_ENV
58+
echo "TRUSTSTORE_PASSWORD=${TRUSTSTORE_PASSWORD}" >> $GITHUB_ENV
59+
4260
- name: Install Squid and SSL Tools
4361
run: |
4462
sudo apt-get update
@@ -104,6 +122,10 @@ jobs:
104122
sudo cp squid.pem /etc/squid/
105123
sudo chown proxy:proxy /etc/squid/squid.pem
106124
125+
# Extract the Databricks workspace certificate
126+
echo -n | openssl s_client -connect ${DATABRICKS_HOST}:443 -showcerts 2>/dev/null | \
127+
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > databricks_workspace.crt
128+
107129
# Create Java Keystore from Root CA - with proper trust anchors
108130
rm -f test-truststore.jks
109131
@@ -115,6 +137,10 @@ jobs:
115137
keytool -importcert -noprompt -trustcacerts -alias intermediateca -file intermediateCA.crt \
116138
-keystore test-truststore.jks -storepass changeit
117139
140+
# Add the Databricks workspace certificate to the trust store
141+
keytool -importcert -noprompt -trustcacerts -alias databricksworkspace -file databricks_workspace.crt \
142+
-keystore test-truststore.jks -storepass changeit
143+
118144
chmod 644 test-truststore.jks
119145
120146
- name: Configure Squid with Standard SSL
@@ -189,24 +215,6 @@ jobs:
189215
run: |
190216
mvn clean package -DskipTests
191217
192-
- name: Set Environment Variables
193-
env:
194-
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}
195-
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
196-
DATABRICKS_HTTP_PATH: ${{ secrets.DATABRICKS_HTTP_PATH }}
197-
HTTP_PROXY_URL: "http://localhost:3128"
198-
HTTPS_PROXY_URL: "https://localhost:3129"
199-
TRUSTSTORE_PATH: "/tmp/ssl-certs/test-truststore.jks"
200-
TRUSTSTORE_PASSWORD: "changeit"
201-
run: |
202-
echo "DATABRICKS_TOKEN=${DATABRICKS_TOKEN}" >> $GITHUB_ENV
203-
echo "DATABRICKS_HOST=${DATABRICKS_HOST}" >> $GITHUB_ENV
204-
echo "DATABRICKS_HTTP_PATH=${DATABRICKS_HTTP_PATH}" >> $GITHUB_ENV
205-
echo "HTTP_PROXY_URL=${HTTP_PROXY_URL}" >> $GITHUB_ENV
206-
echo "HTTPS_PROXY_URL=${HTTPS_PROXY_URL}" >> $GITHUB_ENV
207-
echo "TRUSTSTORE_PATH=${TRUSTSTORE_PATH}" >> $GITHUB_ENV
208-
echo "TRUSTSTORE_PASSWORD=${TRUSTSTORE_PASSWORD}" >> $GITHUB_ENV
209-
210218
- name: Run SSL Tests
211219
run: |
212220
mvn test -Dtest=**/SSLTest.java

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Added
66
- Support for token cache in OAuth U2M Flow using the configuration parameters: `EnableTokenCache` and `TokenCachePassPhrase`.
7+
- Support for additional SSL functionality including use of System trust stores (`UseSystemTruststore`) and allowing self signed certificates (via `AllowSelfSignedCerts`)
78

89
### Updated
910
-

src/main/java/com/databricks/jdbc/dbclient/impl/common/ConfiguratorUtils.java

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,30 @@ private static boolean isJDBCTestEnv() {
4848
*/
4949
public static PoolingHttpClientConnectionManager getBaseConnectionManager(
5050
IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
51+
52+
if (connectionContext.getSSLTrustStore() == null
53+
&& connectionContext.checkCertificateRevocation()
54+
&& !connectionContext.acceptUndeterminedCertificateRevocation()
55+
&& !connectionContext.useSystemTrustStore()
56+
&& !connectionContext.allowSelfSignedCerts()) {
57+
return new PoolingHttpClientConnectionManager();
58+
}
59+
5160
// For test environments, use a trust-all socket factory
5261
if (isJDBCTestEnv()) {
5362
LOGGER.info("Using trust-all socket factory for JDBC test environment");
5463
return new PoolingHttpClientConnectionManager(
5564
SocketFactoryUtil.getTrustAllSocketFactoryRegistry());
5665
}
5766

67+
// If self-signed certificates are allowed, use a trust-all socket factory
68+
if (connectionContext.allowSelfSignedCerts()) {
69+
LOGGER.warn(
70+
"Self-signed certificates are allowed. Please only use this parameter (AllowSelfSignedCerts) when you're sure of what you're doing. This is not recommended for production use.");
71+
return new PoolingHttpClientConnectionManager(
72+
SocketFactoryUtil.getTrustAllSocketFactoryRegistry());
73+
}
74+
5875
// For standard SSL configuration, create a custom socket factory registry
5976
Registry<ConnectionSocketFactory> socketFactoryRegistry =
6077
createConnectionSocketFactoryRegistry(connectionContext);
@@ -71,7 +88,51 @@ public static PoolingHttpClientConnectionManager getBaseConnectionManager(
7188
public static Registry<ConnectionSocketFactory> createConnectionSocketFactoryRegistry(
7289
IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
7390

74-
return createRegistryWithSystemOrDefaultTrustStore(connectionContext);
91+
// First check if a custom trust store is specified
92+
if (connectionContext.getSSLTrustStore() != null) {
93+
return createRegistryWithCustomTrustStore(connectionContext);
94+
} else {
95+
return createRegistryWithSystemOrDefaultTrustStore(connectionContext);
96+
}
97+
}
98+
99+
/**
100+
* Creates a socket factory registry using a custom trust store.
101+
*
102+
* @param connectionContext The connection context containing the trust store information.
103+
* @return A registry of connection socket factories.
104+
* @throws DatabricksHttpException If there is an error setting up the trust store.
105+
*/
106+
private static Registry<ConnectionSocketFactory> createRegistryWithCustomTrustStore(
107+
IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
108+
109+
try {
110+
KeyStore trustStore = loadTruststoreOrNull(connectionContext);
111+
if (trustStore == null) {
112+
String errorMessage =
113+
"Specified trust store could not be loaded: " + connectionContext.getSSLTrustStore();
114+
handleError(errorMessage, new IOException(errorMessage));
115+
}
116+
117+
// Get trust anchors from custom store
118+
Set<TrustAnchor> trustAnchors = getTrustAnchorsFromTrustStore(trustStore);
119+
if (trustAnchors.isEmpty()) {
120+
String errorMessage =
121+
"Custom trust store contains no trust anchors. Certificate validation will fail.";
122+
handleError(errorMessage, new CertificateException(errorMessage));
123+
}
124+
125+
LOGGER.info("Using custom trust store: " + connectionContext.getSSLTrustStore());
126+
127+
return createRegistryFromTrustAnchors(
128+
trustAnchors,
129+
connectionContext,
130+
"custom trust store: " + connectionContext.getSSLTrustStore());
131+
} catch (Exception e) {
132+
handleError(
133+
"Error while setting up custom trust store: " + connectionContext.getSSLTrustStore(), e);
134+
}
135+
return null;
75136
}
76137

77138
/**
@@ -84,6 +145,7 @@ public static Registry<ConnectionSocketFactory> createConnectionSocketFactoryReg
84145
private static Registry<ConnectionSocketFactory> createRegistryWithSystemOrDefaultTrustStore(
85146
IDatabricksConnectionContext connectionContext) throws DatabricksHttpException {
86147

148+
// Check if we should use the system property trust store based on useSystemTrustStore
87149
String sysTrustStore = null;
88150
if (connectionContext.useSystemTrustStore()) {
89151
// When useSystemTrustStore=true, check for javax.net.ssl.trustStore system property
@@ -276,6 +338,56 @@ private static X509TrustManager findX509TrustManager(TrustManager[] trustManager
276338
return null;
277339
}
278340

341+
/**
342+
* Loads a trust store from the path specified in the connection context.
343+
*
344+
* @param connectionContext The connection context containing trust store configuration.
345+
* @return The loaded KeyStore or null if it could not be loaded.
346+
* @throws DatabricksHttpException If there is an error during loading.
347+
*/
348+
public static KeyStore loadTruststoreOrNull(IDatabricksConnectionContext connectionContext)
349+
throws DatabricksHttpException {
350+
String trustStorePath = connectionContext.getSSLTrustStore();
351+
if (trustStorePath == null) {
352+
return null;
353+
}
354+
355+
// If the specified file doesn't exist, throw a specific error
356+
File trustStoreFile = new File(trustStorePath);
357+
if (!trustStoreFile.exists()) {
358+
String errorMessage = "Specified trust store file does not exist: " + trustStorePath;
359+
LOGGER.error(errorMessage);
360+
throw new DatabricksHttpException(
361+
errorMessage, DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
362+
}
363+
364+
char[] password = null;
365+
if (connectionContext.getSSLTrustStorePassword() != null) {
366+
password = connectionContext.getSSLTrustStorePassword().toCharArray();
367+
}
368+
369+
String trustStoreType = connectionContext.getSSLTrustStoreType();
370+
371+
try (FileInputStream trustStoreStream = new FileInputStream(trustStorePath)) {
372+
LOGGER.info("Loading trust store as type: " + trustStoreType);
373+
KeyStore trustStore = KeyStore.getInstance(trustStoreType);
374+
trustStore.load(trustStoreStream, password);
375+
LOGGER.info("Successfully loaded trust store: " + trustStorePath);
376+
return trustStore;
377+
} catch (Exception e) {
378+
String errorMessage =
379+
"Failed to load trust store: "
380+
+ trustStorePath
381+
+ " with type "
382+
+ trustStoreType
383+
+ ": "
384+
+ e.getMessage();
385+
LOGGER.error(errorMessage);
386+
throw new DatabricksHttpException(
387+
errorMessage, e, DatabricksDriverErrorCode.SSL_HANDSHAKE_ERROR);
388+
}
389+
}
390+
279391
/**
280392
* Extracts trust anchors from a KeyStore.
281393
*
@@ -308,6 +420,17 @@ public static Set<TrustAnchor> getTrustAnchorsFromTrustStore(KeyStore trustStore
308420
return Collections.emptySet();
309421
}
310422

423+
/**
424+
* Builds trust manager parameters for certificate path validation including certificate
425+
* revocation checking.
426+
*
427+
* @param trustAnchors The trust anchors to use in the trust manager.
428+
* @param checkCertificateRevocation Whether to check certificate revocation.
429+
* @param acceptUndeterminedCertificateRevocation Whether to accept undetermined certificate
430+
* revocation status.
431+
* @return The trust manager parameters based on the input parameters.
432+
* @throws DatabricksHttpException If there is an error during configuration.
433+
*/
311434
public static CertPathTrustManagerParameters buildTrustManagerParameters(
312435
Set<TrustAnchor> trustAnchors,
313436
boolean checkCertificateRevocation,

0 commit comments

Comments
 (0)