Contents
This document describes the format of the driver spec tests included in the JSON and YAML files included in this directory.
Additional prose tests, that are not represented in the spec tests, are described and MUST be implemented by all drivers.
The spec tests format is an extension of transactions spec tests with some additions:
- A
json_schema
to set on the collection used for operations. - A
key_vault_data
of data that should be inserted in the key vault collection before each test. - Introduction
autoEncryptOpts
to clientOptions - Addition of $db to command in command_started_event
- Addition of $$type to command_started_event and outcome.
The semantics of $$type is that any actual value matching the BSON type indicated by the BSON type string is considered a match.
For example, the following matches a command_started_event for an insert of a document where random must be of type binData
:
- command_started_event: command: insert: *collection_name documents: - { random: { $$type: "binData" } } ordered: true command_name: insert
The values of $$type correspond to these documented string representations of BSON types.
Each YAML file has the following keys:
runOn
Unchanged from Transactions spec tests.database_name
Unchanged from Transactions spec tests.collection_name
Unchanged from Transactions spec tests.data
Unchanged from Transactions spec tests.json_schema
A JSON Schema that should be set on the collection (usingcreateCollection
) before each test run.key_vault_data
The data that should exist in the key vault collection under test before each test run.tests
: An array of tests that are to be run independently of each other. Each test will have some or all of the following fields:description
: Unchanged from Transactions spec tests.skipReason
: Unchanged from Transactions spec tests.clientOptions
: Optional, parameters to pass to MongoClient().autoEncryptOpts
: OptionalkmsProviders
A dictionary of KMS providers to set on the key vault ("aws" or "local")aws
The AWS KMS provider. An empty object. Drivers MUST fill in AWS credentials from the environment.local
The local KMS provider.key
A 96 byte local key.
schemaMap
: Optional, a map from namespaces to local JSON schemas.keyVaultNamespace
: Optional, a namespace to the key vault collection. Defaults to "admin.datakeys".bypassAutoEncryption
: Optional, a boolean to indicate whether or not auto encryption should be bypassed. Defaults tofalse
.
operations
: Array of documents, each describing an operation to be executed. Each document has the following fields:name
: Unchanged from Transactions spec tests.object
: Unchanged from Transactions spec tests.. Defaults to "collection" if omitted.collectionOptions
: Unchanged from Transactions spec tests.command_name
: Unchanged from Transactions spec tests.arguments
: Unchanged from Transactions spec tests.result
: Unchanged from Transactions spec tests.
expectations
: Unchanged from Transactions spec tests.outcome
: Unchanged from Transactions spec tests.
Do the following before running spec tests:
- Start the mongocryptd process.
- Start a mongod process with server version 4.1.9 or later.
- 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).
Load each YAML (or JSON) file using a Canonical Extended JSON parser.
Then for each element in tests
:
If the
skipReason
field is present, skip this test completely.If the
key_vault_data
field is present:- Drop the
admin.datakeys
collection using writeConcern "majority". - Insert the data specified into the
admin.datakeys
with write concern "majority".
- Drop the
Create a MongoClient.
Create a collection object from the MongoClient, using the
database_name
andcollection_name
fields from the YAML file. Drop the collection with writeConcern "majority". If ajson_schema
is defined in the test, use thecreateCollection
command to explicitly create the collection:{"create": <collection>, "validator": {"$jsonSchema": <json_schema>}}
If the YAML file contains a
data
array, insert the documents indata
into the test collection, using writeConcern "majority".Create a new MongoClient using
clientOptions
.- If
autoEncryptOpts
includesaws
as a KMS provider, pass in AWS credentials from the environment. - If
autoEncryptOpts
does not includekeyVaultNamespace
, default it toadmin.datakeys
.
- If
For each element in
operations
:Enter a "try" block or your programming language's closest equivalent.
Create a Database object from the MongoClient, using the
database_name
field at the top level of the test file.Create a Collection object from the Database, using the
collection_name
field at the top level of the test file. IfcollectionOptions
is present create the Collection object with the provided options. Otherwise create the object with the default options.Execute the named method on the provided
object
, passing the arguments listed.If the driver throws an exception / returns an error while executing this series of operations, store the error message and server error code.
If the result document has an "errorContains" field, verify that the method threw an exception or returned an error, and that the value of the "errorContains" field matches the error string. "errorContains" is a substring (case-insensitive) of the actual error message.
If the result document has an "errorCodeName" field, verify that the method threw a command failed exception or returned an error, and that the value of the "errorCodeName" field matches the "codeName" in the server error response.
If the result document has an "errorLabelsContain" field, verify that the method threw an exception or returned an error. Verify that all of the error labels in "errorLabelsContain" are present in the error or exception using the
hasErrorLabel
method.If the result document has an "errorLabelsOmit" field, verify that the method threw an exception or returned an error. Verify that none of the error labels in "errorLabelsOmit" are present in the error or exception using the
hasErrorLabel
method.If the operation returns a raw command response, eg from
runCommand
, then compare only the fields present in the expected result document. Otherwise, compare the method's return value toresult
using the same logic as the CRUD Spec Tests runner.
If the test includes a list of command-started events in
expectations
, compare them to the actual command-started events using the same logic as the Command Monitoring Spec Tests runner.For each element in
outcome
:- If
name
is "collection", create a new MongoClient without encryption and verify that the test collection contains exactly the documents in thedata
array. Ensure this find reads the latest data by using primary read preference with local read concern even when the MongoClient is configured with another read preference or read concern.
- If
The spec test MUST be run with and without auth.
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:
Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk
First, perform the setup.
Create a MongoClient without encryption enabled (referred to as
client
). Enable command monitoring to listen for command_started events.Using
client
, drop the collectionsadmin.datakeys
anddb.coll
.Create the following:
- A MongoClient configured with auto encryption (referred to as
client_encrypted
) - A
ClientEncryption
object (referred to asclient_encryption
)
Configure both objects with
aws
and thelocal
KMS providers as follows:{ "aws": { <AWS credentials> }, "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
Configure both objects with
keyVaultNamespace
set toadmin.datakeys
.Configure the
MongoClient
with the followingschema_map
:{ "db.coll": { "bsonType": "object", "properties": { "encrypted_placeholder": { "encrypt": { "keyId": "/placeholder", "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" } } } } }
Configure
client_encryption
with thekeyVaultClient
of the previously createdclient
.- A MongoClient configured with auto encryption (referred to as
Then, test creating and using data keys from a local
KMS provider:
- Call
client_encryption.createDataKey()
with thelocal
KMS provider and keyAltNames set to["local_altname"]
.- Expect a BSON binary with subtype 4 to be returned, referred to as
local_datakey_id
. - Use
client
to run afind
onadmin.datakeys
by querying with the_id
set to thelocal_datakey_id
. - Expect that exactly one document is returned with the "masterKey.provider" equal to "local".
- Check that
client
captured a command_started event for theinsert
command containing a majority writeConcern.
- Expect a BSON binary with subtype 4 to be returned, referred to as
- Call
client_encryption.encrypt()
with the value "hello local", the algorithmAEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
, and thekey_id
oflocal_datakey_id
.- Expect the return value to be a BSON binary subtype 6, referred to as
local_encrypted
. - Use
client_encrypted
to insert{ _id: "local", "value": <local_encrypted> }
intodb.coll
. - Use
client_encrypted
to run a find querying with_id
of "local" and expectvalue
to be "hello local".
- Expect the return value to be a BSON binary subtype 6, referred to as
- Call
client_encryption.encrypt()
with the value "hello local", the algorithmAEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
, and thekey_alt_name
oflocal_altname
.- Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of
local_encrypted
.
- Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of
Then, repeat the above tests with the aws
KMS provider:
Call
client_encryption.createDataKey()
with theaws
KMS provider, keyAltNames set to["aws_altname"]
, andmasterKey
as follows:{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" }
- Expect a BSON binary with subtype 4 to be returned, referred to as
aws_datakey_id
. - Use
client
to run afind
onadmin.datakeys
by querying with the_id
set to theaws_datakey_id
. - Expect that exactly one document is returned with the "masterKey.provider" equal to "aws".
- Check that
client
captured a command_started event for theinsert
command containing a majority writeConcern.
- Expect a BSON binary with subtype 4 to be returned, referred to as
Call
client_encryption.encrypt()
with the value "hello aws", the algorithmAEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
, and thekey_id
ofaws_datakey_id
.- Expect the return value to be a BSON binary subtype 6, referred to as
aws_encrypted
. - Use
client_encrypted
to insert{ _id: "aws", "value": <aws_encrypted> }
intodb.coll
. - Use
client_encrypted
to run a find querying with_id
of "aws" and expectvalue
to be "hello aws".
- Expect the return value to be a BSON binary subtype 6, referred to as
Call
client_encryption.encrypt()
with the value "hello aws", the algorithmAEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
, and thekey_alt_name
ofaws_altname
.- Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of
aws_encrypted
.
- Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of
Then, run the following final tests:
- Test explicit encrypting an auto encrypted field.
- Use
client_encrypted
to attempt to insert{ "encrypted_placeholder": (local_encrypted) }
- Expect an exception to be thrown, since this is an attempt to auto encrypt an already encrypted value.
- Use
Run the following tests twice, parameterized by a boolean withExternalKeyVault
.
Create a MongoClient without encryption enabled (referred to as
client
).Using
client
, drop the collectionsadmin.datakeys
anddb.coll
. Insert the document external/external-key.json intoadmin.datakeys
.Create the following:
- A MongoClient configured with auto encryption (referred to as
client_encrypted
) - A
ClientEncryption
object (referred to asclient_encryption
)
Configure both objects with the
local
KMS providers as follows:{ "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
Configure both objects with
keyVaultNamespace
set toadmin.datakeys
.Configure
client_encrypted
to use the schema external/external-schema.json fordb.coll
by setting a schema map like:{ "db.coll": <contents of external-schema.json>}
If
withExternalKeyVault == true
, configure both objects with an external key vault client. The external client MUST connect to the same MongoDB cluster that is being tested against, except it MUST use the usernamefake-user
and passwordfake-pwd
.- A MongoClient configured with auto encryption (referred to as
Use
client_encrypted
to insert the document{"encrypted": "test"}
intodb.coll
. IfwithExternalKeyVault == true
, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed.Use
client_encryption
to explicitly encrypt the string"test"
with key IDLOCALAAAAAAAAAAAAAAAAA==
and deterministic algorithm. IfwithExternalKeyVault == true
, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed.
First, perform the setup.
Create a MongoClient without encryption enabled (referred to as
client
).Using
client
, drop and create the collectiondb.coll
configured with the included JSON schema limits/limits-schema.json.Using
client
, drop the collectionadmin.datakeys
. Insert the document limits/limits-key.jsonCreate a MongoClient configured with auto encryption (referred to as
client_encrypted
)Configure with the
local
KMS provider as follows:{ "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
Configure with the
keyVaultNamespace
set toadmin.datakeys
.
Using client_encrypted
perform the following operations:
Insert
{ "_id": "over_2mib_under_16mib", "unencrypted": <the string "a" repeated 2097152 times> }
.Expect this to succeed since this is still under the
maxBsonObjectSize
limit.Insert the document limits/limits-doc.json concatenated with
{ "_id": "encryption_exceeds_2mib", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }
Note: limits-doc.json is a 1005 byte BSON document that encrypts to a ~10,000 byte document.Expect this to succeed since after encryption this still is below the normal maximum BSON document size. 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.
Bulk insert the following:
{ "_id": "over_2mib_1", "unencrypted": <the string "a" repeated (2097152) times> }
{ "_id": "over_2mib_2", "unencrypted": <the string "a" repeated (2097152) times> }
Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using command monitoring.
Bulk insert the following:
- The document limits/limits-doc.json concatenated with
{ "_id": "encryption_exceeds_2mib_1", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }
- The document limits/limits-doc.json concatenated with
{ "_id": "encryption_exceeds_2mib_2", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }
Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using command monitoring.
- The document limits/limits-doc.json concatenated with
Insert
{ "_id": "under_16mib", "unencrypted": <the string "a" repeated 16777216 - 2000 times>
.Expect this to succeed since this is still (just) under the
maxBsonObjectSize
limit.Insert the document limits/limits-doc.json concatenated with
{ "_id": "encryption_exceeds_16mib", "unencrypted": < the string "a" repeated (16777216 - 2000) times > }
Expect this to fail since encryption results in a document exceeding the
maxBsonObjectSize
limit.
Optionally, 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.
Create a MongoClient without encryption enabled (referred to as
client
).Using
client
, drop and create a view nameddb.view
with an empty pipeline. E.g. using the command{ "create": "view", "viewOn": "coll" }
.Create a MongoClient configured with auto encryption (referred to as
client_encrypted
)Configure with the
local
KMS provider as follows:{ "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
Configure with the
keyVaultNamespace
set toadmin.datakeys
.Using
client_encrypted
, attempt to insert a document intodb.view
. Expect an exception to be thrown containing the message: "cannot auto encrypt a view".
The 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.
Create a MongoClient without encryption enabled (referred to as
client
).Using
client
, drop and create the collectiondb.coll
configured with the included JSON schema corpus/corpus-schema.json.Using
client
, drop the collectionadmin.datakeys
. Insert the documents corpus/corpus-key-local.json and corpus/corpus-key-aws.json.Create the following:
- A MongoClient configured with auto encryption (referred to as
client_encrypted
) - A
ClientEncryption
object (referred to asclient_encryption
)
Configure both objects with
aws
and thelocal
KMS providers as follows:{ "aws": { <AWS credentials> }, "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
Where LOCAL_MASTERKEY is the following base64:
Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk
Configure both objects with
keyVaultNamespace
set toadmin.datakeys
.- A MongoClient configured with auto encryption (referred to as
Load corpus/corpus.json to a variable named
corpus
. The corpus contains subdocuments with the following fields:kms
is eitheraws
orlocal
type
is a BSON type string names coming from here)algo
is eitherrand
ordet
for random or deterministic encryptionmethod
is eitherauto
, for automatic encryption orexplicit
for explicit encryptionidentifier
is eitherid
oraltname
for the key identifierallowed
is a boolean indicating whether the encryption for the given parameters is permitted.value
is the value to be tested.
Create a new BSON document, named
corpus_copied
. Iterate over each field ofcorpus
.If the field name is
_id
,altname_aws
andaltname_local
, copy the field tocorpus_copied
.If
method
isauto
, copy the field tocorpus_copied
.If
method
isexplicit
, useclient_encryption
to explicitly encrypt the value.- Encrypt with the algorithm described by
algo
. - If
identifier
isid
- If
kms
islocal
set the key_id to the UUID with base64 valueLOCALAAAAAAAAAAAAAAAAA==
. - If
kms
isaws
set the key_id to the UUID with base64 valueAWSAAAAAAAAAAAAAAAAAAA==
.
- If
- If
identifier
isaltname
- If
kms
islocal
set the key_alt_name to "local". - If
kms
isaws
set the key_alt_name to "aws".
- If
If
allowed
is true, copy the field and encrypted value tocorpus_copied
. Ifallowed
is false. verify that an exception is thrown. Copy the unencrypted value to tocorpus_copied
.- Encrypt with the algorithm described by
Using
client_encrypted
, insertcorpus_copied
intodb.coll
.Using
client_encrypted
, find the inserted document fromdb.coll
to a variable namedcorpus_decrypted
. Since it should have been automatically decrypted, assert the document exactly matchescorpus
.Load corpus/corpus_encrypted.json to a variable named
corpus_encrypted_expected
. Usingclient
find the inserted document fromdb.coll
to a variable namedcorpus_encrypted_actual
.Iterate over each field of
corpus_encrypted_expected
and check the following:- If the
algo
isdet
, that the value equals the value of the corresponding field incorpus_encrypted_actual
. - If the
algo
isrand
andallowed
is true, that the value does not equal the value of the corresponding field incorpus_encrypted_actual
. - If
allowed
is true, decrypt the value withclient_encryption
. Decrypt the value of the corresponding field ofcorpus_encrypted
and validate that they are both equal. - If
allowed
is false, validate the value exactly equals the value of the corresponding field ofcorpus
(neither was encrypted).
- If the
Repeat steps 1-8 with a local JSON schema. I.e. amend step 4 to configure the schema on
client_encrypted
with theschema_map
option.
Data keys created with AWS KMS may specify a custom endpoint to contact (instead of the default endpoint derived from the AWS region).
Create a
ClientEncryption
object (referred to asclient_encryption
)Configure with
aws
KMS providers as follows:{ "aws": { <AWS credentials> } }
Configure with
keyVaultNamespace
set toadmin.datakeys
, and a default MongoClient as thekeyVaultClient
.Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" }
Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", endpoint: "kms.us-east-1.amazonaws.com" }
Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", endpoint: "kms.us-east-1.amazonaws.com:443" }
Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works.
Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", endpoint: "kms.us-east-1.amazonaws.com:12345" }
Expect this to fail with a socket connection error.
Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", endpoint: "kms.us-east-2.amazonaws.com" }
Expect this to fail with an exception with a message containing the string: "us-east-1"
Call client_encryption.createDataKey() with "aws" as the provider and the following masterKey:
{ region: "us-east-1", key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", endpoint: "example.com" }
Expect this to fail with an exception with a message containing the string: "parse error"