Skip to content

Commit 966a4e4

Browse files
test(NODE-4663): add csfle prose test 20 (#4585)
1 parent 1115319 commit 966a4e4

File tree

9 files changed

+261
-150
lines changed

9 files changed

+261
-150
lines changed

global.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare global {
2020
idmsMockServer?: true;
2121
nodejs?: string;
2222
predicate?: (test?: Mocha.Test) => true | string;
23+
crypt_shared?: 'enabled' | 'disabled'
2324
};
2425

2526
sessions?: {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { expect } from 'chai';
2+
import { once } from 'events';
3+
import { createServer, type Server } from 'net';
4+
5+
import { getCSFLEKMSProviders } from '../../csfle-kms-providers';
6+
import { type MongoClient } from '../../mongodb';
7+
import { getEncryptExtraOptions } from '../../tools/utils';
8+
9+
describe('20. Bypass creating mongocryptd client when shared library is loaded', function () {
10+
let server: Server;
11+
let hasConnection = false;
12+
let client: MongoClient;
13+
14+
beforeEach(function () {
15+
// Start a new thread (referred to as listenerThread)
16+
// On listenerThread, create a TcpListener on 127.0.0.1 endpoint and port 27021. Start the listener and wait for establishing connections. If any connection is established, then signal about this to the main thread.
17+
// Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free.
18+
// In Node, we don't need to create a separate thread for the server.
19+
server = createServer({});
20+
server.listen(27021);
21+
server.on('connection', () => (hasConnection = true));
22+
23+
// Create a MongoClient configured with auto encryption (referred to as client_encrypted)
24+
// Configure the required options. Use the local KMS provider as follows:
25+
// { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
26+
// Configure with the keyVaultNamespace set to keyvault.datakeys.
27+
// Configure the following extraOptions:
28+
// {
29+
// "mongocryptdURI": "mongodb://localhost:27021/?serverSelectionTimeoutMS=1000"
30+
// }
31+
client = this.configuration.newClient(
32+
{},
33+
{
34+
autoEncryption: {
35+
kmsProviders: { local: getCSFLEKMSProviders().local },
36+
keyVaultNamespace: 'keyvault.datakeys',
37+
extraOptions: {
38+
cryptSharedLibPath: getEncryptExtraOptions().cryptSharedLibPath,
39+
mongocryptdURI: 'mongodb://localhost:27021'
40+
}
41+
}
42+
}
43+
);
44+
});
45+
46+
afterEach(async function () {
47+
server && (await once(server.close(), 'close'));
48+
await client?.close();
49+
});
50+
51+
it(
52+
'does not create or use a mongocryptd client when the shared library is loaded',
53+
{
54+
requires: {
55+
clientSideEncryption: true,
56+
crypt_shared: 'enabled'
57+
}
58+
},
59+
async function () {
60+
// Use client_encrypted to insert the document {"unencrypted": "test"} into db.coll.
61+
await client.db('db').collection('coll').insertOne({ unencrypted: 'test' });
62+
63+
// Expect no signal from listenerThread.
64+
expect(hasConnection).to.be.false;
65+
66+
// Note: this assertion is not in the spec test. However, unlike other drivers, Node's client
67+
// does not connect when instantiated. So, we won't receive any TCP connections to the
68+
// server if the mongocryptd client is only instantiated. This assertion captures the
69+
// spirit of this test, causing it to fail if we do instantiate a client. I left the
70+
// TCP server in, although it isn't necessary for Node's test, just because its nice to have
71+
// in case Node's client behavior ever changes.
72+
expect(client.autoEncrypter._mongocryptdClient).to.be.undefined;
73+
}
74+
);
75+
});

test/integration/client-side-encryption/client_side_encryption.prose.test.js

Lines changed: 69 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) =>
4040
};
4141

