1============================
2Client Side Encryption Tests
3============================
4
5.. contents::
6
7----
8
9Introduction
10============
11
12This document describes the format of the driver spec tests included in the JSON
13and YAML files included in this directory.
14
15Additional prose tests, that are not represented in the spec tests, are described
16and MUST be implemented by all drivers.
17
18Spec Test Format
19================
20
21The spec tests format is an extension of `transactions spec tests <https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst>`_ with some additions:
22
23- A ``json_schema`` to set on the collection used for operations.
24
25- A ``key_vault_data`` of data that should be inserted in the key vault collection before each test.
26
27- Introduction ``autoEncryptOpts`` to `clientOptions`
28
29- Addition of `$db` to command in `command_started_event`
30
31- Addition of `$$type` to command_started_event and outcome.
32
33The semantics of `$$type` is that any actual value matching the BSON type indicated by the BSON type string is considered a match.
34
35For example, the following matches a command_started_event for an insert of a document where `random` must be of type ``binData``::
36
37 - command_started_event:
38 command:
39 insert: *collection_name
40 documents:
41 - { random: { $$type: "binData" } }
42 ordered: true
43 command_name: insert
44
45
46The values of `$$type` correspond to `these documented string representations of BSON types <https://www.mongodb.com/docs/manual/reference/bson-types/>`_.
47
48
49Each YAML file has the following keys:
50
51.. |txn| replace:: Unchanged from Transactions spec tests.
52
53- ``runOn`` |txn|
54
55- ``database_name`` |txn|
56
57- ``collection_name`` |txn|
58
59- ``data`` |txn|
60
61- ``json_schema`` A JSON Schema that should be set on the collection (using ``createCollection``) before each test run.
62
63- ``key_vault_data`` The data that should exist in the key vault collection under test before each test run.
64
65- ``tests``: An array of tests that are to be run independently of each other.
66 Each test will have some or all of the following fields:
67
68 - ``description``: |txn|
69
70 - ``skipReason``: |txn|
71
72 - ``clientOptions``: Optional, parameters to pass to MongoClient().
73
74 - ``autoEncryptOpts``: Optional
75
76 - ``kmsProviders`` A dictionary of KMS providers to set on the key vault ("aws" or "local")
77
78 - ``aws`` The AWS KMS provider. An empty object. Drivers MUST fill in AWS credentials (`accessKeyId`, `secretAccessKey`) from the environment.
79
80 - ``azure`` The Azure KMS provider credentials. An empty object. Drivers MUST fill in Azure credentials (`tenantId`, `clientId`, and `clientSecret`) from the environment.
81
82 - ``gcp`` The GCP KMS provider credentials. An empty object. Drivers MUST fill in GCP credentials (`email`, `privateKey`) from the environment.
83
84 - ``local`` The local KMS provider.
85
86 - ``key`` A 96 byte local key.
87
88 - ``schemaMap``: Optional, a map from namespaces to local JSON schemas.
89
90 - ``keyVaultNamespace``: Optional, a namespace to the key vault collection. Defaults to "keyvault.datakeys".
91
92 - ``bypassAutoEncryption``: Optional, a boolean to indicate whether or not auto encryption should be bypassed. Defaults to ``false``.
93
94 - ``operations``: Array of documents, each describing an operation to be
95 executed. Each document has the following fields:
96
97 - ``name``: |txn|
98
99 - ``object``: |txn|. Defaults to "collection" if omitted.
100
101 - ``collectionOptions``: |txn|
102
103 - ``command_name``: |txn|
104
105 - ``arguments``: |txn|
106
107 - ``result``: |txn|
108
109 - ``expectations``: |txn|
110
111 - ``outcome``: |txn|
112
113
114
115Use as integration tests
116========================
117
118Do the following before running spec tests:
119
120- Start the mongocryptd process.
121- Start a mongod process with **server version 4.1.9 or later**.
122- Place credentials to an AWS IAM user (access key ID + secret access key) somewhere in the environment outside of tracked code. (If testing on evergreen, project variables are a good place).
123
124Load each YAML (or JSON) file using a Canonical Extended JSON parser.
125
126Then for each element in ``tests``:
127
128#. If the ``skipReason`` field is present, skip this test completely.
129#. If the ``key_vault_data`` field is present:
130
131 #. Drop the ``keyvault.datakeys`` collection using writeConcern "majority".
132 #. Insert the data specified into the ``keyvault.datakeys`` with write concern "majority".
133
134#. Create a MongoClient.
135
136#. Create a collection object from the MongoClient, using the ``database_name``
137 and ``collection_name`` fields from the YAML file. Drop the collection
138 with writeConcern "majority". If a ``json_schema`` is defined in the test,
139 use the ``createCollection`` command to explicitly create the collection:
140
141 .. code:: typescript
142
143 {"create": <collection>, "validator": {"$jsonSchema": <json_schema>}}
144
145#. If the YAML file contains a ``data`` array, insert the documents in ``data``
146 into the test collection, using writeConcern "majority".
147
148#. Create a **new** MongoClient using ``clientOptions``.
149
150 #. If ``autoEncryptOpts`` includes ``aws``, ``awsTemporary``, ``awsTemporaryNoSessionToken``,
151 ``azure``, and/or ``gcp`` as a KMS provider, pass in credentials from the environment.
152
153 - ``awsTemporary``, and ``awsTemporaryNoSessionToken`` require temporary
154 AWS credentials. These can be retrieved using the csfle `set-temp-creds.sh
155 <https://github.com/mongodb-labs/drivers-evergreen-tools/tree/master/.evergreen/csfle>`_
156 script.
157
158 - ``aws``, ``awsTemporary``, and ``awsTemporaryNoSessionToken`` are
159 mutually exclusive.
160
161 ``aws`` should be substituted with:
162
163 .. code:: javascript
164
165 "aws": {
166 "accessKeyId": <set from environment>,
167 "secretAccessKey": <set from environment>
168 }
169
170 ``awsTemporary`` should be substituted with:
171
172 .. code:: javascript
173
174 "aws": {
175 "accessKeyId": <set from environment>,
176 "secretAccessKey": <set from environment>
177 "sessionToken": <set from environment>
178 }
179
180 ``awsTemporaryNoSessionToken`` should be substituted with:
181
182 .. code:: javascript
183
184 "aws": {
185 "accessKeyId": <set from environment>,
186 "secretAccessKey": <set from environment>
187 }
188
189 ``gcp`` should be substituted with:
190
191 .. code:: javascript
192
193 "gcp": {
194 "email": <set from environment>,
195 "privateKey": <set from environment>,
196 }
197
198 ``azure`` should be substituted with:
199
200 .. code:: javascript
201
202 "azure": {
203 "tenantId": <set from environment>,
204 "clientId": <set from environment>,
205 "clientSecret": <set from environment>,
206 }
207
208 ``local`` should be substituted with:
209
210 .. code:: javascript
211
212 "local": { "key": <base64 decoding of LOCAL_MASTERKEY> }
213
214 #. If ``autoEncryptOpts`` does not include ``keyVaultNamespace``, default it
215 to ``keyvault.datakeys``.
216
217#. For each element in ``operations``:
218
219 - Enter a "try" block or your programming language's closest equivalent.
220 - Create a Database object from the MongoClient, using the ``database_name``
221 field at the top level of the test file.
222 - Create a Collection object from the Database, using the
223 ``collection_name`` field at the top level of the test file.
224 If ``collectionOptions`` is present create the Collection object with the
225 provided options. Otherwise create the object with the default options.
226 - Execute the named method on the provided ``object``, passing the
227 arguments listed.
228 - If the driver throws an exception / returns an error while executing this
229 series of operations, store the error message and server error code.
230 - If the result document has an "errorContains" field, verify that the
231 method threw an exception or returned an error, and that the value of the
232 "errorContains" field matches the error string. "errorContains" is a
233 substring (case-insensitive) of the actual error message.
234
235 If the result document has an "errorCodeName" field, verify that the
236 method threw a command failed exception or returned an error, and that
237 the value of the "errorCodeName" field matches the "codeName" in the
238 server error response.
239
240 If the result document has an "errorLabelsContain" field, verify that the
241 method threw an exception or returned an error. Verify that all of the
242 error labels in "errorLabelsContain" are present in the error or exception
243 using the ``hasErrorLabel`` method.
244
245 If the result document has an "errorLabelsOmit" field, verify that the
246 method threw an exception or returned an error. Verify that none of the
247 error labels in "errorLabelsOmit" are present in the error or exception
248 using the ``hasErrorLabel`` method.
249 - If the operation returns a raw command response, eg from ``runCommand``,
250 then compare only the fields present in the expected result document.
251 Otherwise, compare the method's return value to ``result`` using the same
252 logic as the CRUD Spec Tests runner.
253
254#. If the test includes a list of command-started events in ``expectations``,
255 compare them to the actual command-started events using the
256 same logic as the Command Monitoring Spec Tests runner.
257
258#. For each element in ``outcome``:
259
260 - If ``name`` is "collection", create a new MongoClient *without encryption*
261 and verify that the test collection contains exactly the documents in the
262 ``data`` array. Ensure this find reads the latest data by using
263 **primary read preference** with **local read concern** even when the
264 MongoClient is configured with another read preference or read concern.
265
266The spec test MUST be run with *and* without auth.
267
268Prose Tests
269===========
270
271Tests for the ClientEncryption type are not included as part of the YAML tests.
272
273In the prose tests LOCAL_MASTERKEY refers to the following base64:
274
275.. code:: javascript
276
277 Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk
278
279Perform all applicable operations on key vault collections (e.g. inserting an example data key, or running a find command) with readConcern/writeConcern "majority".
280
281Data key and double encryption
282~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
283
284First, perform the setup.
285
286#. Create a MongoClient without encryption enabled (referred to as ``client``). Enable command monitoring to listen for command_started events.
287
288#. Using ``client``, drop the collections ``keyvault.datakeys`` and ``db.coll``.
289
290#. Create the following:
291
292 - A MongoClient configured with auto encryption (referred to as ``client_encrypted``)
293 - A ``ClientEncryption`` object (referred to as ``client_encryption``)
294
295 Configure both objects with the following KMS providers:
296
297 .. code:: javascript
298
299 {
300 "aws": {
301 "accessKeyId": <set from environment>,
302 "secretAccessKey": <set from environment>
303 },
304 "azure": {
305 "tenantId": <set from environment>,
306 "clientId": <set from environment>,
307 "clientSecret": <set from environment>,
308 },
309 "gcp": {
310 "email": <set from environment>,
311 "privateKey": <set from environment>,
312 }
313 "local": { "key": <base64 decoding of LOCAL_MASTERKEY> }
314 }
315
316 Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``.
317
318 Configure the ``MongoClient`` with the following ``schema_map``:
319
320 .. code:: javascript
321
322 {
323 "db.coll": {
324 "bsonType": "object",
325 "properties": {
326 "encrypted_placeholder": {
327 "encrypt": {
328 "keyId": "/placeholder",
329 "bsonType": "string",
330 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
331 }
332 }
333 }
334 }
335 }
336
337 Configure ``client_encryption`` with the ``keyVaultClient`` of the previously created ``client``.
338
339For each KMS provider (``aws``, ``azure``, ``gcp``, and ``local``), referred to as ``provider_name``, run the following test.
340
341#. Call ``client_encryption.createDataKey()``.
342
343 - Set keyAltNames to ``["<provider_name>_altname"]``.
344 - Set the masterKey document based on ``provider_name``.
345
346 For "aws":
347
348 .. code:: javascript
349
350 {
351 region: "us-east-1",
352 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0"
353 }
354
355 For "azure":
356
357 .. code:: javascript
358
359 {
360 "keyVaultEndpoint": "key-vault-csfle.vault.azure.net",
361 "keyName": "key-name-csfle"
362 }
363
364 For "gcp":
365
366 .. code:: javascript
367
368 {
369 "projectId": "devprod-drivers",
370 "location": "global",
371 "keyRing": "key-ring-csfle",
372 "keyName": "key-name-csfle"
373 }
374
375 For "local", do not set a masterKey document.
376 - Expect a BSON binary with subtype 4 to be returned, referred to as ``datakey_id``.
377 - Use ``client`` to run a ``find`` on ``keyvault.datakeys`` by querying with the ``_id`` set to the ``datakey_id``.
378 - Expect that exactly one document is returned with the "masterKey.provider" equal to ``provider_name``.
379 - Check that ``client`` captured a command_started event for the ``insert`` command containing a majority writeConcern.
380
381#. Call ``client_encryption.encrypt()`` with the value "hello <provider_name>", the algorithm ``AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic``, and the ``key_id`` of ``datakey_id``.
382
383 - Expect the return value to be a BSON binary subtype 6, referred to as ``encrypted``.
384 - Use ``client_encrypted`` to insert ``{ _id: "<provider_name>", "value": <encrypted> }`` into ``db.coll``.
385 - Use ``client_encrypted`` to run a find querying with ``_id`` of "<provider_name>" and expect ``value`` to be "hello <provider_name>".
386
387#. Call ``client_encryption.encrypt()`` with the value "hello <provider_name>", the algorithm ``AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic``, and the ``key_alt_name`` of ``<provider_name>_altname``.
388
389 - Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of ``encrypted``.
390
391#. Test explicit encrypting an auto encrypted field.
392
393 - Use ``client_encrypted`` to attempt to insert ``{ "encrypted_placeholder": <encrypted> }``
394 - Expect an exception to be thrown, since this is an attempt to auto encrypt an already encrypted value.
395
396
397
398External Key Vault Test
399~~~~~~~~~~~~~~~~~~~~~~~
400
401Run the following tests twice, parameterized by a boolean ``withExternalKeyVault``.
402
403#. Create a MongoClient without encryption enabled (referred to as ``client``).
404
405#. Using ``client``, drop the collections ``keyvault.datakeys`` and ``db.coll``.
406 Insert the document `external/external-key.json <../external/external-key.json>`_ into ``keyvault.datakeys``.
407
408#. Create the following:
409
410 - A MongoClient configured with auto encryption (referred to as ``client_encrypted``)
411 - A ``ClientEncryption`` object (referred to as ``client_encryption``)
412
413 Configure both objects with the ``local`` KMS providers as follows:
414
415 .. code:: javascript
416
417 { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
418
419 Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``.
420
421 Configure ``client_encrypted`` to use the schema `external/external-schema.json <../external/external-schema.json>`_ for ``db.coll`` by setting a schema map like: ``{ "db.coll": <contents of external-schema.json>}``
422
423 If ``withExternalKeyVault == true``, configure both objects with an external key vault client. The external client MUST connect to the same
424 MongoDB cluster that is being tested against, except it MUST use the username ``fake-user`` and password ``fake-pwd``.
425
426#. Use ``client_encrypted`` to insert the document ``{"encrypted": "test"}`` into ``db.coll``.
427 If ``withExternalKeyVault == true``, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed.
428
429#. Use ``client_encryption`` to explicitly encrypt the string ``"test"`` with key ID ``LOCALAAAAAAAAAAAAAAAAA==`` and deterministic algorithm.
430 If ``withExternalKeyVault == true``, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed.
431
432
433BSON size limits and batch splitting
434~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
435
436First, perform the setup.
437
438#. Create a MongoClient without encryption enabled (referred to as ``client``).
439
440#. Using ``client``, drop and create the collection ``db.coll`` configured with the included JSON schema `limits/limits-schema.json <../limits/limits-schema.json>`_.
441
442#. Using ``client``, drop the collection ``keyvault.datakeys``. Insert the document `limits/limits-key.json <../limits/limits-key.json>`_
443
444#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
445
446 Configure with the ``local`` KMS provider as follows:
447
448 .. code:: javascript
449
450 { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
451
452 Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``.
453
454Using ``client_encrypted`` perform the following operations:
455
456#. Insert ``{ "_id": "over_2mib_under_16mib", "unencrypted": <the string "a" repeated 2097152 times> }``.
457
458 Expect this to succeed since this is still under the ``maxBsonObjectSize`` limit.
459
460#. Insert the document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }``
461 Note: limits-doc.json is a 1005 byte BSON document that encrypts to a ~10,000 byte document.
462
463 Expect this to succeed since after encryption this still is below the normal maximum BSON document size.
464 Note, before auto encryption this document is under the 2 MiB limit. After encryption it exceeds the 2 MiB limit, but does NOT exceed the 16 MiB limit.
465
466#. Bulk insert the following:
467
468 - ``{ "_id": "over_2mib_1", "unencrypted": <the string "a" repeated (2097152) times> }``
469
470 - ``{ "_id": "over_2mib_2", "unencrypted": <the string "a" repeated (2097152) times> }``
471
472 Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using `command monitoring <https://github.com/mongodb/specifications/tree/master/source/command-monitoring/command-monitoring.rst>`_.
473
474#. Bulk insert the following:
475
476 - The document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib_1", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }``
477
478 - The document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib_2", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }``
479
480 Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using `command monitoring <https://github.com/mongodb/specifications/tree/master/source/command-monitoring/command-monitoring.rst>`_.
481
482#. Insert ``{ "_id": "under_16mib", "unencrypted": <the string "a" repeated 16777216 - 2000 times>``.
483
484 Expect this to succeed since this is still (just) under the ``maxBsonObjectSize`` limit.
485
486#. Insert the document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_16mib", "unencrypted": < the string "a" repeated (16777216 - 2000) times > }``
487
488 Expect this to fail since encryption results in a document exceeding the ``maxBsonObjectSize`` limit.
489
490Optionally, if it is possible to mock the maxWriteBatchSize (i.e. the maximum number of documents in a batch) test that setting maxWriteBatchSize=1 and inserting the two documents ``{ "_id": "a" }, { "_id": "b" }`` with ``client_encrypted`` splits the operation into two inserts.
491
492
493Views are prohibited
494~~~~~~~~~~~~~~~~~~~~
495
496#. Create a MongoClient without encryption enabled (referred to as ``client``).
497
498#. Using ``client``, drop and create a view named ``db.view`` with an empty pipeline. E.g. using the command ``{ "create": "view", "viewOn": "coll" }``.
499
500#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
501
502 Configure with the ``local`` KMS provider as follows:
503
504 .. code:: javascript
505
506 { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
507
508 Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``.
509
510#. Using ``client_encrypted``, attempt to insert a document into ``db.view``. Expect an exception to be thrown containing the message: "cannot auto encrypt a view".
511
512
513Corpus Test
514~~~~~~~~~~~
515
516The corpus test exhaustively enumerates all ways to encrypt all BSON value types. Note, the test data includes BSON binary subtype 4 (or standard UUID), which MUST be decoded and encoded as subtype 4. Run the test as follows.
517
5181. Create a MongoClient without encryption enabled (referred to as ``client``).
519
5202. Using ``client``, drop and create the collection ``db.coll`` configured with the included JSON schema `corpus/corpus-schema.json <../corpus/corpus-schema.json>`_.
521
5223. Using ``client``, drop the collection ``keyvault.datakeys``. Insert the documents `corpus/corpus-key-local.json <../corpus/corpus-key-local.json>`_, `corpus/corpus-key-aws.json <../corpus/corpus-key-aws.json>`_, `corpus/corpus-key-azure.json <../corpus/corpus-key-azure.json>`_, and `corpus/corpus-key-gcp.json <../corpus/corpus-key-gcp.json>`_.
523
5244. Create the following:
525
526 - A MongoClient configured with auto encryption (referred to as ``client_encrypted``)
527 - A ``ClientEncryption`` object (referred to as ``client_encryption``)
528
529 Configure both objects with ``aws``, ``azure``, ``gcp``, and ``local`` KMS providers as follows:
530
531 .. code:: javascript
532
533 {
534 "aws": { <AWS credentials> },
535 "azure": { <Azure credentials> },
536 "gcp": { <GCP credentials> },
537 "local": { "key": <base64 decoding of LOCAL_MASTERKEY> }
538 }
539
540 Where LOCAL_MASTERKEY is the following base64:
541
542 .. code:: javascript
543
544 Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk
545
546 Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``.
547
5485. Load `corpus/corpus.json <../corpus/corpus.json>`_ to a variable named ``corpus``. The corpus contains subdocuments with the following fields:
549
550 - ``kms`` is either ``aws``, ``azure``, ``gcp``, or ``local``
551 - ``type`` is a BSON type string `names coming from here <https://www.mongodb.com/docs/manual/reference/operator/query/type/>`_)
552 - ``algo`` is either ``rand`` or ``det`` for random or deterministic encryption
553 - ``method`` is either ``auto``, for automatic encryption or ``explicit`` for explicit encryption
554 - ``identifier`` is either ``id`` or ``altname`` for the key identifier
555 - ``allowed`` is a boolean indicating whether the encryption for the given parameters is permitted.
556 - ``value`` is the value to be tested.
557
558 Create a new BSON document, named ``corpus_copied``.
559 Iterate over each field of ``corpus``.
560
561 - If the field name is ``_id``, ``altname_aws``, ``altname_local``, ``altname_azure``, or ``altname_gcp``, copy the field to ``corpus_copied``.
562 - If ``method`` is ``auto``, copy the field to ``corpus_copied``.
563 - If ``method`` is ``explicit``, use ``client_encryption`` to explicitly encrypt the value.
564
565 - Encrypt with the algorithm described by ``algo``.
566 - If ``identifier`` is ``id``
567
568 - If ``kms`` is ``local`` set the key_id to the UUID with base64 value ``LOCALAAAAAAAAAAAAAAAAA==``.
569 - If ``kms`` is ``aws`` set the key_id to the UUID with base64 value ``AWSAAAAAAAAAAAAAAAAAAA==``.
570 - If ``kms`` is ``azure`` set the key_id to the UUID with base64 value ``AZUREAAAAAAAAAAAAAAAAA==``.
571 - If ``kms`` is ``gcp`` set the key_id to the UUID with base64 value ``GCPAAAAAAAAAAAAAAAAAAA==``.
572
573 - If ``identifier`` is ``altname``
574
575 - If ``kms`` is ``local`` set the key_alt_name to "local".
576 - If ``kms`` is ``aws`` set the key_alt_name to "aws".
577 - If ``kms`` is ``azure`` set the key_alt_name to "azure".
578 - If ``kms`` is ``gcp`` set the key_alt_name to "gcp".
579
580 If ``allowed`` is true, copy the field and encrypted value to ``corpus_copied``.
581 If ``allowed`` is false. verify that an exception is thrown. Copy the unencrypted value to to ``corpus_copied``.
582
583
5846. Using ``client_encrypted``, insert ``corpus_copied`` into ``db.coll``.
585
5867. Using ``client_encrypted``, find the inserted document from ``db.coll`` to a variable named ``corpus_decrypted``. Since it should have been automatically decrypted, assert the document exactly matches ``corpus``.
587
5888. Load `corpus/corpus_encrypted.json <../corpus/corpus-encrypted.json>`_ to a variable named ``corpus_encrypted_expected``.
589 Using ``client`` find the inserted document from ``db.coll`` to a variable named ``corpus_encrypted_actual``.
590
591 Iterate over each field of ``corpus_encrypted_expected`` and check the following:
592
593 - If the ``algo`` is ``det``, that the value equals the value of the corresponding field in ``corpus_encrypted_actual``.
594 - If the ``algo`` is ``rand`` and ``allowed`` is true, that the value does not equal the value of the corresponding field in ``corpus_encrypted_actual``.
595 - If ``allowed`` is true, decrypt the value with ``client_encryption``. Decrypt the value of the corresponding field of ``corpus_encrypted`` and validate that they are both equal.
596 - If ``allowed`` is false, validate the value exactly equals the value of the corresponding field of ``corpus`` (neither was encrypted).
597
5989. Repeat steps 1-8 with a local JSON schema. I.e. amend step 4 to configure the schema on ``client_encrypted`` with the ``schema_map`` option.
599
600Custom Endpoint Test
601~~~~~~~~~~~~~~~~~~~~
602
603Setup
604`````
605
606For each test cases, start by creating two ``ClientEncryption`` objects. Recreate the ``ClientEncryption`` objects for each test case.
607
608Create a ``ClientEncryption`` object (referred to as ``client_encryption``)
609
610Configure with ``keyVaultNamespace`` set to ``keyvault.datakeys``, and a default MongoClient as the ``keyVaultClient``.
611
612Configure with KMS providers as follows:
613
614.. code:: javascript
615
616 {
617 "aws": {
618 "accessKeyId": <set from environment>,
619 "secretAccessKey": <set from environment>
620 },
621 "azure": {
622 "tenantId": <set from environment>,
623 "clientId": <set from environment>,
624 "clientSecret": <set from environment>,
625 "identityPlatformEndpoint": "login.microsoftonline.com:443"
626 },
627 "gcp": {
628 "email": <set from environment>,
629 "privateKey": <set from environment>,
630 "endpoint": "oauth2.googleapis.com:443"
631 }
632 }
633
634Create a ``ClientEncryption`` object (referred to as ``client_encryption_invalid``)
635
636Configure with ``keyVaultNamespace`` set to ``keyvault.datakeys``, and a default MongoClient as the ``keyVaultClient``.
637
638Configure with KMS providers as follows:
639
640.. code:: javascript
641
642 {
643 "azure": {
644 "tenantId": <set from environment>,
645 "clientId": <set from environment>,
646 "clientSecret": <set from environment>,
647 "identityPlatformEndpoint": "example.com:443"
648 },
649 "gcp": {
650 "email": <set from environment>,
651 "privateKey": <set from environment>,
652 "endpoint": "example.com:443"
653 }
654 }
655
656Test cases
657``````````
658
6591. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
660
661 .. code:: javascript
662
663 {
664 region: "us-east-1",
665 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0"
666 }
667
668 Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
669
6702. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
671
672 .. code:: javascript
673
674 {
675 region: "us-east-1",
676 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
677 endpoint: "kms.us-east-1.amazonaws.com"
678 }
679
680 Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
681
6823. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
683
684 .. code:: javascript
685
686 {
687 region: "us-east-1",
688 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
689 endpoint: "kms.us-east-1.amazonaws.com:443"
690 }
691
692 Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
693
6944. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
695
696 .. code:: javascript
697
698 {
699 region: "us-east-1",
700 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
701 endpoint: "kms.us-east-1.amazonaws.com:12345"
702 }
703
704 Expect this to fail with a socket connection error.
705
7065. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
707
708 .. code:: javascript
709
710 {
711 region: "us-east-1",
712 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
713 endpoint: "kms.us-east-2.amazonaws.com"
714 }
715
716 Expect this to fail with an exception with a message containing the string: "us-east-1"
717
7186. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey:
719
720 .. code:: javascript
721
722 {
723 region: "us-east-1",
724 key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
725 endpoint: "example.com"
726 }
727
728 Expect this to fail with an exception with a message containing the string: "parse error"
729
7307. Call `client_encryption.createDataKey()` with "azure" as the provider and the following masterKey:
731
732 .. code:: javascript
733
734 {
735 "keyVaultEndpoint": "key-vault-csfle.vault.azure.net",
736 "keyName": "key-name-csfle"
737 }
738
739 Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
740
741 Call ``client_encryption_invalid.createDataKey()`` with the same masterKey. Expect this to fail with an exception with a message containing the string: "parse error".
742
7438. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey:
744
745 .. code:: javascript
746
747 {
748 "projectId": "devprod-drivers",
749 "location": "global",
750 "keyRing": "key-ring-csfle",
751 "keyName": "key-name-csfle",
752 "endpoint": "cloudkms.googleapis.com:443"
753 }
754
755 Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
756
757 Call ``client_encryption_invalid.createDataKey()`` with the same masterKey. Expect this to fail with an exception with a message containing the string: "parse error".
758
7599. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey:
760
761 .. code:: javascript
762
763 {
764 "projectId": "devprod-drivers",
765 "location": "global",
766 "keyRing": "key-ring-csfle",
767 "keyName": "key-name-csfle",
768 "endpoint": "example.com:443"
769 }
770
771 Expect this to fail with an exception with a message containing the string: "Invalid KMS response".
772
773Bypass spawning mongocryptd
774~~~~~~~~~~~~~~~~~~~~~~~~~~~
775
776Via mongocryptdBypassSpawn
777``````````````````````````
778
779The following tests that setting ``mongocryptdBypassSpawn=true`` really does bypass spawning mongocryptd.
780
781#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
782
783 Configure the required options. Use the ``local`` KMS provider as follows:
784
785 .. code:: javascript
786
787 { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
788
789 Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``.
790
791 Configure ``client_encrypted`` to use the schema `external/external-schema.json <../external/external-schema.json>`_ for ``db.coll`` by setting a schema map like: ``{ "db.coll": <contents of external-schema.json>}``
792
793 Configure the following ``extraOptions``:
794
795 .. code:: javascript
796
797 {
798 "mongocryptdBypassSpawn": true
799 "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000",
800 "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"]
801 }
802
803 Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free.
804
805#. Use ``client_encrypted`` to insert the document ``{"encrypted": "test"}`` into ``db.coll``. Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021.
806
807Via bypassAutoEncryption
808````````````````````````
809
810The following tests that setting ``bypassAutoEncryption=true`` really does bypass spawning mongocryptd.
811
812#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
813
814 Configure the required options. Use the ``local`` KMS provider as follows:
815
816 .. code:: javascript
817
818 { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
819
820 Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``.
821
822 Configure with ``bypassAutoEncryption=true``.
823
824 Configure the following ``extraOptions``:
825
826 .. code:: javascript
827
828 {
829 "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"]
830 }
831
832 Drivers MAY pass a different value to ``--port`` if they expect their testing infrastructure to be using port 27021. Pass a port that should be free.
833
834#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``. Expect this to succeed.
835
836#. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or whatever was passed via ``--port``) with serverSelectionTimeoutMS=1000. Run a handshake command and ensure it fails with a server selection timeout.
837
838Deadlock tests
839~~~~~~~~~~~~~~
840
841.. _Connection Monitoring and Pooling: /source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.rst
842
843The following tests only apply to drivers that have implemented a connection pool (see the `Connection Monitoring and Pooling`_ specification).
844
845There are multiple parameterized test cases. Before each test case, perform the setup.
846
847Setup
848`````
849
850Create a ``MongoClient`` for setup operations named ``client_test``.
851
852Create a ``MongoClient`` for key vault operations with ``maxPoolSize=1`` named ``client_keyvault``. Capture command started events.
853
854Using ``client_test``, drop the collections ``keyvault.datakeys`` and ``db.coll``.
855
856Insert the document `external/external-key.json <../external/external-key.json>`_ into ``keyvault.datakeys`` with majority write concern.
857
858Create a collection ``db.coll`` configured with a JSON schema `external/external-schema.json <../external/external-schema.json>`_ as the validator, like so:
859
860.. code:: typescript
861
862 {"create": "coll", "validator": {"$jsonSchema": <json_schema>}}
863
864Create a ``ClientEncryption`` object, named ``client_encryption`` configured with:
865- ``keyVaultClient``=``client_test``
866- ``keyVaultNamespace``="keyvault.datakeys"
867- ``kmsProviders``=``{ "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }``
868
869Use ``client_encryption`` to encrypt the value "string0" with ``algorithm``="AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" and ``keyAltName``="local". Store the result in a variable named ``ciphertext``.
870
871Proceed to run the test case.
872
873Each test case configures a ``MongoClient`` with automatic encryption (named ``client_encrypted``).
874
875Each test must assert the number of unique ``MongoClient``s created. This can be accomplished by capturing ``TopologyOpeningEvent``, or by checking command started events for a client identifier (not possible in all drivers).
876
877Running a test case
878```````````````````
879- Create a ``MongoClient`` named ``client_encrypted`` configured as follows:
880 - Set ``AutoEncryptionOpts``:
881 - ``keyVaultNamespace="keyvault.datakeys"``
882 - ``kmsProviders``=``{ "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }``
883 - Append ``TestCase.AutoEncryptionOpts`` (defined below)
884 - Capture command started events.
885 - Set ``maxPoolSize=TestCase.MaxPoolSize``
886- If the testcase sets ``AutoEncryptionOpts.bypassAutoEncryption=true``:
887 - Use ``client_test`` to insert ``{ "_id": 0, "encrypted": <ciphertext> }`` into ``db.coll``.
888- Otherwise:
889 - Use ``client_encrypted`` to insert ``{ "_id": 0, "encrypted": "string0" }``.
890- Use ``client_encrypted`` to run a ``findOne`` operation on ``db.coll``, with the filter ``{ "_id": 0 }``.
891- Expect the result to be ``{ "_id": 0, "encrypted": "string0" }``.
892- Check captured events against ``TestCase.Expectations``.
893- Check the number of unique ``MongoClient``s created is equal to ``TestCase.ExpectedNumberOfClients``.
894
895Case 1
896``````
897- MaxPoolSize: 1
898- AutoEncryptionOpts:
899 - bypassAutoEncryption=false
900 - keyVaultClient=unset
901- Expectations:
902 - Expect ``client_encrypted`` to have captured four ``CommandStartedEvent``:
903 - a listCollections to "db".
904 - a find on "keyvault".
905 - an insert on "db".
906 - a find on "db"
907- ExpectedNumberOfClients: 2
908
909Case 2
910``````
911- MaxPoolSize: 1
912- AutoEncryptionOpts:
913 - bypassAutoEncryption=false
914 - keyVaultClient=client_keyvault
915- Expectations:
916 - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``:
917 - a listCollections to "db".
918 - an insert on "db".
919 - a find on "db"
920 - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``:
921 - a find on "keyvault".
922- ExpectedNumberOfClients: 2
923
924Case 3
925``````
926- MaxPoolSize: 1
927- AutoEncryptionOpts:
928 - bypassAutoEncryption=true
929 - keyVaultClient=unset
930- Expectations:
931 - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``:
932 - a find on "db"
933 - a find on "keyvault".
934- ExpectedNumberOfClients: 2
935
936Case 4
937``````
938- MaxPoolSize: 1
939- AutoEncryptionOpts:
940 - bypassAutoEncryption=true
941 - keyVaultClient=client_keyvault
942- Expectations:
943 - Expect ``client_encrypted`` to have captured two ``CommandStartedEvent``:
944 - a find on "db"
945 - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``:
946 - a find on "keyvault".
947- ExpectedNumberOfClients: 1
948
949Case 5
950``````
951Drivers that do not support an unlimited maximum pool size MUST skip this test.
952
953- MaxPoolSize: 0
954- AutoEncryptionOpts:
955 - bypassAutoEncryption=false
956 - keyVaultClient=unset
957- Expectations:
958 - Expect ``client_encrypted`` to have captured five ``CommandStartedEvent``:
959 - a listCollections to "db".
960 - a listCollections to "keyvault".
961 - a find on "keyvault".
962 - an insert on "db".
963 - a find on "db"
964- ExpectedNumberOfClients: 1
965
966Case 6
967``````
968Drivers that do not support an unlimited maximum pool size MUST skip this test.
969
970- MaxPoolSize: 0
971- AutoEncryptionOpts:
972 - bypassAutoEncryption=false
973 - keyVaultClient=client_keyvault
974- Expectations:
975 - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``:
976 - a listCollections to "db".
977 - an insert on "db".
978 - a find on "db"
979 - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``:
980 - a find on "keyvault".
981- ExpectedNumberOfClients: 1
982
983Case 7
984``````
985Drivers that do not support an unlimited maximum pool size MUST skip this test.
986
987- MaxPoolSize: 0
988- AutoEncryptionOpts:
989 - bypassAutoEncryption=true
990 - keyVaultClient=unset
991- Expectations:
992 - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``:
993 - a find on "db"
994 - a find on "keyvault".
995- ExpectedNumberOfClients: 1
996
997Case 8
998``````
999Drivers that do not support an unlimited maximum pool size MUST skip this test.
1000
1001- MaxPoolSize: 0
1002- AutoEncryptionOpts:
1003 - bypassAutoEncryption=true
1004 - keyVaultClient=client_keyvault
1005- Expectations:
1006 - Expect ``client_encrypted`` to have captured two ``CommandStartedEvent``:
1007 - a find on "db"
1008 - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``:
1009 - a find on "keyvault".
1010- ExpectedNumberOfClients: 1
1011
1012KMS TLS Tests
1013~~~~~~~~~~~~~
1014
1015.. _ca.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem
1016.. _expired.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/expired.pem
1017.. _wrong-host.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/wrong-host.pem
1018
1019The following tests that connections to KMS servers with TLS verify peer certificates.
1020
1021The two tests below make use of mock KMS servers which can be run on Evergreen using `the mock KMS server script <https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_http_server.py>`_.
1022Drivers can set up their local Python environment for the mock KMS server by running `the virtualenv activation script <https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/activate_venv.sh>`_.
1023
1024To start a mock KMS server on port 8000 with `ca.pem`_ as a CA file and `expired.pem`_ as a cert file, run the following commands from the ``.evergreen/csfle`` directory.
1025
1026.. code::
1027
1028 . ./activate_venv.sh
1029 python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000
1030
1031Setup
1032`````
1033
1034For both tests, do the following:
1035
1036#. Start a ``mongod`` process with **server version 4.1.9 or later**.
1037
1038#. Create a ``MongoClient`` (referred to as ``client_encrypted``) for key vault operations with ``keyVaultNamespace`` set to ``keyvault.datakeys``:
1039
1040Invalid KMS Certificate
1041```````````````````````
1042
1043#. Start a mock KMS server on port 8000 with `ca.pem`_ as a CA file and `expired.pem`_ as a cert file.
1044
1045#. Call ``client_encrypted.createDataKey()`` with "aws" as the provider and the following masterKey:
1046
1047 .. code:: javascript
1048
1049 {
1050 "region": "us-east-1",
1051 "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
1052 "endpoint": "127.0.0.1:8000",
1053 }
1054
1055 Expect this to fail with an exception with a message referencing an expired certificate. This message will be language dependent.
1056 In Python, this message is "certificate verify failed: certificate has expired". In Go, this message is
1057 "certificate has expired or is not yet valid".
1058
1059Invalid Hostname in KMS Certificate
1060```````````````````````````````````
1061
1062#. Start a mock KMS server on port 8001 with `ca.pem`_ as a CA file and `wrong-host.pem`_ as a cert file.
1063
1064#. Call ``client_encrypted.createDataKey()`` with "aws" as the provider and the following masterKey:
1065
1066 .. code:: javascript
1067
1068 {
1069 "region": "us-east-1",
1070 "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
1071 "endpoint": "127.0.0.1:8001",
1072 }
1073
1074 Expect this to fail with an exception with a message referencing an incorrect or unexpected host. This message will be language dependent.
1075 In Python, this message is "certificate verify failed: IP address mismatch, certificate is not valid for '127.0.0.1'". In Go, this message
1076 is "cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs".
View as plain text