Skip to content

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

Merged
merged 6 commits into from
Jul 23, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/client-side-encryption/state_machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ declare module 'mongodb-client-encryption' {
*/
export type ClientEncryptionTlsOptions = Pick<
MongoClientOptions,
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword'
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
>;

/** @public */
Expand Down Expand Up @@ -521,6 +521,10 @@ export class StateMachine {
tlsOptions: ClientEncryptionTlsOptions,
options: tls.ConnectionOptions
): Promise<void> {
// If a secureContext is provided, ensure it is set.
if (tlsOptions.secureContext) {
options.secureContext = tlsOptions.secureContext;
}
if (tlsOptions.tlsCertificateKeyFile) {
const cert = await fs.readFile(tlsOptions.tlsCertificateKeyFile);
options.cert = options.key = cert;
Expand Down
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';
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 };
Expand All @@ -39,23 +38,41 @@ const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) =>
return result;
};

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
/** @type { MongoDBMetadataUI } */
const metadata = {
const metadata: MongoDBMetadataUI = {
requires: {
clientSideEncryption: true,
topology: '!load-balanced'
}
};

const eeMetadata = {
const eeMetadata: MongoDBMetadataUI = {
requires: {
clientSideEncryption: true,
mongodb: '>=7.0.0',
topology: ['replicaset', 'sharded']
}
};

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:
Expand All @@ -64,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}`;
Expand All @@ -76,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
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -351,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
Expand Down Expand Up @@ -552,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;

Expand Down Expand Up @@ -827,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();
});
});

Expand Down Expand Up @@ -1687,6 +1702,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';

Expand Down Expand Up @@ -1907,12 +1923,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.
Expand Down Expand Up @@ -2308,15 +2324,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);
Expand Down
Loading