4242
const noop = () => {};
43+
/** @type { MongoDBMetadataUI } */
4344
const metadata = {
4445
requires: {
4546
clientSideEncryption: true,
@@ -1146,47 +1147,43 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
11461147
);
11471148
});
11481149

1149-
beforeEach('precondition: the shared library must NOT be loaded', function () {
1150-
const { cryptSharedLibPath } = getEncryptExtraOptions();
1151-
if (cryptSharedLibPath) {
1152-
this.currentTest.skipReason =
1153-
'test requires that the shared library NOT is present, but CRYPT_SHARED_LIB_PATH is set.';
1154-
this.skip();
1155-
}
1156-
// the presence of the shared library can only be reliably determine after
1157-
// libmongocrypt has been initialized, and can be detected with the
1158-
// cryptSharedLibVersionInfo getter on the autoEncrypter.
1159-
expect(!!clientEncrypted.autoEncrypter.cryptSharedLibVersionInfo).to.be.false;
1160-
});
1161-
11621150
afterEach(async function () {
11631151
await clientEncrypted?.close();
11641152
});
11651153

1166-
it('does not spawn mongocryptd', metadata, async function () {
1167-
// Use client_encrypted to insert the document {"encrypted": "test"} into db.coll.
1168-
// Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021.
1169-
const insertError = await clientEncrypted
1170-
.db(dataDbName)
1171-
.collection(dataCollName)
1172-
.insertOne({ encrypted: 'test' })
1173-
.catch(e => e);
1154+
it(
1155+
'does not spawn mongocryptd',
1156+
{
1157+
requires: {
1158+
...metadata.requires,
1159+
crypt_shared: 'enabled'
1160+
}
1161+
},
1162+
async function () {
1163+
// Use client_encrypted to insert the document {"encrypted": "test"} into db.coll.
1164+
// Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021.
1165+
const insertError = await clientEncrypted
1166+
.db(dataDbName)
1167+
.collection(dataCollName)
1168+
.insertOne({ encrypted: 'test' })
1169+
.catch(e => e);
11741170

1175-
expect(insertError)
1176-
.to.be.instanceOf(MongoRuntimeError)
1177-
.to.match(
1178-
/Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn/
1179-
);
1171+
expect(insertError)
1172+
.to.be.instanceOf(MongoRuntimeError)
1173+
.to.match(
1174+
/Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn/
1175+
);
11801176

1181-
const { cause } = insertError;
1177+
const { cause } = insertError;
11821178

1183-
expect(cause).to.be.instanceOf(MongoServerSelectionError);
1184-
expect(cause, 'Error must contain ECONNREFUSED').to.satisfy(
1185-
error =>
1186-
/ECONNREFUSED/.test(error.message) ||
1187-
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
1188-
);
1189-
});
1179+
expect(cause).to.be.instanceOf(MongoServerSelectionError);
1180+
expect(cause, 'Error must contain ECONNREFUSED').to.satisfy(
1181+
error =>
1182+
/ECONNREFUSED/.test(error.message) ||
1183+
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
1184+
);
1185+
}
1186+
);
11901187
});
11911188

11921189
describe('via bypassAutoEncryption', function () {
@@ -1241,19 +1238,6 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
12411238
expect(insertResult).to.have.property('insertedId');
12421239
});
12431240

