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
8 changes: 2 additions & 6 deletions driver-core/src/main/com/mongodb/ConnectionString.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.mongodb.event.ConnectionCheckedOutEvent;
import com.mongodb.event.ConnectionCreatedEvent;
import com.mongodb.event.ConnectionReadyEvent;
import com.mongodb.internal.connection.DnsSuffixValidator;
import com.mongodb.internal.connection.ServerMonitoringModeUtil;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
Expand All @@ -54,7 +55,6 @@
import java.util.stream.Stream;

import static com.mongodb.MongoCredential.ALLOWED_HOSTS_KEY;
import static com.mongodb.internal.connection.DomainNameUtils.normalizeSrvAllowedHostsSuffix;
import static com.mongodb.internal.connection.OidcAuthenticator.OidcValidator.validateCreateOidcCredential;
import static java.lang.String.format;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -501,10 +501,6 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
throw new IllegalArgumentException("srvAllowedHostsSuffix can only be specified with mongodb+srv protocol");
}

if (srvAllowedHostsSuffix != null) {
srvAllowedHostsSuffix = normalizeSrvAllowedHostsSuffix(srvAllowedHostsSuffix);
}

if (directConnection != null && directConnection) {
if (isSrvProtocol) {
throw new IllegalArgumentException("Direct connections are not supported when using mongodb+srv protocol");
Expand Down Expand Up @@ -763,7 +759,7 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
srvServiceName = value;
break;
case "srvallowedhostssuffix":
srvAllowedHostsSuffix = value;
srvAllowedHostsSuffix = new DnsSuffixValidator(value).getSuffix();
break;
default:
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.event.ClusterListener;
import com.mongodb.internal.connection.ServerAddressHelper;
import com.mongodb.internal.connection.DnsSuffixValidator;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;

Expand All @@ -35,7 +36,6 @@

import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.connection.DomainNameUtils.normalizeSrvAllowedHostsSuffix;
import static com.mongodb.internal.connection.ServerAddressHelper.createServerAddress;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
Expand All @@ -51,7 +51,11 @@ public final class ClusterSettings {
private final String srvHost;
private final Integer srvMaxHosts;
private final String srvServiceName;
private final String srvAllowedHostsSuffix;
@Nullable
private final DnsSuffixValidator srvAllowedHostsSuffix;
@Nullable
private final SrvHostValidator srvHostValidator;
private final SrvHostValidator effectiveSrvHostValidator;
private final List<ServerAddress> hosts;
private final ClusterConnectionMode mode;
private final ClusterType requiredClusterType;
Expand Down Expand Up @@ -90,7 +94,8 @@ public static final class Builder {
private String srvHost;
private Integer srvMaxHosts;
private String srvServiceName = "mongodb";
private String srvAllowedHostsSuffix;
private DnsSuffixValidator srvAllowedHostsSuffix;
private SrvHostValidator srvHostValidator;
private List<ServerAddress> hosts = DEFAULT_HOSTS;
private ClusterConnectionMode mode;
private ClusterType requiredClusterType = ClusterType.UNKNOWN;
Expand Down Expand Up @@ -118,6 +123,7 @@ public Builder applySettings(final ClusterSettings clusterSettings) {
srvServiceName = clusterSettings.srvServiceName;
srvMaxHosts = clusterSettings.srvMaxHosts;
srvAllowedHostsSuffix = clusterSettings.srvAllowedHostsSuffix;
srvHostValidator = clusterSettings.srvHostValidator;
hosts = clusterSettings.hosts;
mode = clusterSettings.mode;
requiredReplicaSetName = clusterSettings.requiredReplicaSetName;
Expand Down Expand Up @@ -152,6 +158,27 @@ public Builder srvHost(final String srvHost) {
return this;
}

/**
* Sets the hostname validator to use to validate hosts returned via SRV lookup.
*
* <p><b>WARNING:</b> Modifying the default SRV domain name validation can create vulnerabilities.</p>
* <p>The validator provided will be called for each SRV record discovered and can choose to accept or reject a discovered
* host names. Rejected hosts will be ignored as if the record does not exist.</p>
*
* <p>It is used synchronously during DNS lookup, so the validator should not block. Tt should also be thread-safe.</p>
* <p>This option is mutually exclusive with {@link #srvAllowedHostsSuffix(String)}.</p>
* @param srvHostValidator the validator to use
* @return this
* @since 5.14
*/
public Builder srvHostValidator(final SrvHostValidator srvHostValidator) {
if (this.srvAllowedHostsSuffix != null) {
throw new IllegalArgumentException("Cannot set both srvHostValidator and srvAllowedHostsSuffix");
}
this.srvHostValidator = srvHostValidator;
return this;
}

/**
* Sets the maximum number of hosts to connect to when using SRV protocol.
* This setting is not used if {@link #getMode()} is {@link ClusterConnectionMode#LOAD_BALANCED}.
Expand Down Expand Up @@ -198,14 +225,17 @@ public Builder srvServiceName(final String srvServiceName) {
* used with SRV. Specifying an overly broad suffix (for example a bare TLD) weakens SRV host name validation and
* is the responsibility of the caller.</p>
*
* <p>This option is mutually exclusive with {@link #srvHostValidator(SrvHostValidator)}.</p>
* @param srvAllowedHostsSuffix the SRV allowed hosts suffix; may not be null or empty
* @return this
* @since 5.13
* @see #getSrvAllowedHostsSuffix()
*/
public Builder srvAllowedHostsSuffix(final String srvAllowedHostsSuffix) {
notNull("srvAllowedHostsSuffix", srvAllowedHostsSuffix);
this.srvAllowedHostsSuffix = normalizeSrvAllowedHostsSuffix(srvAllowedHostsSuffix);
if (this.srvHostValidator != null) {
throw new IllegalArgumentException("Cannot set both srvHostValidator and srvAllowedHostsSuffix");
}
this.srvAllowedHostsSuffix = new DnsSuffixValidator(srvAllowedHostsSuffix);
return this;
}

Expand Down Expand Up @@ -470,7 +500,32 @@ public String getSrvServiceName() {
*/
@Nullable
public String getSrvAllowedHostsSuffix() {
return srvAllowedHostsSuffix;
return srvAllowedHostsSuffix != null ? srvAllowedHostsSuffix.getSuffix() : null;
}

/**
* Get the user-supplied SRV host validator
*
* <p><b>WARNING:</b> Modifying the default SRV domain name validation can create vulnerabilities.</p>
*
* @return the SRV host validator
* @since 5.14
*/
@Nullable
public SrvHostValidator getSrvHostValidator() {
return srvHostValidator;
}

/**
* Get the effective SRV host validator
*
* <p>The validator returned may be the user-supplied validator, the validator equivalent to {@link #getSrvAllowedHostsSuffix()}, or
* the default validator based on {@link #getSrvHost()}.</p>
* @return the SRV host validator
* @since 5.14
*/
public SrvHostValidator getEffectiveSrvHostValidator() {
return effectiveSrvHostValidator;
}

/**
Expand Down Expand Up @@ -679,6 +734,34 @@ private ClusterSettings(final Builder builder) {
srvMaxHosts = builder.srvMaxHosts;
srvServiceName = builder.srvServiceName;
srvAllowedHostsSuffix = builder.srvAllowedHostsSuffix;
srvHostValidator = builder.srvHostValidator;
if (builder.srvHostValidator != null) {
effectiveSrvHostValidator = builder.srvHostValidator;
} else if (builder.srvAllowedHostsSuffix != null) {
effectiveSrvHostValidator = builder.srvAllowedHostsSuffix;
} else if (srvHost != null) {
int firstDot = srvHost.indexOf('.');
boolean hasThreeParts = firstDot >= 0 && srvHost.indexOf('.', firstDot + 1) >= 0;
DnsSuffixValidator suffixValidator = new DnsSuffixValidator(hasThreeParts ? srvHost.substring(firstDot) : srvHost);
effectiveSrvHostValidator = suffixValidator.isAllowedSingleLabel() ? suffixValidator : new SrvHostValidator() {
@Override
public boolean isValidHost(final String discoveredHostName) {
return suffixValidator.isValidHost(discoveredHostName)
&& discoveredHostName.chars().filter(c -> c == '.').count() >= 2;
}

@Override
public String toString() {
return String.format("ends with %s and has at least 3 labels", suffixValidator.getSuffix());
}
};
} else {
// This should be unreachable
effectiveSrvHostValidator = h -> false;
}
if (srvHost == null && srvHostValidator != null) {
throw new IllegalArgumentException("An srvHostValidator was supplied for a non-SRV URI");
}
hosts = builder.hosts;
requiredReplicaSetName = builder.requiredReplicaSetName;
if (builder.mode != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.connection;

/**
* Checks that a host name in a discovered SRV record is allowed.
*
* <p>The purpose of this interface is to allow custom validation logic for SRV records.</p>
*
* <p><b>WARNING:</b> Modifying the default SRV domain name validation can create vulnerabilities.</p>
*/
public interface SrvHostValidator {

/**
* Validate the host name discovered in an SRV record.
*
* <p>Checks the host name provided to determine whether the driver should connect to it or not.</p>
* @param discoveredHostName the discovered hostname; the name will be lowercase Punycode (ASCII) without a trailing period
* @return true if the host name can be used; false otherwise
*/
boolean isValidHost(String discoveredHostName);
}

Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,8 @@ private void addHostsToSet(final Set<String> hosts, final Set<ServerAddress> ret
retVal.add(new ServerAddress(host));
}
}

public boolean hasServerAddress(final ServerAddress address) {
return addressToServerTupleMap.containsKey(address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ClusterType;
import com.mongodb.connection.SrvHostValidator;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.dns.DnsResolver;
import com.mongodb.lang.Nullable;

import java.util.Collections;
import java.util.HashSet;
Expand All @@ -39,22 +39,21 @@ class DefaultDnsSrvRecordMonitor implements DnsSrvRecordMonitor {

private final String hostName;
private final String srvServiceName;
@Nullable
private final String srvAllowedHostsSuffix;
private final SrvHostValidator srvHostValidator;
private final long rescanFrequencyMillis;
private final long noRecordsRescanFrequencyMillis;
private final DnsSrvRecordInitializer dnsSrvRecordInitializer;
private final DnsResolver dnsResolver;
private final Thread monitorThread;
private volatile boolean isClosed;

DefaultDnsSrvRecordMonitor(final String hostName, final String srvServiceName, @Nullable final String srvAllowedHostsSuffix,
DefaultDnsSrvRecordMonitor(final String hostName, final String srvServiceName, final SrvHostValidator srvHostValidator,
final long rescanFrequencyMillis, final long noRecordsRescanFrequencyMillis,
final DnsSrvRecordInitializer dnsSrvRecordInitializer, final ClusterId clusterId,
final DnsResolver dnsResolver) {
this.hostName = hostName;
this.srvServiceName = srvServiceName;
this.srvAllowedHostsSuffix = srvAllowedHostsSuffix;
this.srvHostValidator = srvHostValidator;
this.rescanFrequencyMillis = rescanFrequencyMillis;
this.noRecordsRescanFrequencyMillis = noRecordsRescanFrequencyMillis;
this.dnsSrvRecordInitializer = dnsSrvRecordInitializer;
Expand Down Expand Up @@ -84,7 +83,7 @@ public void run() {
while (!isClosed && shouldContinueMonitoring()) {
try {
List<String> resolvedHostNames = dnsResolver.resolveHostFromSrvRecords(hostName, srvServiceName,
srvAllowedHostsSuffix);
srvHostValidator);
Set<ServerAddress> hosts = createServerAddressSet(resolvedHostNames);

if (isClosed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ServerSettings;
import com.mongodb.connection.SrvHostValidator;
import com.mongodb.internal.dns.DefaultDnsResolver;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.DnsClient;
Expand All @@ -44,8 +45,8 @@ public DefaultDnsSrvRecordMonitorFactory(final ClusterId clusterId, final Server

@Override
public DnsSrvRecordMonitor create(final String hostName, final String srvServiceName,
@Nullable final String srvAllowedHostsSuffix, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, srvAllowedHostsSuffix, DEFAULT_RESCAN_FREQUENCY_MILLIS,
final SrvHostValidator srvHostValidator, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, srvHostValidator, DEFAULT_RESCAN_FREQUENCY_MILLIS,
noRecordsRescanFrequency, dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver(dnsClient));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public DnsMultiServerCluster(final ClusterId clusterId, final ClusterSettings se
final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory) {
super(clusterId, settings, serverFactory, clientMetadata);
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(assertNotNull(settings.getSrvHost()), settings.getSrvServiceName(),
settings.getSrvAllowedHostsSuffix(),
settings.getEffectiveSrvHostValidator(),
new DnsSrvRecordInitializer() {
private volatile boolean initialized;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

package com.mongodb.internal.connection;

import com.mongodb.lang.Nullable;
import com.mongodb.connection.SrvHostValidator;

/**
* <p>This class is not part of the public API and may be removed or changed at any time</p>
*/
public interface DnsSrvRecordMonitorFactory {
DnsSrvRecordMonitor create(String hostName, String srvServiceName, @Nullable String srvAllowedHostsSuffix,
DnsSrvRecordMonitor create(String hostName, String srvServiceName, SrvHostValidator srvHostValidator,
DnsSrvRecordInitializer dnsSrvRecordInitializer);
}
Loading