Skip to content

Commit 83d7efb

Browse files
addaleaxgribnoysupalenakhineika
authored
fix(compass): improve validation of command line arguments COMPASS-7260 (#5363)
Co-authored-by: Sergey Petushkov <[email protected]> Co-authored-by: Anna Henningsen <[email protected]> Co-authored-by: Alena Khineika <[email protected]>
1 parent 9fb29c0 commit 83d7efb

File tree

10 files changed

+765
-71
lines changed

10 files changed

+765
-71
lines changed

.evergreen/functions.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ variables:
8383

8484
# This is here with the variables because anchors aren't supported across includes
8585
post:
86+
- command: shell.exec
87+
params:
88+
working_dir: src
89+
shell: bash
90+
script: |
91+
tar czvf all-e2e-logs.tgz packages/compass-e2e-tests/.log
8692
- command: s3.put
8793
params:
8894
<<: *save-artifact-params-private
@@ -92,6 +98,7 @@ post:
9298
- src/packages/compass-e2e-tests/.nyc_output/coverage.json
9399
- src/.testserver/logs/*
94100
- src/.evergreen/logs/*
101+
- src/all-e2e-logs.tgz
95102
remote_file: ${project}/${revision}_${revision_order_id}/${build_variant}/${task_name}
96103
content_type: text/plain
97104
- command: s3.put
@@ -367,7 +374,7 @@ functions:
367374
sleep 20
368375
done
369376
echo "SSH connection established after $attempts attempts"
370-
377+
371378
# Write the host info so that it can be used by the signing tool
372379
if [[ $OSTYPE == "cygwin" ]]; then
373380
identity_file=$(cygpath -wa "$identity_file")
@@ -566,7 +573,7 @@ functions:
566573
tar -xvz)
567574
export COMPASS_CRYPT_LIBRARY_PATH=$(echo $PWD/mongodb-crypt/lib/mongo_*_v1.*)
568575
npm run test-csfle --workspace mongodb-data-service
569-
576+
570577
verify-artifacts:
571578
- command: shell.exec
572579
params:

package-lock.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-e2e-tests/helpers/compass.ts

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export class Compass {
129129
logPath?: string;
130130
userDataPath?: string;
131131
appName?: string;
132+
mainProcessPid?: number;
132133

133134
constructor(
134135
name: string,
@@ -196,20 +197,17 @@ export class Compass {
196197

197198
// get the app logPath out of electron early in case the app crashes before we
198199
// close it and load the logs
199-
this.logPath = await this.browser.execute(() => {
200-
// eslint-disable-next-line @typescript-eslint/no-var-requires
201-
return require('electron').ipcRenderer.invoke('compass:logPath');
202-
});
203-
204-
this.userDataPath = await this.browser.execute(() => {
205-
// eslint-disable-next-line @typescript-eslint/no-var-requires
206-
return require('electron').ipcRenderer.invoke('compass:userDataPath');
207-
});
208-
209-
this.appName = await this.browser.execute(() => {
210-
// eslint-disable-next-line @typescript-eslint/no-var-requires
211-
return require('electron').ipcRenderer.invoke('compass:appName');
212-
});
200+
[this.logPath, this.userDataPath, this.appName, this.mainProcessPid] =
201+
await this.browser.execute(() => {
202+
// eslint-disable-next-line @typescript-eslint/no-var-requires
203+
const { ipcRenderer } = require('electron');
204+
return Promise.all([
205+
ipcRenderer.invoke('compass:logPath'),
206+
ipcRenderer.invoke('compass:userDataPath'),
207+
ipcRenderer.invoke('compass:appName'),
208+
ipcRenderer.invoke('compass:mainProcessPid'),
209+
] as const);
210+
});
213211
}
214212

215213
addDebugger(): void {
@@ -366,14 +364,23 @@ export class Compass {
366364
}
367365
}
368366

369-
debug('Stopping Compass application');
367+
const copyCompassLog = async () => {
368+
const compassLog = await getCompassLog(this.logPath ?? '');
369+
const compassLogPath = path.join(
370+
LOG_PATH,
371+
`compass-log.${this.name}.log`
372+
);
373+
debug(`Writing Compass application log to ${compassLogPath}`);
374+
await fs.writeFile(compassLogPath, compassLog.raw);
375+
return compassLog;
376+
};
377+
378+
// Copy log files before stopping in case that stopping itself fails and needs to be debugged
379+
await copyCompassLog();
380+
debug(`Stopping Compass application [${this.name}]`);
370381
await this.browser.deleteSession();
371382

372-
const compassLog = await getCompassLog(this.logPath ?? '');
373-
const compassLogPath = path.join(LOG_PATH, `compass-log.${this.name}.log`);
374-
debug(`Writing Compass application log to ${compassLogPath}`);
375-
await fs.writeFile(compassLogPath, compassLog.raw);
376-
this.logs = compassLog.structured;
383+
this.logs = (await copyCompassLog()).structured;
377384
}
378385
}
379386

@@ -677,20 +684,9 @@ async function startCompass(
677684
.join(', ')}`
678685
);
679686

680-
filteredProcesses.forEach((p) => {
681-
try {
682-
debug(`Killing process ${p.name} with PID ${p.pid}`);
683-
if (process.platform === 'win32') {
684-
crossSpawn.sync('taskkill', ['/PID', String(p.pid), '/F', '/T']);
685-
} else {
686-
process.kill(p.pid);
687-
}
688-
} catch (err) {
689-
debug(`Failed to kill process ${p.name} with PID ${p.pid}`, {
690-
error: (err as Error).stack,
691-
});
692-
}
693-
});
687+
for (const p of filteredProcesses) {
688+
tryKillProcess(p.pid, p.name);
689+
}
694690
throw err;
695691
}
696692

@@ -704,6 +700,21 @@ async function startCompass(
704700
return compass;
705701
}
706702

703+
function tryKillProcess(pid: number, name = '<unknown>'): void {
704+
try {
705+
debug(`Killing process ${name} with PID ${pid}`);
706+
if (process.platform === 'win32') {
707+
crossSpawn.sync('taskkill', ['/PID', String(pid), '/F', '/T']);
708+
} else {
709+
process.kill(pid);
710+
}
711+
} catch (err) {
712+
debug(`Failed to kill process ${name} with PID ${pid}`, {
713+
error: (err as Error).stack,
714+
});
715+
}
716+
}
717+
707718
/**
708719
* @param {string} logPath The compass application log path
709720
* @returns {Promise<CompassLog>}
@@ -918,18 +929,18 @@ export async function cleanup(compass?: Compass): Promise<void> {
918929
}
919930

920931
let timeoutId;
921-
const timeoutPromise = new Promise<void>((resolve) => {
932+
const timeoutPromise = new Promise<'timeout'>((resolve) => {
922933
timeoutId = setTimeout(() => {
923-
console.error('It took too long to close compass');
924-
resolve();
934+
console.error(`It took too long to close compass [${compass.name}]`);
935+
resolve('timeout');
925936
}, 30000);
926937
});
927938

928-
const closePromise = (async function close(): Promise<void> {
939+
const closePromise = (async function close(): Promise<'closed'> {
929940
try {
930941
await compass.stop();
931942
} catch (err) {
932-
debug('An error occurred while stopping compass:');
943+
debug(`An error occurred while stopping compass: [${compass.name}]`);
933944
debug(err);
934945
try {
935946
// make sure the process can exit
@@ -939,10 +950,18 @@ export async function cleanup(compass?: Compass): Promise<void> {
939950
}
940951
}
941952
clearTimeout(timeoutId);
942-
return;
953+
return 'closed';
943954
})();
944955

945-
return Promise.race([timeoutPromise, closePromise]);
956+
const result = await Promise.race([timeoutPromise, closePromise]);
957+
if (result === 'timeout' && compass.mainProcessPid) {
958+
try {
959+
debug(`Trying to manually kill Compass [${compass.name}]`);
960+
tryKillProcess(compass.mainProcessPid, compass.name);
961+
} catch {
962+
/* already logged ... */
963+
}
964+
}
946965
}
947966

