Skip to content

Commit 3bb5b18

Browse files
aratnotolbertam
authored andcommitted
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
1 parent 90612f6 commit 3bb5b18

File tree

7 files changed

+141
-4
lines changed

7 files changed

+141
-4
lines changed

core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java

+6
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ public enum DefaultDriverOption implements DriverOption {
243243
* <p>Value-type: boolean
244244
*/
245245
SSL_HOSTNAME_VALIDATION("advanced.ssl-engine-factory.hostname-validation"),
246+
/**
247+
* Whether or not to do a DNS reverse-lookup of provided server addresses for SAN addresses.
248+
*
249+
* <p>Value-type: boolean
250+
*/
251+
SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN("advanced.ssl-engine-factory.allow-dns-reverse-lookup-san"),
246252
/**
247253
* The location of the keystore file.
248254
*

core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java

+4
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ public String toString() {
229229
*/
230230
public static final TypedDriverOption<Boolean> SSL_HOSTNAME_VALIDATION =
231231
new TypedDriverOption<>(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, GenericType.BOOLEAN);
232+
233+
public static final TypedDriverOption<Boolean> SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN =
234+
new TypedDriverOption<>(
235+
DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, GenericType.BOOLEAN);
232236
/** The location of the keystore file. */
233237
public static final TypedDriverOption<String> SSL_KEYSTORE_PATH =
234238
new TypedDriverOption<>(DefaultDriverOption.SSL_KEYSTORE_PATH, GenericType.STRING);

core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class ProgrammaticSslEngineFactory implements SslEngineFactory {
4545
protected final SSLContext sslContext;
4646
protected final String[] cipherSuites;
4747
protected final boolean requireHostnameValidation;
48+
protected final boolean allowDnsReverseLookupSan;
4849

4950
/**
5051
* Creates an instance with the given {@link SSLContext}, default cipher suites and no host name
@@ -80,9 +81,28 @@ public ProgrammaticSslEngineFactory(
8081
@NonNull SSLContext sslContext,
8182
@Nullable String[] cipherSuites,
8283
boolean requireHostnameValidation) {
84+
this(sslContext, cipherSuites, requireHostnameValidation, true);
85+
}
86+
87+
/**
88+
* Creates an instance with the given {@link SSLContext}, cipher suites and host name validation.
89+
*
90+
* @param sslContext the {@link SSLContext} to use.
91+
* @param cipherSuites the cipher suites to use, or null to use the default ones.
92+
* @param requireHostnameValidation whether to enable host name validation. If enabled, host name
93+
* validation will be done using HTTPS algorithm.
94+
* @param allowDnsReverseLookupSan whether to allow raw server IPs to be DNS reverse-resolved to
95+
* choose the appropriate Subject Alternative Name.
96+
*/
97+
public ProgrammaticSslEngineFactory(
98+
@NonNull SSLContext sslContext,
99+
@Nullable String[] cipherSuites,
100+
boolean requireHostnameValidation,
101+
boolean allowDnsReverseLookupSan) {
83102
this.sslContext = sslContext;
84103
this.cipherSuites = cipherSuites;
85104
this.requireHostnameValidation = requireHostnameValidation;
105+
this.allowDnsReverseLookupSan = allowDnsReverseLookupSan;
86106
}
87107

88108
@NonNull
@@ -92,7 +112,12 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) {
92112
SocketAddress remoteAddress = remoteEndpoint.resolve();
93113
if (remoteAddress instanceof InetSocketAddress) {
94114
InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
95-
engine = sslContext.createSSLEngine(socketAddress.getHostName(), socketAddress.getPort());
115+
engine =
116+
sslContext.createSSLEngine(
117+
allowDnsReverseLookupSan
118+
? socketAddress.getHostName()
119+
: socketAddress.getHostString(),
120+
socketAddress.getPort());
96121
} else {
97122
engine = sslContext.createSSLEngine();
98123
}

core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.datastax.oss.driver.api.core.context.DriverContext;
2323
import com.datastax.oss.driver.api.core.metadata.EndPoint;
2424
import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
25+
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
2526
import edu.umd.cs.findbugs.annotations.NonNull;
2627
import java.io.InputStream;
2728
import java.net.InetSocketAddress;
@@ -69,6 +70,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory {
6970
private final SSLContext sslContext;
7071
private final String[] cipherSuites;
7172
private final boolean requireHostnameValidation;
73+
private final boolean allowDnsReverseLookupSan;
7274
private ReloadingKeyManagerFactory kmf;
7375

7476
/** Builds a new instance from the driver configuration. */
@@ -88,6 +90,28 @@ public DefaultSslEngineFactory(DriverContext driverContext) {
8890
}
8991
this.requireHostnameValidation =
9092
config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, true);
93+
this.allowDnsReverseLookupSan =
94+
config.getBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, true);
95+
}
96+
97+
@VisibleForTesting
98+
protected String hostname(InetSocketAddress addr) {
99+
return allowDnsReverseLookupSan ? hostMaybeFromDnsReverseLookup(addr) : hostNoLookup(addr);
100+
}
101+
102+
@VisibleForTesting
103+
protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) {
104+
// See java.net.InetSocketAddress.getHostName:
105+
// "This method may trigger a name service reverse lookup if the address was created with a
106+
// literal IP address."
107+
return addr.getHostName();
108+
}
109+
110+
@VisibleForTesting
111+
protected String hostNoLookup(InetSocketAddress addr) {
112+
// See java.net.InetSocketAddress.getHostString:
113+
// "This has the benefit of not attempting a reverse lookup"
114+
return addr.getHostString();
91115
}
92116

