Skip to content

Commit 33c86c9

Browse files
authored
feat(NODE-5566): add ability to provide CRL file via tlsCRLFile (#3834)
1 parent 2323ca8 commit 33c86c9

8 files changed

+116
-40
lines changed

.evergreen/config.in.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,6 @@ functions:
636636
export PROJECT_DIRECTORY="$(pwd)"
637637
export NODE_LTS_VERSION=${NODE_LTS_VERSION}
638638
export DRIVERS_TOOLS="${DRIVERS_TOOLS}"
639-
export SSL_CA_FILE="${SSL_CA_FILE}"
640-
export SSL_KEY_FILE="${SSL_KEY_FILE}"
641639
export MONGODB_URI="${MONGODB_URI}"
642640
643641
bash ${PROJECT_DIRECTORY}/.evergreen/run-tls-tests.sh

.evergreen/config.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,6 @@ functions:
589589
export PROJECT_DIRECTORY="$(pwd)"
590590
export NODE_LTS_VERSION=${NODE_LTS_VERSION}
591591
export DRIVERS_TOOLS="${DRIVERS_TOOLS}"
592-
export SSL_CA_FILE="${SSL_CA_FILE}"
593-
export SSL_KEY_FILE="${SSL_KEY_FILE}"
594592
export MONGODB_URI="${MONGODB_URI}"
595593
596594
bash ${PROJECT_DIRECTORY}/.evergreen/run-tls-tests.sh

.evergreen/run-tls-tests.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ set -o errexit # Exit the script with error if any of the commands fail
44

55
source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh"
66

7-
export SSL_KEY_FILE="$DRIVERS_TOOLS/.evergreen/x509gen/client.pem"
8-
export SSL_CA_FILE="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
7+
export TLS_KEY_FILE="$DRIVERS_TOOLS/.evergreen/x509gen/client.pem"
8+
export TLS_CA_FILE="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem"
9+
export TLS_CRL_FILE="$DRIVERS_TOOLS/.evergreen/x509gen/crl.pem"
910

1011
npm run check:tls

src/connection_string.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,9 @@ export const OPTIONS = {
10951095
tlsCAFile: {
10961096
type: 'string'
10971097
},
1098+
tlsCRLFile: {
1099+
type: 'string'
1100+
},
10981101
tlsCertificateKeyFile: {
10991102
type: 'string'
11001103
},

src/mongo_client.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC
117117
tlsCertificateKeyFilePassword?: string;
118118
/** Specifies the location of a local .pem file that contains the root certificate chain from the Certificate Authority. This file is used to validate the certificate presented by the mongod/mongos instance. */
119119
tlsCAFile?: string;
120+
/** Specifies the location of a local CRL .pem file that contains the client revokation list. */
121+
tlsCRLFile?: string;
120122
/** Bypasses validation of the certificates presented by the mongod/mongos instance */
121123
tlsAllowInvalidCertificates?: boolean;
122124
/** Disables hostname validation of the certificate presented by the mongod/mongos instance. */
@@ -437,6 +439,9 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
437439
if (typeof options.tlsCAFile === 'string') {
438440
options.ca ??= await fs.readFile(options.tlsCAFile);
439441
}
442+
if (typeof options.tlsCRLFile === 'string') {
443+
options.crl ??= await fs.readFile(options.tlsCRLFile);
444+
}
440445
if (typeof options.tlsCertificateKeyFile === 'string') {
441446
if (!options.key || !options.cert) {
442447
const contents = await fs.readFile(options.tlsCertificateKeyFile);
@@ -790,7 +795,7 @@ export interface MongoOptions
790795
* | nodejs native option | driver spec equivalent option name | driver option type |
791796
* |:----------------------|:----------------------------------------------|:-------------------|
792797
* | `ca` | `tlsCAFile` | `string` |
793-
* | `crl` | N/A | `string` |
798+
* | `crl` | `tlsCRLFile` | `string` |
794799
* | `cert` | `tlsCertificateKeyFile` | `string` |
795800
* | `key` | `tlsCertificateKeyFile` | `string` |
796801
* | `passphrase` | `tlsCertificateKeyFilePassword` | `string` |
@@ -805,17 +810,17 @@ export interface MongoOptions
805810
* to a no-op and `rejectUnauthorized` to the inverse value of `tlsAllowInvalidCertificates`. If
806811
* `tlsAllowInvalidCertificates` is not set, then `rejectUnauthorized` will be set to `true`.
807812
*
808-
* ### Note on `tlsCAFile` and `tlsCertificateKeyFile`
813+
* ### Note on `tlsCAFile`, `tlsCertificateKeyFile` and `tlsCRLFile`
809814
*
810-
* The files specified by the paths passed in to the `tlsCAFile` and `tlsCertificateKeyFile` fields
811-
* are read lazily on the first call to `MongoClient.connect`. Once these files have been read and
812-
* the `ca`, `cert` and `key` fields are populated, they will not be read again on subsequent calls to
815+
* The files specified by the paths passed in to the `tlsCAFile`, `tlsCertificateKeyFile` and `tlsCRLFile`
816+
* fields are read lazily on the first call to `MongoClient.connect`. Once these files have been read and
817+
* the `ca`, `cert`, `crl` and `key` fields are populated, they will not be read again on subsequent calls to
813818
* `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca`,
814-
* `cert` and `key` fields will be undefined.
819+
* `cert`, `crl` and `key` fields will be undefined.
815820
*/
816821
tls: boolean;
817-
818822
tlsCAFile?: string;
823+
tlsCRLFile?: string;
819824
tlsCertificateKeyFile?: string;
820825

821826
/** @internal */

test/manual/tls_support.test.ts

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ import {
88
MongoServerSelectionError
99
} from '../mongodb';
1010

11-
const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE'];
11+
const REQUIRED_ENV = ['MONGODB_URI', 'TLS_KEY_FILE', 'TLS_CA_FILE', 'TLS_CRL_FILE'];
1212

1313
describe('TLS Support', function () {
1414
for (const key of REQUIRED_ENV) {
1515
if (process.env[key] == null) {
16-
throw new Error(`skipping SSL tests, ${key} environment variable is not defined`);
16+
throw new Error(`skipping TLS tests, ${key} environment variable is not defined`);
1717
}
1818
}
1919

2020
const CONNECTION_STRING = process.env.MONGODB_URI as string;
21-
const TLS_CERT_KEY_FILE = process.env.SSL_KEY_FILE as string;
22-
const TLS_CA_FILE = process.env.SSL_CA_FILE as string;
21+
const TLS_CERT_KEY_FILE = process.env.TLS_KEY_FILE as string;
22+
const TLS_CA_FILE = process.env.TLS_CA_FILE as string;
23+
const TLS_CRL_FILE = process.env.TLS_CRL_FILE as string;
2324
const tlsSettings = {
2425
tls: true,
2526
tlsCertificateKeyFile: TLS_CERT_KEY_FILE,
@@ -42,41 +43,79 @@ describe('TLS Support', function () {
4243

4344
context('when tls filepaths are provided', () => {
4445
let client: MongoClient;
46+
4547
afterEach(async () => {
46-
if (client) await client.close();
48+
await client?.close();
4749
});
4850

4951
context('when tls filepaths have length > 0', () => {
50-
beforeEach(async () => {
51-
client = new MongoClient(CONNECTION_STRING, tlsSettings);
52-
});
52+
context('when connection will succeed', () => {
53+
beforeEach(async () => {
54+
client = new MongoClient(CONNECTION_STRING, tlsSettings);
55+
});
56+
57+
it('should read in files async at connect time', async () => {
58+
expect(client.options).property('tlsCAFile', TLS_CA_FILE);
59+
expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE);
60+
expect(client.options).not.have.property('ca');
61+
expect(client.options).not.have.property('key');
62+
expect(client.options).not.have.property('cert');
63+
64+
await client.connect();
65+
66+
expect(client.options).property('ca').to.exist;
67+
expect(client.options).property('key').to.exist;
68+
expect(client.options).property('cert').to.exist;
69+
});
70+
71+
context('when client has been opened and closed more than once', function () {
72+
it('should only read files once', async () => {
73+
await client.connect();
74+
await client.close();
5375

54-
it('should read in files async at connect time', async () => {
55-
expect(client.options).property('tlsCAFile', TLS_CA_FILE);
56-
expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE);
57-
expect(client.options).not.have.property('ca');
58-
expect(client.options).not.have.property('key');
59-
expect(client.options).not.have.property('cert');
76+
const caFileAccessTime = (await fs.stat(TLS_CA_FILE)).atime;
77+
const certKeyFileAccessTime = (await fs.stat(TLS_CERT_KEY_FILE)).atime;
6078

61-
await client.connect();
79+
await client.connect();
6280

63-
expect(client.options).property('ca').to.exist;
64-
expect(client.options).property('key').to.exist;
65-
expect(client.options).property('cert').to.exist;
81+
expect((await fs.stat(TLS_CA_FILE)).atime).to.deep.equal(caFileAccessTime);
82+
expect((await fs.stat(TLS_CERT_KEY_FILE)).atime).to.deep.equal(certKeyFileAccessTime);
83+
});
84+
});
6685
});
6786

68-
context('when client has been opened and closed more than once', function () {
69-
it('should only read files once', async () => {
70-
await client.connect();
71-
await client.close();
87+
context('when the connection will fail', () => {
88+
beforeEach(async () => {
89+
client = new MongoClient(CONNECTION_STRING, {
90+
tls: true,
91+
tlsCRLFile: TLS_CRL_FILE,
92+
serverSelectionTimeoutMS: 2000,
93+
connectTimeoutMS: 2000
94+
});
95+
});
7296

73-
const caFileAccessTime = (await fs.stat(TLS_CA_FILE)).atime;
74-
const certKeyFileAccessTime = (await fs.stat(TLS_CERT_KEY_FILE)).atime;
97+
it('should read in files async at connect time', async () => {
98+
expect(client.options).property('tlsCRLFile', TLS_CRL_FILE);
99+
expect(client.options).not.have.property('crl');
75100

76-
await client.connect();
101+
const err = await client.connect().catch(e => e);
102+
103+
expect(err).to.be.instanceof(Error);
104+
expect(client.options).property('crl').to.exist;
105+
});
77106

78-
expect((await fs.stat(TLS_CA_FILE)).atime).to.deep.equal(caFileAccessTime);
79-
expect((await fs.stat(TLS_CERT_KEY_FILE)).atime).to.deep.equal(certKeyFileAccessTime);
107+
context('when client has been opened and closed more than once', function () {
108+
it('should only read files once', async () => {
109+
await client.connect().catch(e => e);
110+
await client.close();
111+
112+
const crlFileAccessTime = (await fs.stat(TLS_CRL_FILE)).atime;
113+
114+
const err = await client.connect().catch(e => e);
115+
116+
expect(err).to.be.instanceof(Error);
117+
expect((await fs.stat(TLS_CRL_FILE)).atime).to.deep.equal(crlFileAccessTime);
118+
});
80119
});
81120
});
82121
});
@@ -114,6 +153,29 @@ describe('TLS Support', function () {
114153
});
115154
});
116155

156+
context('when providing tlsCRLFile', () => {
157+
context('when the file will revoke the certificate', () => {
158+
let client: MongoClient;
159+
beforeEach(() => {
160+
client = new MongoClient(CONNECTION_STRING, {
161+
tls: true,
162+
tlsCAFile: TLS_CA_FILE,
163+
tlsCRLFile: TLS_CRL_FILE,
164+
serverSelectionTimeoutMS: 5000,
165+
connectTimeoutMS: 5000
166+
});
167+
});
168+
afterEach(async () => {
169+
await client?.close();
170+
});
171+
172+
it('throws a MongoServerSelectionError', async () => {
173+
const err = await client.connect().catch(e => e);
174+
expect(err).to.be.instanceOf(MongoServerSelectionError);
175+
});
176+
});
177+
});
178+
117179
context('when tlsCertificateKeyFile is provided, but tlsCAFile is missing', () => {
118180
let client: MongoClient;
119181
beforeEach(() => {

test/unit/connection_string.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,13 @@ describe('Connection String', function () {
438438
});
439439
});
440440

441+
context('when providing tlsCRLFile', function () {
442+
it('sets the tlsCRLFile option', function () {
443+
const options = parseOptions('mongodb://localhost/?tls=true&tlsCRLFile=path/to/crl.pem');
444+
expect(options.tlsCRLFile).to.equal('path/to/crl.pem');
445+
});
446+
});
447+
441448
context('when both tls and ssl options are provided', function () {
442449
context('when the options are provided in the URI', function () {
443450
context('when the options are equal', function () {

test/unit/mongo_client.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe('MongoOptions', function () {
3535
const options = parseOptions('mongodb://localhost:27017/?ssl=true', {
3636
tlsCertificateKeyFile: filename,
3737
tlsCAFile: filename,
38+
tlsCRLFile: filename,
3839
tlsCertificateKeyFilePassword: 'tlsCertificateKeyFilePassword'
3940
});
4041
fs.unlinkSync(filename);
@@ -61,6 +62,7 @@ describe('MongoOptions', function () {
6162
expect(options).to.not.have.property('cert');
6263
expect(options).to.have.property('tlsCertificateKeyFile', filename);
6364
expect(options).to.have.property('tlsCAFile', filename);
65+
expect(options).to.have.property('tlsCRLFile', filename);
6466
expect(options).has.property('passphrase', 'tlsCertificateKeyFilePassword');
6567
expect(options).has.property('tls', true);
6668
});

0 commit comments

Comments
 (0)