Skip to content

Commit a92380b

Browse files
VSCODE-125: Connect with SSL to mongodb shell (#120)
* fix: set proper ssl options when launching mongodb shell * refactor: remove dev conf * feat: have certificate and key inputs combined together * refactor: remove unused key check * refactor: remove extra uri check * fix: get binaries of ssl files * fix: resolve files path * refactor: check if driver options present * refactor: add checks and catch errors * refactor: check ssl option present before assign * refactor: use ssl options to support older mongo
1 parent ea188c4 commit a92380b

File tree

9 files changed

+139
-84
lines changed

9 files changed

+139
-84
lines changed

src/language/mongoDBService.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ import { Visitor } from './visitor';
99
import { ServerCommands, PlaygroundRunParameters } from './serverCommands';
1010

1111
const path = require('path');
12+
const fs = require('fs');
1213

1314
export const languageServerWorkerFileName = 'languageServerWorker.js';
1415

16+
type SslFileOptions = {
17+
sslCA?: string;
18+
sslKey?: string;
19+
sslCert?: string;
20+
};
21+
1522
export default class MongoDBService {
1623
_serviceProvider?: CliServiceProvider;
1724
_runtime?: ElectronRuntime;
1825
_connection: any;
1926
_connectionString?: string;
20-
_connectionOptions?: object;
27+
_connectionOptions?: any;
2128
_cachedFields: object;
2229
_cachedDatabases: [];
2330
_cachedCollections: object;
@@ -43,6 +50,47 @@ export default class MongoDBService {
4350
return this._connectionOptions;
4451
}
4552

53+
private isSslConnection(connectionOptions: any): boolean {
54+
return !!(
55+
connectionOptions &&
56+
(connectionOptions.sslCA ||
57+
connectionOptions.sslCert ||
58+
connectionOptions.sslPass)
59+
);
60+
}
61+
62+
private readSslFileSync(sslOption: string | string[]): any {
63+
if (Array.isArray(sslOption)) {
64+
return fs.readFileSync(sslOption[0]);
65+
}
66+
67+
if (typeof sslOption !== 'string') {
68+
return;
69+
}
70+
71+
return fs.readFileSync(sslOption);
72+
}
73+
74+
private loadSslBinaries(): void {
75+
if (this._connectionOptions.sslCA) {
76+
this._connectionOptions.sslCA = this.readSslFileSync(
77+
this._connectionOptions.sslCA
78+
);
79+
}
80+
81+
if (this._connectionOptions.sslKey) {
82+
this._connectionOptions.sslKey = this.readSslFileSync(
83+
this._connectionOptions.sslKey
84+
);
85+
}
86+
87+
if (this._connectionOptions.sslCert) {
88+
this._connectionOptions.sslCert = this.readSslFileSync(
89+
this._connectionOptions.sslCert
90+
);
91+
}
92+
}
93+
4694
public async connectToServiceProvider(params: {
4795
connectionString?: string;
4896
connectionOptions?: any;
@@ -54,13 +102,25 @@ export default class MongoDBService {
54102
this.clearCurrentSessionCollections();
55103

56104
this._connectionString = params.connectionString;
57-
this._connectionOptions = params.connectionOptions;
105+
this._connectionOptions = params.connectionOptions || {};
58106
this._extensionPath = params.extensionPath;
59107

60108
if (!this._connectionString) {
61109
return Promise.resolve(false);
62110
}
63111

112+
if (this.isSslConnection(this._connectionOptions)) {
113+
try {
114+
this.loadSslBinaries();
115+
} catch (error) {
116+
this._connection.console.log(
117+
`SSL FILES read error: ${util.inspect(error)}`
118+
);
119+
120+
return Promise.resolve(false);
121+
}
122+
}
123+
64124
try {
65125
this._serviceProvider = await CliServiceProvider.connect(
66126
this._connectionString,

src/mdbExtensionController.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -397,35 +397,84 @@ export default class MDBExtensionController implements vscode.Disposable {
397397
);
398398
}
399399

400-
public openMongoDBShell(): Promise<boolean> {
401-
let mdbConnectionString;
400+
private isSslConnection(activeConnectionModel: any): boolean {
401+
return !!(
402+
activeConnectionModel &&
403+
activeConnectionModel.driverOptions &&
404+
(activeConnectionModel.driverOptions.sslCA ||
405+
activeConnectionModel.driverOptions.sslCert ||
406+
activeConnectionModel.driverOptions.sslPass)
407+
);
408+
}
409+
410+
private getSslOptionsString(driverOptions: any): string {
411+
let mdbSslOptionsString = '--ssl';
412+
413+
if (!driverOptions.checkServerIdentity) {
414+
mdbSslOptionsString = `${mdbSslOptionsString} --sslAllowInvalidHostnames`;
415+
}
402416

403-
if (this._connectionController) {
404-
const activeConnectionModel = this._connectionController
405-
.getActiveConnectionModel()
406-
?.getAttributes({ derived: true });
417+
if (!driverOptions.sslValidate) {
418+
mdbSslOptionsString = `${mdbSslOptionsString} --sslAllowInvalidCertificates`;
419+
}
420+
421+
if (driverOptions.sslCA) {
422+
mdbSslOptionsString = `${mdbSslOptionsString} --sslCAFile ${driverOptions.sslCA}`;
423+
}
407424

408-
mdbConnectionString = activeConnectionModel
409-
? activeConnectionModel.driverUrlWithSsh
410-
: '';
425+
if (driverOptions.sslCert) {
426+
mdbSslOptionsString = `${mdbSslOptionsString} --sslPEMKeyFile ${driverOptions.sslCert}`;
411427
}
412428

413-
if (!mdbConnectionString) {
429+
if (driverOptions.sslPass) {
430+
mdbSslOptionsString = `${mdbSslOptionsString} --sslPEMKeyPassword $MDB_SSL_CERTIFICATE_KEY_FILE_PASSWORD`;
431+
}
432+
433+
return mdbSslOptionsString;
434+
}
435+
436+
public openMongoDBShell(): Promise<boolean> {
437+
const mongoDBShellEnv: any = {};
438+
let mdbSslOptionsString = '';
439+
440+
if (
441+
!this._connectionController ||
442+
!this._connectionController.isCurrentlyConnected()
443+
) {
414444
vscode.window.showErrorMessage(
415445
'You need to be connected before launching the MongoDB Shell.'
416446
);
417447

418448
return Promise.resolve(false);
419449
}
420450

451+
const activeConnectionModel = this._connectionController
452+
.getActiveConnectionModel()
453+
?.getAttributes({ derived: true });
454+
455+
mongoDBShellEnv['MDB_CONNECTION_STRING'] = activeConnectionModel
456+
? activeConnectionModel.driverUrlWithSsh
457+
: '';
458+
459+
if (activeConnectionModel && this.isSslConnection(activeConnectionModel)) {
460+
mdbSslOptionsString = this.getSslOptionsString(
461+
activeConnectionModel.driverOptions
462+
);
463+
464+
if (activeConnectionModel.driverOptions.sslPass) {
465+
mongoDBShellEnv['MDB_SSL_CERTIFICATE_KEY_FILE_PASSWORD'] =
466+
activeConnectionModel.driverOptions.sslPass;
467+
}
468+
}
469+
470+
const shellCommand = vscode.workspace.getConfiguration('mdb').get('shell');
421471
const mongoDBShell = vscode.window.createTerminal({
422472
name: 'MongoDB Shell',
423-
env: { MDB_CONNECTION_STRING: mdbConnectionString }
473+
env: mongoDBShellEnv
424474
});
425-
const shellCommand = vscode.workspace.getConfiguration('mdb').get('shell');
426475

427476
mongoDBShell.sendText(
428-
`${shellCommand} $MDB_CONNECTION_STRING; unset MDB_CONNECTION_STRING`
477+
`${shellCommand} ${mdbSslOptionsString} $MDB_CONNECTION_STRING; unset MDB_CONNECTION_STRING; unset MDB_SSL_CERTIFICATE_KEY_FILE_PASSWORD`
429478
);
430479
mongoDBShell.show();
431480

src/test/suite/extension.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ suite('Extension Test Suite', () => {
1515

1616
let fakeShowErrorMessage: any;
1717
let fakeGetActiveConnectionModel: any;
18+
let fakeIsCurrentlyConnected: any;
1819
let createTerminalSpy: any;
1920

2021
beforeEach(() => {
@@ -25,6 +26,10 @@ suite('Extension Test Suite', () => {
2526
mockMDBExtension._connectionController,
2627
'getActiveConnectionModel'
2728
);
29+
fakeIsCurrentlyConnected = sandbox.stub(
30+
mockMDBExtension._connectionController,
31+
'isCurrentlyConnected'
32+
);
2833

2934
createTerminalSpy = sinon.spy(vscode.window, 'createTerminal');
3035
});
@@ -103,6 +108,7 @@ suite('Extension Test Suite', () => {
103108
port: 27018
104109
})
105110
);
111+
fakeIsCurrentlyConnected.returns(true);
106112

107113
try {
108114
await mockMDBExtension.openMongoDBShell();
@@ -138,6 +144,7 @@ suite('Extension Test Suite', () => {
138144
sshTunnelPassword: 'password'
139145
})
140146
);
147+
fakeIsCurrentlyConnected.returns(true);
141148

142149
try {
143150
await mockMDBExtension.openMongoDBShell();

src/test/suite/views/webviewController.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ suite('Connect Form View Test Suite', () => {
299299
testTelemetryController
300300
);
301301
const fakeVSCodeOpenDialog = sinon.fake.resolves({
302-
path: './somefilepath/test.text'
302+
path: '/somefilepath/test.text'
303303
});
304304

305305
let messageRecieved;
@@ -361,7 +361,7 @@ suite('Connect Form View Test Suite', () => {
361361
html: '',
362362
postMessage: (message: any): void => {
363363
assert(message.action === 'file_action');
364-
assert(message.files[0] === 'somefilepath/test.text');
364+
assert(message.files[0] === path.resolve('/somefilepath/test.text'));
365365

366366
testConnectionController.disconnect();
367367
done();

src/views/webview-app/components/connect-form/ssl/ssl-server-client-validation.tsx

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
ActionTypes,
99
OnChangeSSLCAAction,
1010
OnChangeSSLCertAction,
11-
OnChangeSSLKeyAction,
1211
SSLPassChangedAction
1312
} from '../../../store/actions';
1413
import { AppState } from '../../../store/store';
@@ -19,14 +18,12 @@ type stateProps = {
1918
isValid: boolean;
2019
sslCA?: string[];
2120
sslCert?: string[];
22-
sslKey?: string[];
2321
sslPass?: string;
2422
};
2523

2624
type dispatchProps = {
2725
onChangeSSLCA: () => void;
2826
onChangeSSLCertificate: () => void;
29-
onChangeSSLPrivateKey: () => void;
3027
sslPrivateKeyPasswordChanged: (newSSLPass: string) => void;
3128
};
3229

@@ -49,13 +46,6 @@ class SSLServerClientValidation extends React.Component<props> {
4946
this.props.onChangeSSLCertificate();
5047
};
5148

52-
/**
53-
* Handles sslKey change.
54-
*/
55-
onClientPrivateKeyChanged = (): void => {
56-
this.props.onChangeSSLPrivateKey();
57-
};
58-
5949
/**
6050
* Handles sslPass change.
6151
*
@@ -66,7 +56,7 @@ class SSLServerClientValidation extends React.Component<props> {
6656
};
6757

6858
render(): React.ReactNode {
69-
const { isValid, sslCA, sslCert, sslKey, sslPass } = this.props;
59+
const { isValid, sslCA, sslCert, sslPass } = this.props;
7060

7161
return (
7262
<div
@@ -80,24 +70,15 @@ class SSLServerClientValidation extends React.Component<props> {
8070
onClick={this.onCertificateAuthorityChanged}
8171
values={sslCA}
8272
link="https://docs.mongodb.com/manual/tutorial/configure-ssl/#certificate-authorities"
83-
multi
8473
/>
8574
<FileInputButton
86-
label="Client Certificate"
75+
label="Client Certificate and Key"
8776
id="sslCert"
8877
error={!isValid && sslCert === undefined}
8978
onClick={this.onClientCertificateChanged}
9079
values={sslCert}
9180
link="https://docs.mongodb.com/manual/tutorial/configure-ssl/#pem-file"
9281
/>
93-
<FileInputButton
94-
label="Client Private Key"
95-
id="sslKey"
96-
error={!isValid && sslKey === undefined}
97-
onClick={this.onClientPrivateKeyChanged}
98-
values={sslKey}
99-
link="https://docs.mongodb.com/manual/tutorial/configure-ssl/#pem-file"
100-
/>
10182
<FormInput
10283
label="Client Key Password"
10384
name="sslPass"
@@ -117,7 +98,6 @@ const mapStateToProps = (state: AppState): stateProps => {
11798
isValid: state.isValid,
11899
sslCA: state.currentConnection.sslCA,
119100
sslCert: state.currentConnection.sslCert,
120-
sslKey: state.currentConnection.sslKey,
121101
sslPass: state.currentConnection.sslPass
122102
};
123103
};
@@ -129,9 +109,6 @@ const mapDispatchToProps: dispatchProps = {
129109
onChangeSSLCertificate: (): OnChangeSSLCertAction => ({
130110
type: ActionTypes.ON_CHANGE_SSL_CERT
131111
}),
132-
onChangeSSLPrivateKey: (): OnChangeSSLKeyAction => ({
133-
type: ActionTypes.ON_CHANGE_SSL_KEY
134-
}),
135112
sslPrivateKeyPasswordChanged: (newSSLPass: string): SSLPassChangedAction => ({
136113
type: ActionTypes.SSL_PASS_CHANGED,
137114
sslPass: newSSLPass

src/views/webview-app/connection-model/connection-model.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ const validateSsl = (attrs: ConnectionModel): void => {
105105
throw new TypeError('sslCA is required when ssl is ALL.');
106106
}
107107

108-
if (!attrs.sslKey) {
109-
throw new TypeError('sslKey is required when ssl is ALL.');
110-
}
111-
112108
if (!attrs.sslCert) {
113109
throw new TypeError('sslCert is required when ssl is ALL.');
114110
}

0 commit comments

Comments
 (0)