Skip to content

Commit 3d5bd51

Browse files
feat(NODE-6289): allow valid srv hostnames with less than 3 parts (#4197)
1 parent 7fde8dd commit 3d5bd51

File tree

5 files changed

+406
-50
lines changed

5 files changed

+406
-50
lines changed

Diff for: src/connection_string.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ import { ReadPreference, type ReadPreferenceMode } from './read_preference';
3434
import { ServerMonitoringMode } from './sdam/monitor';
3535
import type { TagSet } from './sdam/server_description';
3636
import {
37+
checkParentDomainMatch,
3738
DEFAULT_PK_FACTORY,
3839
emitWarning,
3940
HostAddress,
4041
isRecord,
41-
matchesParentDomain,
4242
parseInteger,
4343
setDifference,
4444
squashError
@@ -64,11 +64,6 @@ export async function resolveSRVRecord(options: MongoOptions): Promise<HostAddre
6464
throw new MongoAPIError('Option "srvHost" must not be empty');
6565
}
6666

67-
if (options.srvHost.split('.').length < 3) {
68-
// TODO(NODE-3484): Replace with MongoConnectionStringError
69-
throw new MongoAPIError('URI must include hostname, domain name, and tld');
70-
}
71-
7267
// Asynchronously start TXT resolution so that we do not have to wait until
7368
// the SRV record is resolved before starting a second DNS query.
7469
const lookupAddress = options.srvHost;
@@ -86,9 +81,7 @@ export async function resolveSRVRecord(options: MongoOptions): Promise<HostAddre
8681
}
8782

8883
for (const { name } of addresses) {
89-
if (!matchesParentDomain(name, lookupAddress)) {
90-
throw new MongoAPIError('Server record does not share hostname with parent URI');
91-
}
84+
checkParentDomainMatch(name, lookupAddress);
9285
}
9386

9487
const hostAddresses = addresses.map(r => HostAddress.fromString(`${r.name}:${r.port ?? 27017}`));

Diff for: src/sdam/srv_polling.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
33

44
import { MongoRuntimeError } from '../error';
55
import { TypedEventEmitter } from '../mongo_types';
6-
import { HostAddress, matchesParentDomain, squashError } from '../utils';
6+
import { checkParentDomainMatch, HostAddress, squashError } from '../utils';
77

88
/**
99
* @internal
@@ -127,8 +127,11 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
127127

128128
const finalAddresses: dns.SrvRecord[] = [];
129129
for (const record of srvRecords) {
130-
if (matchesParentDomain(record.name, this.srvHost)) {
130+
try {
131+
checkParentDomainMatch(record.name, this.srvHost);
131132
finalAddresses.push(record);
133+
} catch (error) {
134+
squashError(error);
132135
}
133136
}
134137

Diff for: src/utils.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { FindCursor } from './cursor/find_cursor';
1818
import type { Db } from './db';
1919
import {
2020
type AnyError,
21+
MongoAPIError,
2122
MongoCompatibilityError,
2223
MongoInvalidArgumentError,
2324
MongoNetworkTimeoutError,
@@ -1142,29 +1143,47 @@ export function parseUnsignedInteger(value: unknown): number | null {
11421143
}
11431144

11441145
/**
1145-
* Determines whether a provided address matches the provided parent domain.
1146+
* This function throws a MongoAPIError in the event that either of the following is true:
1147+
* * If the provided address domain does not match the provided parent domain
1148+
* * If the parent domain contains less than three `.` separated parts and the provided address does not contain at least one more domain level than its parent
11461149
*
11471150
* If a DNS server were to become compromised SRV records would still need to
11481151
* advertise addresses that are under the same domain as the srvHost.
11491152
*
11501153
* @param address - The address to check against a domain
11511154
* @param srvHost - The domain to check the provided address against
1152-
* @returns Whether the provided address matches the parent domain
1155+
* @returns void
11531156
*/
1154-
export function matchesParentDomain(address: string, srvHost: string): boolean {
1157+
export function checkParentDomainMatch(address: string, srvHost: string): void {
11551158
// Remove trailing dot if exists on either the resolved address or the srv hostname
11561159
const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
11571160
const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
11581161

11591162
const allCharacterBeforeFirstDot = /^.*?\./;
1163+
const srvIsLessThanThreeParts = normalizedSrvHost.split('.').length < 3;
11601164
// Remove all characters before first dot
11611165
// Add leading dot back to string so
11621166
// an srvHostDomain = '.trusted.site'
11631167
// will not satisfy an addressDomain that endsWith '.fake-trusted.site'
11641168
const addressDomain = `.${normalizedAddress.replace(allCharacterBeforeFirstDot, '')}`;
1165-
const srvHostDomain = `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;
1169+
let srvHostDomain = srvIsLessThanThreeParts
1170+
? normalizedSrvHost
1171+
: `.${normalizedSrvHost.replace(allCharacterBeforeFirstDot, '')}`;
11661172

1167-
return addressDomain.endsWith(srvHostDomain);
1173+
if (!srvHostDomain.startsWith('.')) {
1174+
srvHostDomain = '.' + srvHostDomain;
1175+
}
1176+
if (
1177+
srvIsLessThanThreeParts &&
1178+
normalizedAddress.split('.').length <= normalizedSrvHost.split('.').length
1179+
) {
1180+
throw new MongoAPIError(
1181+
'Server record does not have at least one more domain level than parent URI'
1182+
);
1183+
}
1184+
if (!addressDomain.endsWith(srvHostDomain)) {
1185+
throw new MongoAPIError('Server record does not share hostname with parent URI');
1186+
}
11681187
}
11691188

11701189
interface RequestOptions {

0 commit comments

Comments
 (0)