1244-
beforeEach('precondition: the shared library must NOT be loaded', function () {
1245-
const { cryptSharedLibPath } = getEncryptExtraOptions();
1246-
if (cryptSharedLibPath) {
1247-
this.currentTest.skipReason =
1248-
'test requires that the shared library NOT is present, but CRYPT_SHARED_LIB_PATH is set.';
1249-
this.skip();
1250-
}
1251-
// the presence of the shared library can only be reliably determine after
1252-
// libmongocrypt has been initialized, and can be detected with the
1253-
// cryptSharedLibVersionInfo getter on the autoEncrypter.
1254-
expect(!!clientEncrypted.autoEncrypter.cryptSharedLibVersionInfo).to.be.false;
1255-
});
1256-
12571241
afterEach(async function () {
12581242
await clientEncrypted?.close();
12591243
await client?.close();
@@ -1262,34 +1246,34 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
12621246
// Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021
12631247
// (or whatever was passed via --port) with serverSelectionTimeoutMS=1000. Run a handshake
12641248
// command and ensure it fails with a server selection timeout.
1265-
it('does not spawn mongocryptd', metadata, async function () {
1266-
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
1267-
const error = await client.connect().catch(e => e);
1249+
it(
1250+
'does not spawn mongocryptd',
1251+
{
1252+
requires: {
1253+
...metadata.requires,
1254+
crypt_shared: 'enabled'
1255+
}
1256+
},
1257+
async function () {
1258+
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
1259+
const error = await client.connect().catch(e => e);
12681260

1269-
expect(error, 'Error MUST be a MongoServerSelectionError error').to.be.instanceOf(
1270-
MongoServerSelectionError
1271-
);
1272-
expect(error, 'Error MUST contain ECONNREFUSED information').to.satisfy(
1273-
error =>
1274-
/ECONNREFUSED/.test(error.message) ||
1275-
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
1276-
);
1277-
});
1261+
expect(error, 'Error MUST be a MongoServerSelectionError error').to.be.instanceOf(
1262+
MongoServerSelectionError
1263+
);
1264+
expect(error, 'Error MUST contain ECONNREFUSED information').to.satisfy(
1265+
error =>
1266+
/ECONNREFUSED/.test(error.message) ||
1267+
!!error.cause?.cause?.errors?.every(e => e.code === 'ECONNREFUSED')
1268+
);
1269+
}
1270+
);
12781271
});
12791272

12801273
describe('via loading shared library', function () {
12811274
let clientEncrypted;
12821275
let client;
12831276

1284-
beforeEach(function () {
1285-
const { cryptSharedLibPath } = getEncryptExtraOptions();
1286-
if (!cryptSharedLibPath) {
1287-
this.currentTest.skipReason =
1288-
'test requires that the shared library is present, but CRYPT_SHARED_LIB_PATH is unset.';
1289-
this.skip();
1290-
}
1291-
});
1292-
12931277
// Setup
12941278
beforeEach(async function () {
12951279
const { cryptSharedLibPath } = getEncryptExtraOptions();
@@ -1345,11 +1329,23 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
13451329
// 4. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or
13461330
// whatever was passed via `--port` with serverSelectionTimeoutMS=1000.) Run a handshake
13471331
// command and ensure it fails with a server selection timeout
1348-
it('should not spawn mongocryptd', metadata, async function () {
1349-
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
1350-
const error = await client.connect().catch(e => e);
1351-
expect(error).to.be.instanceOf(MongoServerSelectionError, /'Server selection timed out'/i);
1352-
});
1332+
it(
1333+
'should not spawn mongocryptd',
1334+
{
1335+
requires: {
1336+
...metadata.requires,
1337+
crypt_shared: 'enabled'
1338+
}
1339+
},
1340+
async function () {
1341+
client = new MongoClient('mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000');
1342+
const error = await client.connect().catch(e => e);
1343+
expect(error).to.be.instanceOf(
1344+
MongoServerSelectionError,
1345+
/'Server selection timed out'/i
1346+
);
1347+
}
1348+
);
13531349
});
13541350
});
13551351

test/integration/node-specific/auto_encrypter.test.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@ import {
1010
StateMachine,
1111
type UUID
1212
} from '../../mongodb';
13-
import { ClientSideEncryptionFilter } from '../../tools/runner/filters/client_encryption_filter';
14-
15-
export const cryptShared = (status: 'enabled' | 'disabled') => () => {
16-
const isCryptSharedLoaded = ClientSideEncryptionFilter.cryptShared != null;
17-
18-
if (status === 'enabled') {
19-
return isCryptSharedLoaded ? true : 'Test requires the shared library.';
20-
}
21-
22-
return isCryptSharedLoaded ? 'Test requires that the crypt shared library NOT be present' : true;
23-
};
2413

