Skip to content

Commit 631bf28

Browse files
authored
fix(data-service): update how we set ALLOWED_HOSTS when enableUntrustedEndpoints is true COMPASS-6916 (#4523)
1 parent 58b07e3 commit 631bf28

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

packages/data-service/src/connect-mongo-client.spec.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -290,17 +290,47 @@ describe('prepareOIDCOptions', function () {
290290
]);
291291
});
292292

293-
it('sets ALLOWED_HOSTS on the authMechanismProperties (non-url) to * when enableUntrustedEndpoints is true', function () {
294-
const options = prepareOIDCOptions({
295-
connectionString: 'mongodb://localhost:27017',
296-
oidc: {
297-
enableUntrustedEndpoints: true,
298-
},
299-
});
293+
it('maps ALLOWED_HOSTS on the authMechanismProperties (non-url) when enableUntrustedEndpoints is true', function () {
294+
function actual(connectionString: string) {
295+
return prepareOIDCOptions({
296+
connectionString,
297+
oidc: {
298+
enableUntrustedEndpoints: true,
299+
},
300+
}).authMechanismProperties;
301+
}
300302

301-
expect(options.authMechanismProperties).to.deep.equal({
302-
ALLOWED_HOSTS: ['*'],
303-
});
303+
function expected(ALLOWED_HOSTS: string[]) {
304+
return { ALLOWED_HOSTS };
305+
}
306+
307+
expect(actual('mongodb://localhost/')).to.deep.equal(
308+
expected(['localhost'])
309+
);
310+
expect(actual('mongodb://localhost:27017/')).to.deep.equal(
311+
expected(['localhost'])
312+
);
313+
expect(actual('mongodb://localhost:12345/')).to.deep.equal(
314+
expected(['localhost'])
315+
);
316+
expect(actual('mongodb://localhost:12345,[::1]/')).to.deep.equal(
317+
expected(['localhost', '::1'])
318+
);
319+
expect(actual('mongodb://localhost,[::1]:999/')).to.deep.equal(
320+
expected(['localhost', '::1'])
321+
);
322+
expect(actual('mongodb://localhost,bar.foo.net/')).to.deep.equal(
323+
expected(['localhost', 'bar.foo.net'])
324+
);
325+
expect(actual('mongodb+srv://bar.foo.net/')).to.deep.equal(
326+
expected(['*.foo.net'])
327+
);
328+
expect(actual('mongodb://127.0.0.1:12345/')).to.deep.equal(
329+
expected(['127.0.0.1'])
330+
);
331+
expect(actual('mongodb://2130706433:12345/')).to.deep.equal(
332+
expected(['2130706433'])
333+
); // decimal IPv4
304334
});
305335

306336
it('does not set ALLOWED_HOSTS on the authMechanismProperties (non-url) when enableUntrustedEndpoints is not set', function () {

packages/data-service/src/connect-mongo-client.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import type {
77
} from '@mongodb-js/devtools-connect';
88
import type SSHTunnel from '@mongodb-js/ssh-tunnel';
99
import EventEmitter from 'events';
10-
import { redactConnectionOptions, redactConnectionString } from './redact';
10+
import ConnectionString from 'mongodb-connection-string-url';
1111
import _ from 'lodash';
12+
13+
import { redactConnectionOptions, redactConnectionString } from './redact';
1214
import type { ConnectionOptions } from './connection-options';
1315
import {
1416
forceCloseTunnel,
@@ -28,6 +30,36 @@ export type CloneableMongoClient = MongoClient & {
2830

2931
export type ReauthenticationHandler = () => PromiseLike<void> | void;
3032

33+
// Return an ALLOWED_HOSTS value that matches the hosts listed in the connection
34+
// string, including possible SRV "sibling" domains.
35+
function matchingAllowedHosts(
36+
connectionOptions: Readonly<ConnectionOptions>
37+
): string[] {
38+
const connectionString = new ConnectionString(
39+
connectionOptions.connectionString,
40+
{ looseValidation: true }
41+
);
42+
const suffixes = connectionString.hosts.map((hostStr) => {
43+
// eslint-disable-next-line
44+
const { host } = hostStr.match(/^(?<host>.+?)(?<port>:[^:\]\[]+)?$/)
45+
?.groups!;
46+
if (host.startsWith('[') && host.endsWith(']')) {
47+
return host.slice(1, -1); // IPv6
48+
}
49+
if (host.match(/^[0-9.]+$/)) {
50+
return host; // IPv4
51+
}
52+
if (!host.includes('.') || !connectionString.isSRV) {
53+
return host;
54+
}
55+
// An SRV record for foo.bar.net can resolve to any hosts that match `*.bar.net`
56+
const parts = host.split('.');
57+
parts[0] = '*';
58+
return parts.join('.');
59+
});
60+
return [...new Set(suffixes)];
61+
}
62+
3163
export function prepareOIDCOptions(
3264
connectionOptions: Readonly<ConnectionOptions>,
3365
signal?: AbortSignal,
@@ -51,10 +83,11 @@ export function prepareOIDCOptions(
5183
return allowedFlows;
5284
};
5385

54-
// Set the driver's `authMechanismProperties` (non-url)
55-
// `ALLOWED_HOSTS` value to `*`.
5686
if (connectionOptions.oidc?.enableUntrustedEndpoints) {
57-
options.authMechanismProperties.ALLOWED_HOSTS = ['*'];
87+
// Set the driver's `authMechanismProperties` (non-url) `ALLOWED_HOSTS` value
88+
// to match the connection string hosts, including possible SRV "sibling" domains.
89+
options.authMechanismProperties.ALLOWED_HOSTS =
90+
matchingAllowedHosts(connectionOptions);
5891
}
5992

6093
// @ts-expect-error Will go away on @types/node update

packages/data-service/src/connection-options.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export type OIDCOptions = Omit<
66
NonNullable<DevtoolsConnectOptions['oidc']>,
77
'notifyDeviceFlow' | 'signal' | 'allowedFlows'
88
> & {
9-
// This sets the driver's `authMechanismProperties` (non-url)
10-
// `ALLOWED_HOSTS` value to `*`.
9+
// Set the driver's `authMechanismProperties` (non-url) `ALLOWED_HOSTS` value
10+
// to match the connection string hosts, including possible SRV "sibling" domains.
1111
enableUntrustedEndpoints?: boolean;
1212

1313
allowedFlows?: ExtractArrayEntryType<

0 commit comments

Comments
 (0)