948967
function pathName(text: string) {

packages/compass-preferences-model/src/preferences-schema.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export type CliOnlyPreferences = {
8181
version?: boolean;
8282
help?: boolean;
8383
showExampleConfig?: boolean;
84+
trustedConnectionString?: boolean;
8485
};
8586

8687
export type NonUserPreferences = {
@@ -747,6 +748,22 @@ const cliOnlyPreferencesProps: Required<{
747748
validator: z.boolean().optional(),
748749
type: 'boolean',
749750
},
751+
/**
752+
* Allows the automatic initiation of the connection establishment process
753+
* when launching Compass from the command line to indicate that the connection string comes from a trusted source,
754+
* even if the provided connection string contains disallowed connection options.
755+
*/
756+
trustedConnectionString: {
757+
ui: false,
758+
cli: true,
759+
global: false,
760+
description: {
761+
short: 'Always allow to Automatically Connect',
762+
long: 'Allow automatic connection establishment when launching Compass, even if the provided connection string contains connection options that would not be accepted when coming from an untrusted source',
763+
},
764+
validator: z.boolean().default(false),
765+
type: 'boolean',
766+
},
750767
};
751768

752769
const nonUserPreferences: Required<{

packages/compass/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@
213213
"lodash": "^4.17.21",
214214
"make-fetch-happen": "^8.0.14",
215215
"marky": "^1.2.1",
216+
"mongodb": "^6.2.0",
217+
"mongodb-build-info": "^1.7.0",
216218
"mongodb-connection-string-url": "^2.6.0",
217219
"mongodb-data-service": "^22.17.4",
218220
"mongodb-instance-model": "^12.17.4",

packages/compass/src/app/auto-connect.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { promises as fsPromises } from 'fs';
44
import { UUID } from 'bson';
55
import { ipcRenderer } from 'hadron-ipc';
66
import { ConnectionString } from 'mongodb-connection-string-url';
7+
import { getConnectionStringFromArgs } from '../main/auto-connect';
78
import type { AutoConnectPreferences } from '../main/auto-connect';
89

910
async function getWindowAutoConnectPreferences(): Promise<AutoConnectPreferences> {
@@ -60,7 +61,7 @@ export async function loadAutoConnectInfo(
6061
});
6162
let id: string | undefined;
6263
if (positionalArguments.length > 0) {
63-
id = positionalArguments[0];
64+
id = getConnectionStringFromArgs(positionalArguments);
6465
} else if (connections.length === 1) {
6566
id = connections[0].id;
6667
}
@@ -77,11 +78,13 @@ export async function loadAutoConnectInfo(
7778
}
7879
return applyUsernameAndPassword(connectionInfo, { username, password });
7980
} else {
81+
const connectionString = getConnectionStringFromArgs(positionalArguments);
82+
if (!connectionString) {
83+
throw new Error('Could not find a connection string');
84+
}
8085
return applyUsernameAndPassword(
8186
{
82-
connectionOptions: {
83-
connectionString: positionalArguments[0],
84-
},
87+
connectionOptions: { connectionString },
8588
id: new UUID().toString(),
8689
},
8790
{ username, password }

packages/compass/src/main/application.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ class CompassApplication {
297297
ipcMain?.handle('compass:appName', () => {
298298
return app.getName();
299299
});
300+
301+
ipcMain?.handle('compass:mainProcessPid', () => {
302+
return process.pid;
303+
});
300304
}
301305

302306
private static async setupLogging(): Promise<void> {

0 commit comments

Comments
 (0)