2514
describe('mongocryptd auto spawn', function () {
2615
let client: MongoClient;
@@ -75,7 +64,7 @@ describe('mongocryptd auto spawn', function () {
7564

7665
it(
7766
'should autoSpawn a mongocryptd on init by default',
78-
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
67+
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
7968
async function () {
8069
const autoEncrypter = client.autoEncrypter;
8170
const mongocryptdManager = autoEncrypter._mongocryptdManager;
@@ -90,7 +79,7 @@ describe('mongocryptd auto spawn', function () {
9079

9180
it(
9281
'should not attempt to kick off mongocryptd on a non-network error from mongocrpytd',
93-
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
82+
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
9483
async function () {
9584
let called = false;
9685
sinon
@@ -124,7 +113,7 @@ describe('mongocryptd auto spawn', function () {
124113

125114
it(
126115
'should respawn the mongocryptd after a MongoNetworkTimeoutError is returned when communicating with mongocryptd',
127-
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
116+
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
128117
async function () {
129118
let called = false;
130119
sinon
@@ -158,7 +147,7 @@ describe('mongocryptd auto spawn', function () {
158147

159148
it(
160149
'should propagate error if MongoNetworkTimeoutError is experienced twice in a row',
161-
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
150+
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
162151
async function () {
163152
const stub = sinon
164153
.stub(StateMachine.prototype, 'markCommand')
@@ -193,7 +182,7 @@ describe('mongocryptd auto spawn', function () {
193182

194183
it(
195184
'should return a useful message if mongocryptd fails to autospawn',
196-
{ requires: { clientSideEncryption: true, predicate: cryptShared('disabled') } },
185+
{ requires: { clientSideEncryption: true, crypt_shared: 'disabled' } },
197186
async function () {
198187
client = this.configuration.newClient(
199188
{},

test/integration/node-specific/crypt_shared_lib.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { dirname } from 'path';
44

55
import { BSON } from '../../mongodb';
66
import { getEncryptExtraOptions } from '../../tools/utils';
7-
import { cryptShared } from './auto_encrypter.test';
87

98
const { EJSON } = BSON;
109

@@ -30,7 +29,7 @@ describe('crypt shared library', () => {
3029
'should load a shared library by specifying its path',
3130
{
3231
requires: {
33-
predicate: cryptShared('enabled')
32+
crypt_shared: 'enabled'
3433
}
3534
},
3635
async function () {
@@ -57,7 +56,7 @@ describe('crypt shared library', () => {
5756
'should load a shared library by specifying a search path',
5857
{
5958
requires: {
60-
predicate: cryptShared('enabled')
59+
crypt_shared: 'enabled'
6160
}
6261
},
6362
async function () {

test/tools/runner/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export class TestConfiguration {
8888
version: string;
8989
libmongocrypt: string | null;
9090
};
91+
cryptSharedVersion: MongoClient['autoEncrypter']['cryptSharedLibVersionInfo'] | null;
9192
parameters: Record<string, any>;
9293
singleMongosLoadBalancerUri: string;
9394
multiMongosLoadBalancerUri: string;
@@ -121,6 +122,7 @@ export class TestConfiguration {
121122
const hostAddresses = hosts.map(HostAddress.fromString);
122123
this.version = context.version;
123124
this.clientSideEncryption = context.clientSideEncryption;
125+
this.cryptSharedVersion = context.cryptShared;
124126
this.parameters = { ...context.parameters };
125127
this.singleMongosLoadBalancerUri = context.singleMongosLoadBalancerUri;
126128
this.multiMongosLoadBalancerUri = context.multiMongosLoadBalancerUri;

0 commit comments

Comments
 (0)