|
1 | 1 | import { EJSON, UUID } from 'bson';
|
2 | 2 | import { expect } from 'chai';
|
3 | 3 | import * as crypto from 'crypto';
|
| 4 | +import * as sinon from 'sinon'; |
4 | 5 |
|
| 6 | +// eslint-disable-next-line @typescript-eslint/no-restricted-imports |
| 7 | +import { StateMachine } from '../../../lib/client-side-encryption/state_machine'; |
5 | 8 | // eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
6 | 9 | import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption';
|
7 |
| -import { type Collection, type CommandStartedEvent, type MongoClient } from '../../mongodb'; |
8 |
| -import * as BSON from '../../mongodb'; |
9 |
| -import { getEncryptExtraOptions } from '../../tools/utils'; |
10 |
| - |
11 |
| -const metadata = { |
| 10 | +import { |
| 11 | + BSON, |
| 12 | + type Collection, |
| 13 | + type CommandStartedEvent, |
| 14 | + type MongoClient, |
| 15 | + MongoOperationTimeoutError |
| 16 | +} from '../../mongodb'; |
| 17 | +import { type FailPoint, getEncryptExtraOptions } from '../../tools/utils'; |
| 18 | + |
| 19 | +const metadata: MongoDBMetadataUI = { |
12 | 20 | requires: {
|
13 | 21 | mongodb: '>=4.2.0',
|
14 | 22 | clientSideEncryption: true
|
15 | 23 | }
|
16 | 24 | };
|
17 | 25 |
|
| 26 | +const LOCAL_KEY = Buffer.from( |
| 27 | + 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', |
| 28 | + 'base64' |
| 29 | +); |
| 30 | + |
18 | 31 | describe('Client Side Encryption Functional', function () {
|
19 | 32 | const dataDbName = 'db';
|
20 | 33 | const dataCollName = 'coll';
|
@@ -401,6 +414,154 @@ describe('Client Side Encryption Functional', function () {
|
401 | 414 | });
|
402 | 415 | }
|
403 | 416 | );
|
| 417 | + |
| 418 | + describe.only('CSOT on ClientEncryption', function () { |
| 419 | + function makeBlockingFailFor(command: string, blockTimeMS: number) { |
| 420 | + beforeEach(async function () { |
| 421 | + const utilClient = this.configuration.newClient(); |
| 422 | + await utilClient.db('admin').command({ |
| 423 | + configureFailPoint: 'failCommand', |
| 424 | + mode: { times: 2 }, |
| 425 | + data: { |
| 426 | + failCommands: [command], |
| 427 | + blockConnection: true, |
| 428 | + blockTimeMS, |
| 429 | + appName: 'clientEncryption' |
| 430 | + } |
| 431 | + } as FailPoint); |
| 432 | + await utilClient.close(); |
| 433 | + }); |
| 434 | + |
| 435 | + afterEach(async function () { |
| 436 | + sinon.restore(); |
| 437 | + const utilClient = this.configuration.newClient(); |
| 438 | + utilClient |
| 439 | + .db('admin') |
| 440 | + .command({ configureFailPoint: 'failCommand', mode: 'off' } as FailPoint); |
| 441 | + await utilClient.close(); |
| 442 | + }); |
| 443 | + } |
| 444 | + |
| 445 | + async function expectCSOTTimeout(fn: () => Promise<void>) { |
| 446 | + const start = performance.now(); |
| 447 | + const error = await fn().then( |
| 448 | + () => null, |
| 449 | + error => error |
| 450 | + ); |
| 451 | + const end = performance.now(); |
| 452 | + if (error?.name === 'MongoBulkWriteError') { |
| 453 | + expect(error) |
| 454 | + .to.have.property('errorResponse') |
| 455 | + .that.is.instanceOf(MongoOperationTimeoutError); |
| 456 | + } else { |
| 457 | + expect(error).to.be.instanceOf(MongoOperationTimeoutError); |
| 458 | + } |
| 459 | + expect(end - start).to.be.within(500, 1000); |
| 460 | + } |
| 461 | + |
| 462 | + let client; |
| 463 | + let clientEncryption: ClientEncryption; |
| 464 | + |
| 465 | + beforeEach(async function () { |
| 466 | + if (!this.configuration.clientSideEncryption.enabled) { |
| 467 | + this.skip(); |
| 468 | + } |
| 469 | + |
| 470 | + client = this.configuration.newClient({}, { appName: 'clientEncryption' }); |
| 471 | + await client.connect(); |
| 472 | + clientEncryption = new ClientEncryption(client, { |
| 473 | + kmsProviders: { local: { key: LOCAL_KEY } }, |
| 474 | + keyVaultNamespace, |
| 475 | + keyVaultClient: null, |
| 476 | + timeoutMS: 500, |
| 477 | + ...getEncryptExtraOptions() |
| 478 | + }); |
| 479 | + }); |
| 480 | + |
| 481 | + afterEach(async function () { |
| 482 | + await client.close(); |
| 483 | + }); |
| 484 | + |
| 485 | + describe('rewrapManyDataKey', function () { |
| 486 | + makeBlockingFailFor('update', 2000); |
| 487 | + |
| 488 | + beforeEach(async function () { |
| 489 | + sinon.stub(StateMachine.prototype, 'execute').callsFake(async function () { |
| 490 | + return BSON.serialize({ v: [{ _id: new UUID() }] }); |
| 491 | + }); |
| 492 | + }); |
| 493 | + |
| 494 | + afterEach(async function () { |
| 495 | + sinon.restore(); |
| 496 | + }); |
| 497 | + |
| 498 | + it('throws a timeout error if the bulk operation takes too long', async function () { |
| 499 | + await expectCSOTTimeout(async () => { |
| 500 | + await clientEncryption.rewrapManyDataKey({ _id: new UUID() }, { provider: 'local' }); |
| 501 | + }); |
| 502 | + }); |
| 503 | + }); |
| 504 | + |
| 505 | + describe('deleteKey', function () { |
| 506 | + makeBlockingFailFor('delete', 2000); |
| 507 | + |
| 508 | + it('throws a timeout error if the delete operation takes too long', async function () { |
| 509 | + await expectCSOTTimeout(async () => { |
| 510 | + await clientEncryption.deleteKey(new UUID()); |
| 511 | + }); |
| 512 | + }); |
| 513 | + }); |
| 514 | + |
| 515 | + describe('getKey', function () { |
| 516 | + makeBlockingFailFor('find', 2000); |
| 517 | + |
| 518 | + it('throws a timeout error if the bulk operation takes too long', async function () { |
| 519 | + await expectCSOTTimeout(async () => { |
| 520 | + await clientEncryption.getKey(new UUID()); |
| 521 | + }); |
| 522 | + }); |
| 523 | + }); |
| 524 | + |
| 525 | + describe('getKeys', function () { |
| 526 | + makeBlockingFailFor('find', 2000); |
| 527 | + |
| 528 | + it('throws a timeout error if the find operation takes too long', async function () { |
| 529 | + await expectCSOTTimeout(async () => { |
| 530 | + await clientEncryption.getKeys().toArray(); |
| 531 | + }); |
| 532 | + }); |
| 533 | + }); |
| 534 | + |
| 535 | + describe('removeKeyAltName', function () { |
| 536 | + makeBlockingFailFor('findAndModify', 2000); |
| 537 | + |
| 538 | + it('throws a timeout error if the findAndModify operation takes too long', async function () { |
| 539 | + await expectCSOTTimeout(async () => { |
| 540 | + await clientEncryption.removeKeyAltName(new UUID(), 'blah'); |
| 541 | + }); |
| 542 | + }); |
| 543 | + }); |
| 544 | + |
| 545 | + describe('addKeyAltName', function () { |
| 546 | + makeBlockingFailFor('findAndModify', 2000); |
| 547 | + |
| 548 | + it('throws a timeout error if the findAndModify operation takes too long', async function () { |
| 549 | + await expectCSOTTimeout(async () => { |
| 550 | + await clientEncryption.addKeyAltName(new UUID(), 'blah'); |
| 551 | + }); |
| 552 | + }); |
| 553 | + }); |
| 554 | + |
| 555 | + describe('getKeyByAltName', function () { |
| 556 | + makeBlockingFailFor('find', 2000); |
| 557 | + |
| 558 | + it('throws a timeout error if the find operation takes too long', async function () { |
| 559 | + await expectCSOTTimeout(async () => { |
| 560 | + await clientEncryption.getKeyByAltName('blah'); |
| 561 | + }); |
| 562 | + }); |
| 563 | + }); |
| 564 | + }); |
404 | 565 | });
|
405 | 566 |
|
406 | 567 | describe('Range Explicit Encryption with JS native types', function () {
|
|
0 commit comments