Skip to content

Commit

Permalink
feat(cli-repl): Ask for tlsCertificateKeyFilePassword when it is needed
Browse files Browse the repository at this point in the history
MONGOSH-1730

When using TLS enabled with a Certificate that requires a password, the mongosh does not prompt for a passphrase automatically, failing with an error. This adds a password prompt for that case.
  • Loading branch information
gagik authored Oct 10, 2024
1 parent 0360e4b commit 3baf6f7
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
34 changes: 32 additions & 2 deletions packages/cli-repl/src/cli-repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ export class CliRepl implements MongoshIOProvider {
if (this.isPasswordMissingURI(cs)) {
cs.password = encodeURIComponent(await this.requirePassword());
}

if (await this.isTlsKeyFilePasswordMissingURI(searchParams)) {
const keyFilePassword = encodeURIComponent(
await this.requirePassword('Enter TLS key file password')
);
searchParams.set('tlsCertificateKeyFilePassword', keyFilePassword);
}

this.ensurePasswordFieldIsPresentInAuth(driverOptions);
driverUri = cs.toString();
}
Expand Down Expand Up @@ -1008,6 +1016,28 @@ export class CliRepl implements MongoshIOProvider {
);
}

async isTlsKeyFilePasswordMissingURI(
searchParams: ReturnType<
typeof ConnectionString.prototype.typedSearchParams<DevtoolsConnectOptions>
>
): Promise<boolean> {
const tlsCertificateKeyFile = searchParams.get('tlsCertificateKeyFile');
const tlsCertificateKeyFilePassword = searchParams.get(
'tlsCertificateKeyFilePassword'
);

if (tlsCertificateKeyFile && !tlsCertificateKeyFilePassword) {
const { contents } = await this.readFileUTF8(tlsCertificateKeyFile);

// Matches standard encrypted key formats for PKCS#12/PKCS#8 and PKCS#1
return (
contents.search(/(ENCRYPTED PRIVATE KEY|Proc-Type: 4,ENCRYPTED)/) !== -1
);
}

return false;
}

/**
* Sets the auth.password field to undefined in the driverOptions if the auth
* object is present with a truthy username. This is required by the driver, e.g.
Expand All @@ -1028,13 +1058,13 @@ export class CliRepl implements MongoshIOProvider {
/**
* Require the user to enter a password.
*/
async requirePassword(): Promise<string> {
async requirePassword(passwordPrompt = 'Enter password'): Promise<string> {
const passwordPromise = askpassword({
input: this.input,
output: this.promptOutput,
replacementCharacter: '*',
});
this.promptOutput.write('Enter password: ');
this.promptOutput.write(`${passwordPrompt}: `);
try {
try {
return (await passwordPromise).toString();
Expand Down
35 changes: 35 additions & 0 deletions packages/e2e-tests/test/e2e-tls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,41 @@ describe('e2e TLS', function () {
expect(logFileContents).not.to.include(CLIENT_CERT_PASSWORD);
});

it('asks for tlsCertificateKeyFilePassword when it is needed (connection string, encrypted)', async function () {
const shell = this.startTestShell({
args: [
await connectionStringWithLocalhost(server, {
serverSelectionTimeoutMS: '1500',
authMechanism: 'MONGODB-X509',
tls: 'true',
tlsCAFile: CA_CERT,
tlsCertificateKeyFile: CLIENT_CERT_ENCRYPTED,
}),
],
env,
});

await shell.waitForLine(/Enter TLS key file password:/);
await shell.executeLine(CLIENT_CERT_PASSWORD);

expect(
await shell.executeLine('db.runCommand({ connectionStatus: 1 })')
).to.include(`user: '${certUser}'`);

expect(
await shell.executeLine(
'db.getSiblingDB("$external").auth({mechanism: "MONGODB-X509"})'
)
).to.include('ok: 1');
expect(
await shell.executeLine('db.runCommand({ connectionStatus: 1 })')
).to.include(`user: '${certUser}'`);

const logPath = path.join(logBasePath, `${shell.logId}_log`);
const logFileContents = await fs.readFile(logPath, 'utf8');
expect(logFileContents).not.to.include(CLIENT_CERT_PASSWORD);
});

it('fails with invalid cert (args)', async function () {
const shell = this.startTestShell({
args: [
Expand Down
16 changes: 16 additions & 0 deletions packages/e2e-tests/test/test-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,22 @@ export class TestShell {
return this._process;
}

async waitForLine(pattern: RegExp, start = 0): Promise<void> {
await eventually(() => {
const output = this._output.slice(start);
const lines = output.split('\n');
const found = !!lines.filter((l) => pattern.exec(l));
if (!found) {
throw new assert.AssertionError({
message: 'expected line',
expected: pattern.toString(),
actual:
this._output.slice(0, start) + '[line search starts here]' + output,
});
}
});
}

async waitForPrompt(start = 0): Promise<void> {
await eventually(() => {
const output = this._output.slice(start);
Expand Down

0 comments on commit 3baf6f7

Please sign in to comment.