This is an automated email from the ASF dual-hosted git repository.
tolbertam pushed a commit to branch 4.x
in repository https://gitbox.apache.org/repos/asf/cassandra-java-driver.git
The following commit(s) were added to refs/heads/4.x by this push:
new 3bb5b1890 CASSJAVA-80: Support configuration to disable DNS
reverse-lookups for SAN validation
3bb5b1890 is described below
commit 3bb5b18903636976279506308c60f1911b7b8ed5
Author: Abe Ratnofsky <[email protected]>
AuthorDate: Thu Feb 13 17:11:36 2025 -0500
CASSJAVA-80: Support configuration to disable DNS reverse-lookups for SAN
validation
patch by Abe Ratnofsky; reviewed by Alexandre Dutra, Andy Tolbert, and
Francisco Guerrero for CASSJAVA-80
---
.../api/core/config/DefaultDriverOption.java | 6 ++
.../driver/api/core/config/TypedDriverOption.java | 4 ++
.../api/core/ssl/ProgrammaticSslEngineFactory.java | 27 ++++++++-
.../internal/core/ssl/DefaultSslEngineFactory.java | 26 ++++++++-
.../internal/core/ssl/SniSslEngineFactory.java | 10 +++-
core/src/main/resources/reference.conf | 6 ++
.../driver/core/ssl/DefaultSslEngineFactoryIT.java | 66 ++++++++++++++++++++++
7 files changed, 141 insertions(+), 4 deletions(-)
diff --git
a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
index 11f2702c3..acd4bb9cc 100644
---
a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
+++
b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
@@ -243,6 +243,12 @@ public enum DefaultDriverOption implements DriverOption {
* <p>Value-type: boolean
*/
SSL_HOSTNAME_VALIDATION("advanced.ssl-engine-factory.hostname-validation"),
+ /**
+ * Whether or not to do a DNS reverse-lookup of provided server addresses
for SAN addresses.
+ *
+ * <p>Value-type: boolean
+ */
+
SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN("advanced.ssl-engine-factory.allow-dns-reverse-lookup-san"),
/**
* The location of the keystore file.
*
diff --git
a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
index ca60b67f0..93e2b4684 100644
---
a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
+++
b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
@@ -229,6 +229,10 @@ public class TypedDriverOption<ValueT> {
*/
public static final TypedDriverOption<Boolean> SSL_HOSTNAME_VALIDATION =
new TypedDriverOption<>(DefaultDriverOption.SSL_HOSTNAME_VALIDATION,
GenericType.BOOLEAN);
+
+ public static final TypedDriverOption<Boolean>
SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN =
+ new TypedDriverOption<>(
+ DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN,
GenericType.BOOLEAN);
/** The location of the keystore file. */
public static final TypedDriverOption<String> SSL_KEYSTORE_PATH =
new TypedDriverOption<>(DefaultDriverOption.SSL_KEYSTORE_PATH,
GenericType.STRING);
diff --git
a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
index 6dfe4087b..d65eaa864 100644
---
a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
+++
b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
@@ -45,6 +45,7 @@ public class ProgrammaticSslEngineFactory implements
SslEngineFactory {
protected final SSLContext sslContext;
protected final String[] cipherSuites;
protected final boolean requireHostnameValidation;
+ protected final boolean allowDnsReverseLookupSan;
/**
* Creates an instance with the given {@link SSLContext}, default cipher
suites and no host name
@@ -80,9 +81,28 @@ public class ProgrammaticSslEngineFactory implements
SslEngineFactory {
@NonNull SSLContext sslContext,
@Nullable String[] cipherSuites,
boolean requireHostnameValidation) {
+ this(sslContext, cipherSuites, requireHostnameValidation, true);
+ }
+
+ /**
+ * Creates an instance with the given {@link SSLContext}, cipher suites and
host name validation.
+ *
+ * @param sslContext the {@link SSLContext} to use.
+ * @param cipherSuites the cipher suites to use, or null to use the default
ones.
+ * @param requireHostnameValidation whether to enable host name validation.
If enabled, host name
+ * validation will be done using HTTPS algorithm.
+ * @param allowDnsReverseLookupSan whether to allow raw server IPs to be DNS
reverse-resolved to
+ * choose the appropriate Subject Alternative Name.
+ */
+ public ProgrammaticSslEngineFactory(
+ @NonNull SSLContext sslContext,
+ @Nullable String[] cipherSuites,
+ boolean requireHostnameValidation,
+ boolean allowDnsReverseLookupSan) {
this.sslContext = sslContext;
this.cipherSuites = cipherSuites;
this.requireHostnameValidation = requireHostnameValidation;
+ this.allowDnsReverseLookupSan = allowDnsReverseLookupSan;
}
@NonNull
@@ -92,7 +112,12 @@ public class ProgrammaticSslEngineFactory implements
SslEngineFactory {
SocketAddress remoteAddress = remoteEndpoint.resolve();
if (remoteAddress instanceof InetSocketAddress) {
InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
- engine = sslContext.createSSLEngine(socketAddress.getHostName(),
socketAddress.getPort());
+ engine =
+ sslContext.createSSLEngine(
+ allowDnsReverseLookupSan
+ ? socketAddress.getHostName()
+ : socketAddress.getHostString(),
+ socketAddress.getPort());
} else {
engine = sslContext.createSSLEngine();
}
diff --git
a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
index 475ec38d5..343d3f9e4 100644
---
a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
+++
b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
@@ -22,6 +22,7 @@ import
com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
+import
com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.InputStream;
import java.net.InetSocketAddress;
@@ -69,6 +70,7 @@ public class DefaultSslEngineFactory implements
SslEngineFactory {
private final SSLContext sslContext;
private final String[] cipherSuites;
private final boolean requireHostnameValidation;
+ private final boolean allowDnsReverseLookupSan;
private ReloadingKeyManagerFactory kmf;
/** Builds a new instance from the driver configuration. */
@@ -88,6 +90,28 @@ public class DefaultSslEngineFactory implements
SslEngineFactory {
}
this.requireHostnameValidation =
config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, true);
+ this.allowDnsReverseLookupSan =
+
config.getBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, true);
+ }
+
+ @VisibleForTesting
+ protected String hostname(InetSocketAddress addr) {
+ return allowDnsReverseLookupSan ? hostMaybeFromDnsReverseLookup(addr) :
hostNoLookup(addr);
+ }
+
+ @VisibleForTesting
+ protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) {
+ // See java.net.InetSocketAddress.getHostName:
+ // "This method may trigger a name service reverse lookup if the address
was created with a
+ // literal IP address."
+ return addr.getHostName();
+ }
+
+ @VisibleForTesting
+ protected String hostNoLookup(InetSocketAddress addr) {
+ // See java.net.InetSocketAddress.getHostString:
+ // "This has the benefit of not attempting a reverse lookup"
+ return addr.getHostString();
}
@NonNull
@@ -97,7 +121,7 @@ public class DefaultSslEngineFactory implements
SslEngineFactory {
SocketAddress remoteAddress = remoteEndpoint.resolve();
if (remoteAddress instanceof InetSocketAddress) {
InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
- engine = sslContext.createSSLEngine(socketAddress.getHostName(),
socketAddress.getPort());
+ engine = sslContext.createSSLEngine(hostname(socketAddress),
socketAddress.getPort());
} else {
engine = sslContext.createSSLEngine();
}
diff --git
a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
index 98af19045..4d2cb69fb 100644
---
a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
+++
b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
@@ -38,9 +38,15 @@ public class SniSslEngineFactory implements SslEngineFactory
{
private final SSLContext sslContext;
private final CopyOnWriteArrayList<String> fakePorts = new
CopyOnWriteArrayList<>();
+ private final boolean allowDnsReverseLookupSan;
public SniSslEngineFactory(SSLContext sslContext) {
+ this(sslContext, true);
+ }
+
+ public SniSslEngineFactory(SSLContext sslContext, boolean
allowDnsReverseLookupSan) {
this.sslContext = sslContext;
+ this.allowDnsReverseLookupSan = allowDnsReverseLookupSan;
}
@NonNull
@@ -71,8 +77,8 @@ public class SniSslEngineFactory implements SslEngineFactory {
// To avoid that, we create a unique "fake" port for every node. We still
get session reuse for
// a given node, but not across nodes. This is safe because the advisory
port is only used for
// session caching.
- SSLEngine engine =
- sslContext.createSSLEngine(address.getHostName(),
getFakePort(sniServerName));
+ String peerHost = allowDnsReverseLookupSan ? address.getHostName() :
address.getHostString();
+ SSLEngine engine = sslContext.createSSLEngine(peerHost,
getFakePort(sniServerName));
engine.setUseClientMode(true);
SSLParameters parameters = engine.getSSLParameters();
parameters.setServerNames(ImmutableList.of(new
SNIHostName(sniServerName)));
diff --git a/core/src/main/resources/reference.conf
b/core/src/main/resources/reference.conf
index 7b1c43f8b..f09ffd18a 100644
--- a/core/src/main/resources/reference.conf
+++ b/core/src/main/resources/reference.conf
@@ -789,6 +789,12 @@ datastax-java-driver {
# name matches the hostname of the server being connected to. If not set,
defaults to true.
// hostname-validation = true
+ # Whether or not to allow a DNS reverse-lookup of provided server
addresses for SAN addresses,
+ # if cluster endpoints are specified as literal IPs.
+ # This is left as true for compatibility, but in most environments a DNS
reverse-lookup should
+ # not be necessary to get an address that matches the server certificate
SANs.
+ // allow-dns-reverse-lookup-san = true
+
# The locations and passwords used to access truststore and keystore
contents.
# These properties are optional. If either truststore-path or
keystore-path are specified,
# the driver builds an SSLContext from these files. If neither option is
specified, the
diff --git
a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java
b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java
index 5f97e661e..a2afeade3 100644
---
a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java
+++
b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java
@@ -21,10 +21,13 @@ import
com.datastax.oss.driver.api.core.AllNodesFailedException;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge;
import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule;
import com.datastax.oss.driver.api.testinfra.session.SessionUtils;
+import com.datastax.oss.driver.assertions.Assertions;
import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory;
+import java.net.InetSocketAddress;
import org.junit.ClassRule;
import org.junit.Test;
@@ -88,4 +91,67 @@ public class DefaultSslEngineFactoryIT {
session.execute("select * from system.local");
}
}
+
+ public static class InstrumentedSslEngineFactory extends
DefaultSslEngineFactory {
+ int countReverseLookups = 0;
+ int countNoLookups = 0;
+
+ public InstrumentedSslEngineFactory(DriverContext driverContext) {
+ super(driverContext);
+ }
+
+ @Override
+ protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) {
+ countReverseLookups++;
+ return super.hostMaybeFromDnsReverseLookup(addr);
+ }
+
+ @Override
+ protected String hostNoLookup(InetSocketAddress addr) {
+ countNoLookups++;
+ return super.hostNoLookup(addr);
+ }
+ };
+
+ @Test
+ public void should_respect_config_for_san_resolution() {
+ DriverConfigLoader loader =
+ SessionUtils.configLoaderBuilder()
+ .withClass(
+ DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS,
InstrumentedSslEngineFactory.class)
+ .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false)
+ .withString(
+ DefaultDriverOption.SSL_TRUSTSTORE_PATH,
+ CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath())
+ .withString(
+ DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD,
+ CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)
+ .build();
+ try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) {
+ InstrumentedSslEngineFactory ssl =
+ (InstrumentedSslEngineFactory)
session.getContext().getSslEngineFactory().get();
+ Assertions.assertThat(ssl.countReverseLookups).isGreaterThan(0);
+ Assertions.assertThat(ssl.countNoLookups).isEqualTo(0);
+ }
+
+ loader =
+ SessionUtils.configLoaderBuilder()
+ .withClass(
+ DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS,
InstrumentedSslEngineFactory.class)
+ .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false)
+ .withString(
+ DefaultDriverOption.SSL_TRUSTSTORE_PATH,
+ CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath())
+ .withString(
+ DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD,
+ CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)
+ .withBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN,
false)
+ .build();
+ try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) {
+ InstrumentedSslEngineFactory ssl =
+ (InstrumentedSslEngineFactory)
session.getContext().getSslEngineFactory().get();
+ Assertions.assertThat(ssl.countReverseLookups).isEqualTo(0);
+ Assertions.assertThat(ssl.countNoLookups).isGreaterThan(0);
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]