Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(NODE-3591): tlsCertificateKeyFile option does not default cert #2979

Merged
merged 5 commits into from Sep 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/connection_string.ts
Expand Up @@ -176,7 +176,7 @@ function getBoolean(name: string, value: unknown): boolean {
const valueString = String(value).toLowerCase();
if (TRUTHS.has(valueString)) return true;
if (FALSEHOODS.has(valueString)) return false;
throw new MongoParseError(`For ${name} Expected stringified boolean value, got: ${value}`);
throw new MongoParseError(`Expected ${name} to be stringified boolean value, got: ${value}`);
}

function getInt(name: string, value: unknown): number {
Expand Down Expand Up @@ -335,6 +335,19 @@ export function parseOptions(
allOptions.set(key, values);
}

if (allOptions.has('tlsCertificateKeyFile') && !allOptions.has('tlsCertificateFile')) {
allOptions.set('tlsCertificateFile', allOptions.get('tlsCertificateKeyFile'));
}

if (allOptions.has('tls') || allOptions.has('ssl')) {
const tlsAndSslOpts = (allOptions.get('tls') || [])
.concat(allOptions.get('ssl') || [])
.map(getBoolean.bind(null, 'tls/ssl'));
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
if (new Set(tlsAndSslOpts).size !== 1) {
throw new MongoParseError('All values of tls/ssl must be the same.');
}
}

const unsupportedOptions = setDifference(
allKeys,
Array.from(Object.keys(OPTIONS)).map(s => s.toLowerCase())
Expand Down Expand Up @@ -384,18 +397,6 @@ export function parseOptions(
mongoOptions.dbName = 'test';
}

if (allOptions.has('tls')) {
if (new Set(allOptions.get('tls')?.map(getBoolean)).size !== 1) {
throw new MongoParseError('All values of tls must be the same.');
}
}

if (allOptions.has('ssl')) {
if (new Set(allOptions.get('ssl')?.map(getBoolean)).size !== 1) {
throw new MongoParseError('All values of ssl must be the same.');
}
}

checkTLSOptions(mongoOptions);

if (options.promiseLibrary) PromiseProvider.set(options.promiseLibrary);
Expand Down
2 changes: 1 addition & 1 deletion src/mongo_client.ts
Expand Up @@ -112,7 +112,7 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC
ssl?: boolean;
/** Specifies the location of a local TLS Certificate */
tlsCertificateFile?: string;
/** Specifies the location of a local .pem file that contains either the clients TLS/SSL certificate or the clients TLS/SSL certificate and key. */
/** Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key or only the client's TLS/SSL key when tlsCertificateFile is used to provide the certificate. */
tlsCertificateKeyFile?: string;
/** Specifies the password to de-crypt the tlsCertificateKeyFile. */
tlsCertificateKeyFilePassword?: string;
Expand Down
2 changes: 1 addition & 1 deletion test/unit/core/connection_string.test.js
Expand Up @@ -136,7 +136,7 @@ describe('Connection String', function () {
it('should validate non-equal tls values', function () {
expect(() => parseOptions('mongodb://localhost/?tls=true&tls=false')).to.throw(
MongoParseError,
'All values of tls must be the same.'
'All values of tls/ssl must be the same.'
);
});
});
Expand Down
91 changes: 90 additions & 1 deletion test/unit/mongo_client_options.test.js
Expand Up @@ -128,7 +128,7 @@ describe('MongoOptions', function () {
ssl: true,
sslPass: 'pass',
sslValidate: true,
tls: false,
tls: true,
tlsAllowInvalidCertificates: true,
tlsAllowInvalidHostnames: true,
tlsCertificateKeyFilePassword: 'tls-pass',
Expand Down Expand Up @@ -245,6 +245,18 @@ describe('MongoOptions', function () {
expect(options).has.property('tls', false);
});

it('ssl= can be used to set tls=true', function () {
const options = parseOptions('mongodb+srv://server.example.com/?ssl=true');
expect(options).has.property('srvHost', 'server.example.com');
expect(options).has.property('tls', true);
});

it('tls= can be used to set tls=true', function () {
const options = parseOptions('mongodb+srv://server.example.com/?tls=true');
expect(options).has.property('srvHost', 'server.example.com');
expect(options).has.property('tls', true);
});

it('supports ReadPreference option in url', function () {
const options = parseOptions('mongodb://localhost/?readPreference=nearest');
expect(options.readPreference).to.be.an.instanceof(ReadPreference);
Expand Down Expand Up @@ -366,6 +378,83 @@ describe('MongoOptions', function () {
expect(optionsUndefined.checkServerIdentity).to.equal(undefined);
});

describe('[tls certificate handling]', () => {
before(() => {
fs.writeFileSync('testCertKey.pem', 'cert key');
fs.writeFileSync('testKey.pem', 'test key');
fs.writeFileSync('testCert.pem', 'test cert');
});

after(() => {
fs.unlinkSync('testCertKey.pem');
fs.unlinkSync('testKey.pem');
fs.unlinkSync('testCert.pem');
});

it('correctly sets the cert and key if only tlsCertificateKeyFile is provided', function () {
const optsFromObject = parseOptions('mongodb://localhost/', {
tlsCertificateKeyFile: 'testCertKey.pem'
});
expect(optsFromObject).to.have.property('cert', 'cert key');
expect(optsFromObject).to.have.property('key', 'cert key');

const optsFromUri = parseOptions('mongodb://localhost?tlsCertificateKeyFile=testCertKey.pem');
expect(optsFromUri).to.have.property('cert', 'cert key');
expect(optsFromUri).to.have.property('key', 'cert key');
});

it('correctly sets the cert and key if both tlsCertificateKeyFile and tlsCertificateFile is provided', function () {
const optsFromObject = parseOptions('mongodb://localhost/', {
tlsCertificateKeyFile: 'testKey.pem',
tlsCertificateFile: 'testCert.pem'
});
expect(optsFromObject).to.have.property('cert', 'test cert');
expect(optsFromObject).to.have.property('key', 'test key');

const optsFromUri = parseOptions(
'mongodb://localhost?tlsCertificateKeyFile=testKey.pem&tlsCertificateFile=testCert.pem'
);
expect(optsFromUri).to.have.property('cert', 'test cert');
expect(optsFromUri).to.have.property('key', 'test key');
});
});

it('throws an error if multiple tls parameters are not all set to the same value', () => {
expect(() => parseOptions('mongodb://localhost?tls=true&tls=false')).to.throw(
'All values of tls/ssl must be the same.'
);
});

it('throws an error if multiple ssl parameters are not all set to the same value', () => {
expect(() => parseOptions('mongodb://localhost?ssl=true&ssl=false')).to.throw(
'All values of tls/ssl must be the same.'
);
});

it('throws an error if tls and ssl parameters are not all set to the same value', () => {
expect(() => parseOptions('mongodb://localhost?tls=true&ssl=false')).to.throw(
'All values of tls/ssl must be the same.'
);
expect(() => parseOptions('mongodb://localhost?tls=false&ssl=true')).to.throw(
'All values of tls/ssl must be the same.'
);
});

it('correctly sets tls if multiple tls parameters are all set to the same value', () => {
expect(parseOptions('mongodb://localhost?tls=true&tls=true')).to.have.property('tls', true);
expect(parseOptions('mongodb://localhost?tls=false&tls=false')).to.have.property('tls', false);
});

it('correctly sets tls if multiple ssl parameters are all set to the same value', () => {
expect(parseOptions('mongodb://localhost?ssl=true&ssl=true')).to.have.property('tls', true);
expect(parseOptions('mongodb://localhost?ssl=false&ssl=false')).to.have.property('tls', false);
});

it('correctly sets tls if tls and ssl parameters are all set to the same value', () => {
expect(parseOptions('mongodb://localhost?ssl=true&tls=true')).to.have.property('tls', true);
expect(parseOptions('mongodb://localhost?ssl=false&tls=false')).to.have.property('tls', false);
});

it('transforms tlsInsecure correctly', function () {
const optionsTrue = parseOptions('mongodb://localhost/', {
tlsInsecure: true
Expand Down