-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(NODE-3924)!: read tls files async #3776
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
Changes from 17 commits
9881228
b7763ae
0fbb2f9
28a2ba5
8bc7296
ddb1e7f
32de88b
d69dd69
b418f99
e571b80
84a3a89
8acfbb6
52bf930
694b4a7
0dfac88
135aadf
428aa6f
1053452
1e6d240
20d09ea
702f6f8
7438df7
b1d4e51
253aa6d
c454e37
5352581
a67d3a1
af14ffe
1550a94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import { promises as fs } from 'fs'; | ||
| import type { TcpNetConnectOpts } from 'net'; | ||
| import type { ConnectionOptions as TLSConnectionOptions, TLSSocketOptions } from 'tls'; | ||
| import { promisify } from 'util'; | ||
|
|
@@ -433,6 +434,18 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> { | |
|
|
||
| const options = this[kOptions]; | ||
|
|
||
| if (options.tls) { | ||
| if (!options.ca && typeof options.tlsCAFile === 'string' && options.tlsCAFile.length > 0) { | ||
| options.ca = await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); | ||
| } | ||
|
||
| if ( | ||
| !options.key && | ||
| typeof options.tlsCertificateKeyFile === 'string' && | ||
| options.tlsCertificateKeyFile.length > 0 | ||
| ) { | ||
| options.key = await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); | ||
| } | ||
| } | ||
| if (typeof options.srvHost === 'string') { | ||
| const hosts = await resolveSRVRecord(options); | ||
|
|
||
|
|
@@ -783,9 +796,20 @@ export interface MongoOptions | |
| * If `tlsInsecure` is set to `false`, then it will set the node native options `checkServerIdentity` | ||
| * to a no-op and `rejectUnauthorized` to the inverse value of `tlsAllowInvalidCertificates`. If | ||
| * `tlsAllowInvalidCertificates` is not set, then `rejectUnauthorized` will be set to `true`. | ||
| * | ||
| * ### Note on `tlsCAFile` and `tlsCertificateKeyFile` | ||
| * | ||
| * The files specified by the paths passed in to the `tlsCAFile` and `tlsCertificateKeyFile` fields | ||
| * are read lazily on the first call to `MongoClient.connect`. Once these files have been read and | ||
| * the `ca` and `key` fields are populated, they will not be read again on subsequent calls to | ||
| * `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca` | ||
| * and `key` fields will be undefined. | ||
| */ | ||
| tls: boolean; | ||
|
|
||
| tlsCAFile?: string; | ||
| tlsCertificateKeyFile?: string; | ||
|
|
||
| /** @internal */ | ||
| [featureFlag: symbol]: any; | ||
|
|
||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { expect } from 'chai'; | ||
| import { promises as fs } from 'fs'; | ||
|
|
||
| import { LEGACY_HELLO_COMMAND, MongoClient, type MongoClientOptions } from '../mongodb'; | ||
|
|
||
| const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE']; | ||
|
|
||
| describe('TLS Support', function () { | ||
| for (const key of REQUIRED_ENV) { | ||
| if (process.env[key] == null) { | ||
| throw new Error(`skipping SSL tests, ${key} environment variable is not defined`); | ||
| } | ||
| } | ||
|
|
||
| const CONNECTION_STRING = process.env.MONGODB_URI as string; | ||
| const TLS_CERT_KEY_FILE = process.env.SSL_KEY_FILE as string; | ||
| const TLS_CA_FILE = process.env.SSL_CA_FILE as string; | ||
| const tlsSettings = { | ||
| tls: true, | ||
| tlsCertificateKeyFile: TLS_CERT_KEY_FILE, | ||
| tlsCAFile: TLS_CA_FILE | ||
| }; | ||
|
|
||
| it( | ||
| 'should connect with tls via client options', | ||
| makeConnectionTest(CONNECTION_STRING, tlsSettings) | ||
| ); | ||
|
|
||
| it( | ||
| 'should connect with tls via url options', | ||
| makeConnectionTest( | ||
| `${CONNECTION_STRING}?${Object.keys(tlsSettings) | ||
| .map(key => `${key}=${tlsSettings[key]}`) | ||
| .join('&')}` | ||
| ) | ||
| ); | ||
|
|
||
| context('when tls filepaths are provided', () => { | ||
| let client: MongoClient; | ||
| context('when tls filepaths have length > 0', () => { | ||
| beforeEach(async () => { | ||
| client = new MongoClient(CONNECTION_STRING, tlsSettings); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| if (client) await client.close(); | ||
| }); | ||
|
|
||
| it('should read in files async at connect time', async () => { | ||
| expect(client.options).property('tlsCAFile', TLS_CA_FILE); | ||
| expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE); | ||
| expect(client.options).not.have.property('ca'); | ||
| expect(client.options).not.have.property('key'); | ||
|
|
||
| await client.connect(); | ||
|
|
||
| expect(client.options).property('ca').to.exist; | ||
| expect(client.options).property('key').to.exist; | ||
| }); | ||
|
|
||
| context('when client has been opened and closed more than once', function () { | ||
| it('should only read files once', async () => { | ||
| await client.connect(); | ||
| await client.close(); | ||
|
|
||
| const caFileAccessTime = (await fs.stat(TLS_CA_FILE)).atime; | ||
| const certKeyFileAccessTime = (await fs.stat(TLS_CERT_KEY_FILE)).atime; | ||
|
|
||
| await client.connect(); | ||
|
|
||
| expect((await fs.stat(TLS_CA_FILE)).atime).to.deep.equal(caFileAccessTime); | ||
| expect((await fs.stat(TLS_CERT_KEY_FILE)).atime).to.deep.equal(certKeyFileAccessTime); | ||
| }); | ||
| }); | ||
| }); | ||
| context('when tls filepaths have length == 0', () => { | ||
| beforeEach(async () => { | ||
| client = new MongoClient(CONNECTION_STRING, { | ||
| serverSelectionTimeoutMS: 2000, | ||
| tls: true, | ||
| tlsCAFile: '', | ||
| tlsCertificateKeyFile: '' | ||
| }); | ||
| }); | ||
|
|
||
| it('ignores file paths and fails to connect', async () => { | ||
| const err = await client.connect().catch(e => e); | ||
| expect(err).to.be.instanceOf(Error); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| function makeConnectionTest(connectionString: string, clientOptions?: MongoClientOptions) { | ||
| return async function () { | ||
| const client = new MongoClient(connectionString, clientOptions); | ||
|
|
||
| await client.connect(); | ||
| await client.db('admin').command({ [LEGACY_HELLO_COMMAND]: 1 }); | ||
| await client.db('test').collection('test').findOne({}); | ||
| return await client.close(); | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,23 +44,22 @@ describe('MongoOptions', function () { | |
| * | ||
| * ### Additional options: | ||
| * | ||
| * | nodejs native option | driver spec compliant option name | driver option type | | ||
| * |:----------------------|:----------------------------------------------|:-------------------| | ||
| * | `ca` | `tlsCAFile` | `string` | | ||
| * | `crl` | N/A | `string` | | ||
| * | `key` | `tlsCertificateKeyFile` | `string` | | ||
| * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | | ||
| * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | | ||
| * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | | ||
| * | see note below | `tlsInsecure` | `boolean` | | ||
| * | nodejs native option | driver spec compliant option name | driver option type | | ||
| * |:------------------------|:----------------------------------------------|:-------------------| | ||
| * | `tlsCAFile` | `tlsCAFile` | `string` | | ||
| * | `crl` | N/A | `string` | | ||
| * | `tlsCertificateKeyFile` | `tlsCertificateKeyFile` | `string` | | ||
|
||
| * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | | ||
| * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | | ||
| * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | | ||
| * | see note below | `tlsInsecure` | `boolean` | | ||
| * | ||
| */ | ||
| expect(options).to.not.have.property('tlsCertificateKeyFile'); | ||
| expect(options).to.not.have.property('tlsCAFile'); | ||
| expect(options).to.not.have.property('tlsCertificateKeyFilePassword'); | ||
| expect(options).has.property('ca', ''); | ||
| expect(options).has.property('key'); | ||
| expect(options.key).has.length(0); | ||
| expect(options).to.not.have.property('key'); | ||
| expect(options).to.not.have.property('ca'); | ||
| expect(options).to.have.property('tlsCertificateKeyFile', filename); | ||
| expect(options).to.have.property('tlsCAFile', filename); | ||
| expect(options).has.property('passphrase', 'tlsCertificateKeyFilePassword'); | ||
| expect(options).has.property('tls', true); | ||
| }); | ||
|
|
@@ -394,10 +393,10 @@ describe('MongoOptions', function () { | |
| const optsFromObject = parseOptions('mongodb://localhost/', { | ||
| tlsCertificateKeyFile: 'testCertKey.pem' | ||
| }); | ||
| expect(optsFromObject).to.have.property('key', 'cert key'); | ||
| expect(optsFromObject).to.have.property('tlsCertificateKeyFile', 'testCertKey.pem'); | ||
|
|
||
| const optsFromUri = parseOptions('mongodb://localhost?tlsCertificateKeyFile=testCertKey.pem'); | ||
| expect(optsFromUri).to.have.property('key', 'cert key'); | ||
| expect(optsFromUri).to.have.property('tlsCertificateKeyFile', 'testCertKey.pem'); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.