93117
@NonNull
@@ -97,7 +121,7 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) {
97121
SocketAddress remoteAddress = remoteEndpoint.resolve();
98122
if (remoteAddress instanceof InetSocketAddress) {
99123
InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
100-
engine = sslContext.createSSLEngine(socketAddress.getHostName(), socketAddress.getPort());
124+
engine = sslContext.createSSLEngine(hostname(socketAddress), socketAddress.getPort());
101125
} else {
102126
engine = sslContext.createSSLEngine();
103127
}

core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@ public class SniSslEngineFactory implements SslEngineFactory {
3838

3939
private final SSLContext sslContext;
4040
private final CopyOnWriteArrayList<String> fakePorts = new CopyOnWriteArrayList<>();
41+
private final boolean allowDnsReverseLookupSan;
4142

4243
public SniSslEngineFactory(SSLContext sslContext) {
44+
this(sslContext, true);
45+
}
46+
47+
public SniSslEngineFactory(SSLContext sslContext, boolean allowDnsReverseLookupSan) {
4348
this.sslContext = sslContext;
49+
this.allowDnsReverseLookupSan = allowDnsReverseLookupSan;
4450
}
4551

4652
@NonNull
@@ -71,8 +77,8 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) {
7177
// To avoid that, we create a unique "fake" port for every node. We still get session reuse for
7278
// a given node, but not across nodes. This is safe because the advisory port is only used for
7379
// session caching.
74-
SSLEngine engine =
75-
sslContext.createSSLEngine(address.getHostName(), getFakePort(sniServerName));
80+
String peerHost = allowDnsReverseLookupSan ? address.getHostName() : address.getHostString();
81+
SSLEngine engine = sslContext.createSSLEngine(peerHost, getFakePort(sniServerName));
7682
engine.setUseClientMode(true);
7783
SSLParameters parameters = engine.getSSLParameters();
7884
parameters.setServerNames(ImmutableList.of(new SNIHostName(sniServerName)));

core/src/main/resources/reference.conf

+6
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,12 @@ datastax-java-driver {
789789
# name matches the hostname of the server being connected to. If not set, defaults to true.
790790
// hostname-validation = true
791791

792+
# Whether or not to allow a DNS reverse-lookup of provided server addresses for SAN addresses,
793+
# if cluster endpoints are specified as literal IPs.
794+
# This is left as true for compatibility, but in most environments a DNS reverse-lookup should
795+
# not be necessary to get an address that matches the server certificate SANs.
796+
// allow-dns-reverse-lookup-san = true
797+
792798
# The locations and passwords used to access truststore and keystore contents.
793799
# These properties are optional. If either truststore-path or keystore-path are specified,
794800
# the driver builds an SSLContext from these files. If neither option is specified, the

integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java

+66
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
import com.datastax.oss.driver.api.core.CqlSession;
2222
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
2323
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
24+
import com.datastax.oss.driver.api.core.context.DriverContext;
2425
import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge;
2526
import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule;
2627
import com.datastax.oss.driver.api.testinfra.session.SessionUtils;
28+
import com.datastax.oss.driver.assertions.Assertions;
2729
import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory;
30+
import java.net.InetSocketAddress;
2831
import org.junit.ClassRule;
2932
import org.junit.Test;
3033

@@ -88,4 +91,67 @@ public void should_not_connect_if_not_using_ssl() {
8891
session.execute("select * from system.local");
8992
}
9093
}
94+
95+
public static class InstrumentedSslEngineFactory extends DefaultSslEngineFactory {
96+
int countReverseLookups = 0;
97+
int countNoLookups = 0;
98+
99+
public InstrumentedSslEngineFactory(DriverContext driverContext) {
100+
super(driverContext);
101+
}
102+
103+
@Override
104+
protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) {
105+
countReverseLookups++;
106+
return super.hostMaybeFromDnsReverseLookup(addr);
107+
}
108+
109+
@Override
110+
protected String hostNoLookup(InetSocketAddress addr) {
111+
countNoLookups++;
112+
return super.hostNoLookup(addr);
113+
}
114+
};
115+
116+
@Test
117+
public void should_respect_config_for_san_resolution() {
118+
DriverConfigLoader loader =
119+
SessionUtils.configLoaderBuilder()
120+
.withClass(
121+
DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, InstrumentedSslEngineFactory.class)
122+
.withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false)
123+
.withString(
124+
DefaultDriverOption.SSL_TRUSTSTORE_PATH,
125+
CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath())
126+
.withString(
127+
DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD,
128+
CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)
129+
.build();
130+
try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) {
131+
InstrumentedSslEngineFactory ssl =
132+
(InstrumentedSslEngineFactory) session.getContext().getSslEngineFactory().get();
133+
Assertions.assertThat(ssl.countReverseLookups).isGreaterThan(0);
134+
Assertions.assertThat(ssl.countNoLookups).isEqualTo(0);
135+
}
136+
137+
loader =
138+
SessionUtils.configLoaderBuilder()
139+
.withClass(
140+
DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, InstrumentedSslEngineFactory.class)
141+
.withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false)
142+
.withString(
143+
DefaultDriverOption.SSL_TRUSTSTORE_PATH,
144+
CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath())
145+
.withString(
146+
DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD,
147+
CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD)
148+
.withBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, false)
149+
.build();
150+
try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) {
151+
InstrumentedSslEngineFactory ssl =
152+
(InstrumentedSslEngineFactory) session.getContext().getSslEngineFactory().get();
153+
Assertions.assertThat(ssl.countReverseLookups).isEqualTo(0);
154+
Assertions.assertThat(ssl.countNoLookups).isGreaterThan(0);
155+
}
156+
}
91157
}

0 commit comments

Comments
 (0)