Skip to content

Commit a45e0e1

Browse files
authored
test(NODE-5069): move and improve FLE prose 14 test (#3576)
1 parent 946a7d8 commit a45e0e1

File tree

2 files changed

+254
-224
lines changed

2 files changed

+254
-224
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import { expect } from 'chai';
2+
3+
import {
4+
Binary,
5+
BSON,
6+
CommandFailedEvent,
7+
CommandSucceededEvent,
8+
MongoClient,
9+
MongoNetworkError
10+
} from '../../mongodb';
11+
import { installNodeDNSWorkaroundHooks } from '../../tools/runner/hooks/configuration';
12+
import { getEncryptExtraOptions } from '../../tools/utils';
13+
14+
const metadata: MongoDBMetadataUI = {
15+
requires: {
16+
clientSideEncryption: true,
17+
mongodb: '>=4.2.0',
18+
topology: '!load-balanced'
19+
}
20+
};
21+
22+
const LOCAL_KEY = Buffer.from(
23+
'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk',
24+
'base64'
25+
);
26+
27+
describe('14. Decryption Events', metadata, function () {
28+
installNodeDNSWorkaroundHooks();
29+
30+
let setupClient: MongoClient;
31+
let clientEncryption;
32+
let keyId: Binary;
33+
let cipherText: Binary;
34+
let malformedCiphertext: Binary;
35+
let encryptedClient: MongoClient;
36+
let aggregateSucceeded: CommandSucceededEvent | undefined;
37+
let aggregateFailed: CommandFailedEvent | undefined;
38+
39+
beforeEach(async function () {
40+
const mongodbClientEncryption = this.configuration.mongodbClientEncryption;
41+
// Create a MongoClient named ``setupClient``.
42+
setupClient = this.configuration.newClient();
43+
// Drop and create the collection ``db.decryption_events``.
44+
const db = setupClient.db('db');
45+
await db.dropCollection('decryption_events').catch(() => null);
46+
await db.createCollection('decryption_events');
47+
// Create a ClientEncryption object named ``clientEncryption`` with these options:
48+
// ClientEncryptionOpts {
49+
// keyVaultClient: <setupClient>,
50+
// keyVaultNamespace: "keyvault.datakeys",
51+
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
52+
// }
53+
clientEncryption = new mongodbClientEncryption.ClientEncryption(setupClient, {
54+
keyVaultNamespace: 'keyvault.datakeys',
55+
kmsProviders: { local: { key: LOCAL_KEY } },
56+
bson: BSON,
57+
extraOptions: getEncryptExtraOptions()
58+
});
59+
// Create a data key with the "local" KMS provider.
60+
// Storing the result in a variable named ``keyID``.
61+
keyId = await clientEncryption.createDataKey('local');
62+
// Use ``clientEncryption`` to encrypt the string "hello" with the following ``EncryptOpts``:
63+
// EncryptOpts {
64+
// keyId: <keyID>,
65+
// algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
66+
// }
67+
// Store the result in a variable named ``ciphertext``.
68+
cipherText = await clientEncryption.encrypt('hello', {
69+
keyId: keyId,
70+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
71+
});
72+
// Copy ``ciphertext`` into a variable named ``malformedCiphertext``.
73+
// Change the last byte to 0. This will produce an invalid HMAC tag.
74+
const buffer = Buffer.from(cipherText.buffer);
75+
buffer.writeInt8(0, buffer.length - 1);
76+
malformedCiphertext = new Binary(buffer, 6);
77+
// Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``:
78+
// AutoEncryptionOpts {
79+
// keyVaultNamespace: "keyvault.datakeys";
80+
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
81+
// }
82+
// Configure ``encryptedClient`` with "retryReads=false".
83+
encryptedClient = this.configuration.newClient(
84+
{},
85+
{
86+
writeConcern: { w: 'majority' },
87+
retryReads: false,
88+
monitorCommands: true,
89+
autoEncryption: {
90+
keyVaultNamespace: 'keyvault.datakeys',
91+
kmsProviders: { local: { key: LOCAL_KEY } },
92+
extraOptions: getEncryptExtraOptions()
93+
}
94+
}
95+
);
96+
// Register a listener for CommandSucceeded events on ``encryptedClient``.
97+
encryptedClient.on('commandSucceeded', event => {
98+
if (event.commandName === 'aggregate') {
99+
aggregateSucceeded = event;
100+
}
101+
});
102+
// The listener must store the most recent CommandFailedEvent error for the "aggregate" command.
103+
encryptedClient.on('commandFailed', event => {
104+
if (event.commandName === 'aggregate') {
105+
aggregateFailed = event;
106+
}
107+
});
108+
});
109+
110+
afterEach(async function () {
111+
aggregateSucceeded = undefined;
112+
aggregateFailed = undefined;
113+
await setupClient.close();
114+
await encryptedClient.close();
115+
});
116+
117+
context('Case 1: Command Error', metadata, function () {
118+
beforeEach(async function () {
119+
// Use ``setupClient`` to configure the following failpoint:
120+
// {
121+
// "configureFailPoint": "failCommand",
122+
// "mode": {
123+
// "times": 1
124+
// },
125+
// "data": {
126+
// "errorCode": 123,
127+
// "failCommands": [
128+
// "aggregate"
129+
// ]
130+
// }
131+
// }
132+
await setupClient
133+
.db()
134+
.admin()
135+
.command({
136+
configureFailPoint: 'failCommand',
137+
mode: {
138+
times: 1
139+
},
140+
data: {
141+
errorCode: 123,
142+
failCommands: ['aggregate']
143+
}
144+
});
145+
});
146+
147+
it('expects an error and a command failed event', async function () {
148+
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
149+
// Expect an exception to be thrown from the command error. Expect a CommandFailedEvent.
150+
const collection = encryptedClient.db('db').collection('decryption_events');
151+
152+
const error = await collection
153+
.aggregate([])
154+
.toArray()
155+
.catch(error => error);
156+
157+
expect(error).to.have.property('code', 123);
158+
expect(aggregateFailed).to.have.nested.property('failure.code', 123);
159+
});
160+
});
161+
162+
context('Case 2: Network Error', metadata, function () {
163+
beforeEach(async function () {
164+
// Use ``setupClient`` to configure the following failpoint:
165+
// {
166+
// "configureFailPoint": "failCommand",
167+
// "mode": {
168+
// "times": 1
169+
// },
170+
// "data": {
171+
// "errorCode": 123,
172+
// "closeConnection": true,
173+
// "failCommands": [
174+
// "aggregate"
175+
// ]
176+
// }
177+
// }
178+
await setupClient
179+
.db()
180+
.admin()
181+
.command({
182+
configureFailPoint: 'failCommand',
183+
mode: {
184+
times: 1
185+
},
186+
data: {
187+
errorCode: 123,
188+
closeConnection: true,
189+
failCommands: ['aggregate']
190+
}
191+
});
192+
});
193+
194+
it('expects an error and a command failed event', async function () {
195+
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
196+
// Expect an exception to be thrown from the network error. Expect a CommandFailedEvent.
197+
const collection = encryptedClient.db('db').collection('decryption_events');
198+
199+
const error = await collection
200+
.aggregate([])
201+
.toArray()
202+
.catch(error => error);
203+
204+
expect(error).to.be.instanceOf(MongoNetworkError);
205+
expect(aggregateFailed).to.have.nested.property('failure.message').to.include('closed');
206+
});
207+
});
208+
209+
context('Case 3: Decrypt Error', metadata, function () {
210+
it('errors on decryption but command succeeds', async function () {
211+
// Use ``encryptedClient`` to insert the document ``{ "encrypted": <malformedCiphertext> }``
212+
// into ``db.decryption_events``.
213+
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
214+
// Expect an exception to be thrown from the decryption error.
215+
// Expect a CommandSucceededEvent. Expect the CommandSucceededEvent.reply
216+
// to contain BSON binary for the field
217+
// ``cursor.firstBatch.encrypted``.
218+
const collection = encryptedClient.db('db').collection('decryption_events');
219+
await collection.insertOne({ encrypted: malformedCiphertext });
220+
221+
const error = await collection
222+
.aggregate([])
223+
.toArray()
224+
.catch(error => error);
225+
226+
expect(error).to.have.property('message').to.include('HMAC validation failure');
227+
expect(aggregateSucceeded)
228+
.to.have.nested.property('reply.cursor.firstBatch[0].encrypted')
229+
.to.be.instanceOf(Binary);
230+
});
231+
});
232+
233+
context('Case 4: Decrypt Success', metadata, function () {
234+
it('succeeds on decryption and command succeeds', async function () {
235+
// Use ``encryptedClient`` to insert the document ``{ "encrypted": <ciphertext> }``
236+
// into ``db.decryption_events``.
237+
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
238+
// Expect no exception.
239+
// Expect a CommandSucceededEvent. Expect the CommandSucceededEvent.reply
240+
// to contain BSON binary for the field ``cursor.firstBatch.encrypted``.
241+
const collection = encryptedClient.db('db').collection('decryption_events');
242+
await collection.insertOne({ encrypted: cipherText });
243+
244+
const result = await collection.aggregate([]).toArray();
245+
246+
expect(result).to.have.nested.property('[0].encrypted', 'hello');
247+
expect(aggregateSucceeded)
248+
.to.have.nested.property('reply.cursor.firstBatch[0].encrypted')
249+
.to.be.instanceOf(Binary);
250+
});
251+
});
252+
});

0 commit comments

Comments
 (0)