-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(NODE-4179): allow secureContext in KMS TLS options #4578
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -108,10 +108,12 @@ declare module 'mongodb-client-encryption' { | |
* - tlsInsecure | ||
* | ||
* These options are not included in the type, and are ignored if provided. | ||
* | ||
* Note that if a secureContext option is provided, all other TLS options will be ignored. | ||
*/ | ||
export type ClientEncryptionTlsOptions = Pick< | ||
MongoClientOptions, | ||
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | ||
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a blocker for us, but is there a reason not to accept other options that the driver passes through for regular connections (i.e. what's listed in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The scope of the ticket was for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @addaleax does that work for you? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, although I'll say that the intention when the ticket was first created was absolutely not just scoped to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @addaleax fully agree - in retrospect we (the node team) should have been more diligent about stating our implicit assumptions in the ticket requirements; it's also a good reminder for us to always follow up with our stakeholders after refinement, whenever possible, to make sure we are all aligned; sorry about this, we'll try to do better next time :) @durran do you want to go ahead and file that follow up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
>; | ||
|
||
/** @public */ | ||
|
@@ -526,15 +528,20 @@ export class StateMachine { | |
tlsOptions: ClientEncryptionTlsOptions, | ||
options: tls.ConnectionOptions | ||
): Promise<void> { | ||
if (tlsOptions.tlsCertificateKeyFile) { | ||
const cert = await fs.readFile(tlsOptions.tlsCertificateKeyFile); | ||
options.cert = options.key = cert; | ||
} | ||
if (tlsOptions.tlsCAFile) { | ||
options.ca = await fs.readFile(tlsOptions.tlsCAFile); | ||
} | ||
if (tlsOptions.tlsCertificateKeyFilePassword) { | ||
options.passphrase = tlsOptions.tlsCertificateKeyFilePassword; | ||
// If a secureContext is provided, it takes precedence over the other options. | ||
if (tlsOptions.secureContext) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we be making an analogous change in the driver in, e.g., V7? we don't currently do any overrides for it in the main client logic (see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that would make sense to me here for sure. I'd also consider the possibility of taking either an existing SecureContext object or the options object needed to create one via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @durran Were you able to verify the behavior for what happens when we pass both secureContext and the standalone options to Node in these two cases: (1) secureContext has valid auth, standalone options have invalid auth and (2) secureContext has invalid auth, standalone options have valid auth? If in both cases the secureContext is used and the other options are ignored, we should be able to make the same change to the driver option handling right now for consistency. Otherwise please file the V7 ticket to do so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll file a ticket for V7. In my testing when providing both a secure context and other TLS options, the connection just hangs and then times out (https://evergreen.mongodb.com/test_log/mongo_node_driver_next_rhel80_large_node_latest_test_tls_support_latest_patch_bff57ed87b236e7840a700989f3287f1be856fa9_68701e519bc80f00076e5617_25_07_10_20_11_06/0?test_name=b8c798e2ba619290e1fe4c562783bdc7#L0) so it doesn't seem like there's any real validation. |
||
options.secureContext = tlsOptions.secureContext; | ||
} else { | ||
if (tlsOptions.tlsCertificateKeyFile) { | ||
const cert = await fs.readFile(tlsOptions.tlsCertificateKeyFile); | ||
options.cert = options.key = cert; | ||
} | ||
if (tlsOptions.tlsCAFile) { | ||
options.ca = await fs.readFile(tlsOptions.tlsCAFile); | ||
} | ||
if (tlsOptions.tlsCertificateKeyFilePassword) { | ||
options.passphrase = tlsOptions.tlsCertificateKeyFilePassword; | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,24 @@ | ||
'use strict'; | ||
const BSON = require('bson'); | ||
const { expect } = require('chai'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const { dropCollection, APMEventCollector } = require('../shared'); | ||
|
||
const { EJSON } = BSON; | ||
const { LEGACY_HELLO_COMMAND, MongoCryptError, MongoRuntimeError } = require('../../mongodb'); | ||
const { MongoServerError, MongoServerSelectionError, MongoClient } = require('../../mongodb'); | ||
const { getEncryptExtraOptions } = require('../../tools/utils'); | ||
|
||
const { | ||
externalSchema | ||
} = require('../../spec/client-side-encryption/external/external-schema.json'); | ||
/* eslint-disable no-restricted-modules */ | ||
const { ClientEncryption } = require('../../../src/client-side-encryption/client_encryption'); | ||
const { getCSFLEKMSProviders } = require('../../csfle-kms-providers'); | ||
const { AlpineTestConfiguration } = require('../../tools/runner/config'); | ||
|
||
const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) => { | ||
import { BSON, EJSON } from 'bson'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note there is no related change to this feature in this test. I needed to convert it to TS because it started breaking everything at module load with:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind the conversion, but what caused that? It would be good to at least understand where this is coming from and why now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All I could confirm is that my changes in driver.test.ts somehow caused this to happen, even though it doesn't reference the prose test at all. It happens on all Node versions. I didn't want to spend too much time getting down to the exact reason and just figured converting it now would be faster. |
||
import { expect } from 'chai'; | ||
import * as fs from 'fs/promises'; | ||
import * as path from 'path'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports | ||
import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; | ||
import { getCSFLEKMSProviders } from '../../csfle-kms-providers'; | ||
import { | ||
LEGACY_HELLO_COMMAND, | ||
MongoClient, | ||
MongoCryptError, | ||
MongoRuntimeError, | ||
MongoServerError, | ||
MongoServerSelectionError | ||
} from '../../mongodb'; | ||
import { AlpineTestConfiguration } from '../../tools/runner/config'; | ||
import { getEncryptExtraOptions } from '../../tools/utils'; | ||
import { APMEventCollector, dropCollection } from '../shared'; | ||
|
||
export const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) => { | ||
const result = getCSFLEKMSProviders(); | ||
if (localKey) { | ||
result.local = { key: localKey }; | ||
|
@@ -39,6 +38,7 @@ const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) => | |
return result; | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
const noop = () => {}; | ||
const metadata = { | ||
requires: { | ||
|
@@ -55,6 +55,24 @@ const eeMetadata = { | |
} | ||
}; | ||
|
||
async function loadExternal(file) { | ||
return EJSON.parse( | ||
await fs.readFile( | ||
path.resolve(__dirname, '../../spec/client-side-encryption/external', file), | ||
'utf8' | ||
) | ||
); | ||
} | ||
|
||
async function loadLimits(file) { | ||
return EJSON.parse( | ||
await fs.readFile( | ||
path.resolve(__dirname, '../../spec/client-side-encryption/limits', file), | ||
'utf8' | ||
) | ||
); | ||
} | ||
|
||
// Tests for the ClientEncryption type are not included as part of the YAML tests. | ||
|
||
// In the prose tests LOCAL_MASTERKEY refers to the following base64: | ||
|
@@ -63,6 +81,9 @@ const eeMetadata = { | |
|
||
// Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk | ||
describe('Client Side Encryption Prose Tests', metadata, function () { | ||
let externalKey; | ||
let externalSchema; | ||
|
||
const dataDbName = 'db'; | ||
const dataCollName = 'coll'; | ||
const dataNamespace = `${dataDbName}.${dataCollName}`; | ||
|
@@ -75,6 +96,11 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
'base64' | ||
); | ||
|
||
before(async function () { | ||
externalKey = await loadExternal('external-key.json'); | ||
externalSchema = await loadExternal('external-schema.json'); | ||
}); | ||
|
||
describe('Data key and double encryption', function () { | ||
// Data key and double encryption | ||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
@@ -350,18 +376,8 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
// and confirming that the externalClient is firing off keyVault requests during | ||
// encrypted operations | ||
describe('External Key Vault Test', function () { | ||
function loadExternal(file) { | ||
return EJSON.parse( | ||
fs.readFileSync(path.resolve(__dirname, '../../spec/client-side-encryption/external', file)) | ||
); | ||
} | ||
|
||
const externalKey = loadExternal('external-key.json'); | ||
const externalSchema = loadExternal('external-schema.json'); | ||
|
||
beforeEach(function () { | ||
beforeEach(async function () { | ||
this.client = this.configuration.newClient(); | ||
|
||
// 1. Create a MongoClient without encryption enabled (referred to as ``client``). | ||
return ( | ||
this.client | ||
|
@@ -551,15 +567,15 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
}); | ||
|
||
describe('BSON size limits and batch splitting', function () { | ||
function loadLimits(file) { | ||
return EJSON.parse( | ||
fs.readFileSync(path.resolve(__dirname, '../../spec/client-side-encryption/limits', file)) | ||
); | ||
} | ||
|
||
const limitsSchema = loadLimits('limits-schema.json'); | ||
const limitsKey = loadLimits('limits-key.json'); | ||
const limitsDoc = loadLimits('limits-doc.json'); | ||
let limitsSchema; | ||
let limitsKey; | ||
let limitsDoc; | ||
|
||
before(async function () { | ||
limitsSchema = await loadLimits('limits-schema.json'); | ||
limitsKey = await loadLimits('limits-key.json'); | ||
limitsDoc = await loadLimits('limits-doc.json'); | ||
}); | ||
|
||
let hasRunFirstTimeSetup = false; | ||
|
||
|
@@ -826,9 +842,9 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
|
||
describe('Corpus Test', function () { | ||
it('runs in a separate suite', () => { | ||
expect(() => | ||
fs.statSync(path.resolve(__dirname, './client_side_encryption.prose.06.corpus.test.ts')) | ||
).not.to.throw(); | ||
expect(async () => { | ||
await fs.stat(path.resolve(__dirname, './client_side_encryption.prose.06.corpus.test.ts')); | ||
}).not.to.throw(); | ||
}); | ||
}); | ||
|
||
|
@@ -1691,6 +1707,7 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
context( | ||
'Case 5: `tlsDisableOCSPEndpointCheck` is permitted', | ||
metadata, | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
function () {} | ||
).skipReason = 'TODO(NODE-4840): Node does not support any OCSP options'; | ||
|
||
|
@@ -1911,12 +1928,12 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
beforeEach(async function () { | ||
// Load the file encryptedFields.json as encryptedFields. | ||
encryptedFields = EJSON.parse( | ||
await fs.promises.readFile(path.join(data, 'encryptedFields.json')), | ||
await fs.readFile(path.join(data, 'encryptedFields.json'), 'utf8'), | ||
{ relaxed: false } | ||
); | ||
// Load the file key1-document.json as key1Document. | ||
key1Document = EJSON.parse( | ||
await fs.promises.readFile(path.join(data, 'keys', 'key1-document.json')), | ||
await fs.readFile(path.join(data, 'keys', 'key1-document.json'), 'utf8'), | ||
{ relaxed: false } | ||
); | ||
// Read the "_id" field of key1Document as key1ID. | ||
|
@@ -2312,15 +2329,13 @@ describe('Client Side Encryption Prose Tests', metadata, function () { | |
kmip: {}, | ||
local: undefined | ||
}; | ||
/** @type {import('../../mongodb').MongoClient} */ | ||
let client1; | ||
/** @type {import('../../mongodb').MongoClient} */ | ||
let client2; | ||
|
||
describe('Case 1: Rewrap with separate ClientEncryption', function () { | ||
/** | ||
* Run the following test case for each pair of KMS providers (referred to as ``srcProvider`` and ``dstProvider``). | ||
* Include pairs where ``srcProvider`` equals ``dstProvider``. | ||
* Run the following test case for each pair of KMS providers (referred to as `srcProvider` and `dstProvider`). | ||
* Include pairs where `srcProvider` equals `dstProvider`. | ||
*/ | ||
function* generateTestCombinations() { | ||
const providers = Object.keys(masterKeys); | ||
|
Uh oh!
There was an error while loading. Please reload this page.