PYTHON-4112 Support named KMS providers (#1487)

Requires pymongocrypt >= 1.9.0 and libmongocrypt >= 1.9.0.
This commit is contained in:
Shane Harvey 2024-01-30 12:00:24 -08:00 committed by GitHub
parent 68d22b20bd
commit 0615df47b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 2674 additions and 86 deletions

View File

@ -14,6 +14,12 @@ PyMongo 4.7 brings a number of improvements including:
- Replaced usage of :class:`bson.son.SON` on all internal classes and commands to dict,
:attr:`options.pool_options.metadata` is now of type ``dict`` as opposed to :class:`bson.son.SON`.
- Significantly improved the performance of encoding BSON documents to JSON.
- Support for named KMS providers for client side field level encryption.
Previously supported KMS providers were only: aws, azure, gcp, kmip, and local.
The KMS provider is now expanded to support name suffixes (e.g. local:myname).
Named KMS providers enables more than one of each KMS provider type to be configured.
See the docstring for :class:`~pymongo.encryption_options.AutoEncryptionOpts`.
Note that named KMS providers requires pymongocrypt >=1.9 and libmongocrypt >=1.9.
- Fixed a bug where :class:`~bson.int64.Int64` instances could not always be encoded by `orjson`_. The following now
works::

View File

@ -125,8 +125,8 @@ examples show how to setup automatic client-side field level encryption
using :class:`~pymongo.encryption.ClientEncryption` to create a new
encryption data key.
.. note:: Automatic client-side field level encryption requires MongoDB 4.2
enterprise or a MongoDB 4.2 Atlas cluster. The community version of the
.. note:: Automatic client-side field level encryption requires MongoDB >=4.2
enterprise or a MongoDB >=4.2 Atlas cluster. The community version of the
server supports automatic decryption as well as
:ref:`explicit-client-side-encryption`.
@ -255,7 +255,7 @@ will result in an error.
Server-Side Field Level Encryption Enforcement
``````````````````````````````````````````````
The MongoDB 4.2 server supports using schema validation to enforce encryption
MongoDB >=4.2 servers supports using schema validation to enforce encryption
of specific fields in a collection. This schema validation will prevent an
application from inserting unencrypted values for any fields marked with the
``"encrypt"`` JSON schema keyword.
@ -457,8 +457,8 @@ Explicit encryption is a MongoDB community feature and does not use the
Explicit Encryption with Automatic Decryption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Although automatic encryption requires MongoDB 4.2 enterprise or a
MongoDB 4.2 Atlas cluster, automatic *decryption* is supported for all users.
Although automatic encryption requires MongoDB >=4.2 enterprise or a
MongoDB >=4.2 Atlas cluster, automatic *decryption* is supported for all users.
To configure automatic *decryption* without automatic *encryption* set
``bypass_auto_encryption=True`` in
:class:`~pymongo.encryption_options.AutoEncryptionOpts`:

View File

@ -101,7 +101,7 @@ def _wrap_encryption_errors() -> Iterator[None]:
# we should propagate them unchanged.
raise
except Exception as exc:
raise EncryptionError(exc) from None
raise EncryptionError(exc) from exc
class _EncryptionIO(MongoCryptCallback): # type: ignore[misc]
@ -520,6 +520,9 @@ class ClientEncryption(Generic[_DocumentType]):
data keys. This key should be generated and stored as securely
as possible.
KMS providers may be specified with an optional name suffix
separated by a colon, for example "kmip:name" or "aws:name".
Named KMS providers do not support :ref:`CSFLE on-demand credentials`.
:param key_vault_namespace: The namespace for the key vault collection.
The key vault collection contains all data keys used for encryption
and decryption. Data keys are stored as documents in this MongoDB
@ -674,12 +677,13 @@ class ClientEncryption(Generic[_DocumentType]):
"""Create and insert a new data key into the key vault collection.
:param kms_provider: The KMS provider to use. Supported values are
"aws", "azure", "gcp", "kmip", and "local".
"aws", "azure", "gcp", "kmip", "local", or a named provider like
"kmip:name".
:param master_key: Identifies a KMS-specific key used to encrypt the
new data key. If the kmsProvider is "local" the `master_key` is
not applicable and may be omitted.
If the `kms_provider` is "aws" it is required and has the
If the `kms_provider` type is "aws" it is required and has the
following fields::
- `region` (string): Required. The AWS region, e.g. "us-east-1".
@ -689,7 +693,7 @@ class ClientEncryption(Generic[_DocumentType]):
requests to. May include port number, e.g.
"kms.us-east-1.amazonaws.com:443".
If the `kms_provider` is "azure" it is required and has the
If the `kms_provider` type is "azure" it is required and has the
following fields::
- `keyVaultEndpoint` (string): Required. Host with optional
@ -697,7 +701,7 @@ class ClientEncryption(Generic[_DocumentType]):
- `keyName` (string): Required. Key name in the key vault.
- `keyVersion` (string): Optional. Version of the key to use.
If the `kms_provider` is "gcp" it is required and has the
If the `kms_provider` type is "gcp" it is required and has the
following fields::
- `projectId` (string): Required. The Google cloud project ID.
@ -709,7 +713,7 @@ class ClientEncryption(Generic[_DocumentType]):
- `endpoint` (string): Optional. Host with optional port.
Defaults to "cloudkms.googleapis.com".
If the `kms_provider` is "kmip" it is optional and has the
If the `kms_provider` type is "kmip" it is optional and has the
following fields::
- `keyId` (string): Optional. `keyId` is the KMIP Unique

View File

@ -55,13 +55,13 @@ class AutoEncryptionOpts:
) -> None:
"""Options to configure automatic client-side field level encryption.
Automatic client-side field level encryption requires MongoDB 4.2
enterprise or a MongoDB 4.2 Atlas cluster. Automatic encryption is not
Automatic client-side field level encryption requires MongoDB >=4.2
enterprise or a MongoDB >=4.2 Atlas cluster. Automatic encryption is not
supported for operations on a database or view and will result in
error.
Although automatic encryption requires MongoDB 4.2 enterprise or a
MongoDB 4.2 Atlas cluster, automatic *decryption* is supported for all
Although automatic encryption requires MongoDB >=4.2 enterprise or a
MongoDB >=4.2 Atlas cluster, automatic *decryption* is supported for all
users. To configure automatic *decryption* without automatic
*encryption* set ``bypass_auto_encryption=True``. Explicit
encryption and explicit decryption is also supported for all users
@ -94,12 +94,23 @@ class AutoEncryptionOpts:
data keys. This key should be generated and stored as securely
as possible.
KMS providers may be specified with an optional name suffix
separated by a colon, for example "kmip:name" or "aws:name".
Named KMS providers do not support :ref:`CSFLE on-demand credentials`.
Named KMS providers enables more than one of each KMS provider type to be configured.
For example, to configure multiple local KMS providers::
kms_providers = {
"local": {"key": local_kek1}, # Unnamed KMS provider.
"local:myname": {"key": local_kek2}, # Named KMS provider with name "myname".
}
:param key_vault_namespace: The namespace for the key vault collection.
The key vault collection contains all data keys used for encryption
and decryption. Data keys are stored as documents in this MongoDB
collection. Data keys are protected with encryption by a KMS
provider.
:param key_vault_client: By default the key vault collection
:param key_vault_client: By default, the key vault collection
is assumed to reside in the same MongoDB cluster as the encrypted
MongoClient. Use this option to route data key queries to a
separate MongoDB cluster.

View File

@ -113,6 +113,10 @@ AWS_CREDS = {
"accessKeyId": os.environ.get("FLE_AWS_KEY", ""),
"secretAccessKey": os.environ.get("FLE_AWS_SECRET", ""),
}
AWS_CREDS_2 = {
"accessKeyId": os.environ.get("FLE_AWS_KEY2", ""),
"secretAccessKey": os.environ.get("FLE_AWS_SECRET2", ""),
}
AZURE_CREDS = {
"tenantId": os.environ.get("FLE_AZURE_TENANTID", ""),
"clientId": os.environ.get("FLE_AZURE_CLIENTID", ""),

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -55,6 +55,38 @@
"result": {
"errorContains": "Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption."
}
},
{
"name": "assertCollectionNotExists",
"object": "testRunner",
"arguments": {
"database": "default",
"collection": "enxcol_.encryptedCollection.esc"
}
},
{
"name": "assertCollectionNotExists",
"object": "testRunner",
"arguments": {
"database": "default",
"collection": "enxcol_.encryptedCollection.ecc"
}
},
{
"name": "assertCollectionNotExists",
"object": "testRunner",
"arguments": {
"database": "default",
"collection": "enxcol_.encryptedCollection.ecoc"
}
},
{
"name": "assertCollectionNotExists",
"object": "testRunner",
"arguments": {
"database": "default",
"collection": "encryptedCollection"
}
}
]
}

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset"
]

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -2,7 +2,6 @@
"runOn": [
{
"minServerVersion": "7.0.0",
"serverless": "forbid",
"topology": [
"replicaset",
"sharded",

View File

@ -216,7 +216,10 @@
"command_started_event": {
"command": {
"getMore": {
"$$type": "long"
"$$type": [
"int",
"long"
]
},
"collection": "default",
"batchSize": 2

View File

@ -0,0 +1,197 @@
{
"runOn": [
{
"minServerVersion": "4.1.10"
}
],
"database_name": "default",
"collection_name": "default",
"data": [],
"json_schema": {
"properties": {
"encrypted_string": {
"encrypt": {
"keyId": [
{
"$binary": {
"base64": "local+name2+AAAAAAAAAA==",
"subType": "04"
}
}
],
"bsonType": "string",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
}
},
"bsonType": "object"
},
"key_vault_data": [
{
"_id": {
"$binary": {
"base64": "local+name2+AAAAAAAAAA==",
"subType": "04"
}
},
"keyMaterial": {
"$binary": {
"base64": "DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==",
"subType": "00"
}
},
"creationDate": {
"$date": {
"$numberLong": "1552949630483"
}
},
"updateDate": {
"$date": {
"$numberLong": "1552949630483"
}
},
"status": {
"$numberInt": "0"
},
"masterKey": {
"provider": "local:name2"
}
}
],
"tests": [
{
"description": "Automatically encrypt and decrypt with a named KMS provider",
"clientOptions": {
"autoEncryptOpts": {
"kmsProviders": {
"local:name2": {
"key": {
"$binary": {
"base64": "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk",
"subType": "00"
}
}
}
}
}
},
"operations": [
{
"name": "insertOne",
"arguments": {
"document": {
"_id": 1,
"encrypted_string": "string0"
}
}
},
{
"name": "find",
"arguments": {
"filter": {
"_id": 1
}
},
"result": [
{
"_id": 1,
"encrypted_string": "string0"
}
]
}
],
"expectations": [
{
"command_started_event": {
"command": {
"listCollections": 1,
"filter": {
"name": "default"
}
},
"command_name": "listCollections"
}
},
{
"command_started_event": {
"command": {
"find": "datakeys",
"filter": {
"$or": [
{
"_id": {
"$in": [
{
"$binary": {
"base64": "local+name2+AAAAAAAAAA==",
"subType": "04"
}
}
]
}
},
{
"keyAltNames": {
"$in": []
}
}
]
},
"$db": "keyvault",
"readConcern": {
"level": "majority"
}
},
"command_name": "find"
}
},
{
"command_started_event": {
"command": {
"insert": "default",
"documents": [
{
"_id": 1,
"encrypted_string": {
"$binary": {
"base64": "AZaHGpfp2pntvgAAAAAAAAAC07sFvTQ0I4O2U49hpr4HezaK44Ivluzv5ntQBTYHDlAJMLyRMyB6Dl+UGHBgqhHe/Xw+pcT9XdiUoOJYAx9g+w==",
"subType": "06"
}
}
}
],
"ordered": true
},
"command_name": "insert"
}
},
{
"command_started_event": {
"command": {
"find": "default",
"filter": {
"_id": 1
}
},
"command_name": "find"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"encrypted_string": {
"$binary": {
"base64": "AZaHGpfp2pntvgAAAAAAAAAC07sFvTQ0I4O2U49hpr4HezaK44Ivluzv5ntQBTYHDlAJMLyRMyB6Dl+UGHBgqhHe/Xw+pcT9XdiUoOJYAx9g+w==",
"subType": "06"
}
}
}
]
}
}
}
]
}

View File

@ -0,0 +1,396 @@
{
"description": "namedKMS-createDataKey",
"schemaVersion": "1.18",
"runOnRequirements": [
{
"csfle": true
}
],
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"clientEncryption": {
"id": "clientEncryption0",
"clientEncryptionOpts": {
"keyVaultClient": "client0",
"keyVaultNamespace": "keyvault.datakeys",
"kmsProviders": {
"aws:name1": {
"accessKeyId": {
"$$placeholder": 1
},
"secretAccessKey": {
"$$placeholder": 1
}
},
"azure:name1": {
"tenantId": {
"$$placeholder": 1
},
"clientId": {
"$$placeholder": 1
},
"clientSecret": {
"$$placeholder": 1
}
},
"gcp:name1": {
"email": {
"$$placeholder": 1
},
"privateKey": {
"$$placeholder": 1
}
},
"kmip:name1": {
"endpoint": {
"$$placeholder": 1
}
},
"local:name1": {
"key": {
"$$placeholder": 1
}
}
}
}
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "keyvault"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "datakeys"
}
}
],
"initialData": [
{
"databaseName": "keyvault",
"collectionName": "datakeys",
"documents": []
}
],
"tests": [
{
"description": "create data key with named AWS KMS provider",
"operations": [
{
"name": "createDataKey",
"object": "clientEncryption0",
"arguments": {
"kmsProvider": "aws:name1",
"opts": {
"masterKey": {
"key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
"region": "us-east-1"
}
}
},
"expectResult": {
"$$type": "binData"
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"databaseName": "keyvault",
"command": {
"insert": "datakeys",
"documents": [
{
"_id": {
"$$type": "binData"
},
"keyMaterial": {
"$$type": "binData"
},
"creationDate": {
"$$type": "date"
},
"updateDate": {
"$$type": "date"
},
"status": {
"$$exists": true
},
"masterKey": {
"provider": "aws:name1",
"key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
"region": "us-east-1"
}
}
],
"writeConcern": {
"w": "majority"
}
}
}
}
]
}
]
},
{
"description": "create datakey with named Azure KMS provider",
"operations": [
{
"name": "createDataKey",
"object": "clientEncryption0",
"arguments": {
"kmsProvider": "azure:name1",
"opts": {
"masterKey": {
"keyVaultEndpoint": "key-vault-csfle.vault.azure.net",
"keyName": "key-name-csfle"
}
}
},
"expectResult": {
"$$type": "binData"
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"databaseName": "keyvault",
"command": {
"insert": "datakeys",
"documents": [
{
"_id": {
"$$type": "binData"
},
"keyMaterial": {
"$$type": "binData"
},
"creationDate": {
"$$type": "date"
},
"updateDate": {
"$$type": "date"
},
"status": {
"$$exists": true
},
"masterKey": {
"provider": "azure:name1",
"keyVaultEndpoint": "key-vault-csfle.vault.azure.net",
"keyName": "key-name-csfle"
}
}
],
"writeConcern": {
"w": "majority"
}
}
}
}
]
}
]
},
{
"description": "create datakey with named GCP KMS provider",
"operations": [
{
"name": "createDataKey",
"object": "clientEncryption0",
"arguments": {
"kmsProvider": "gcp:name1",
"opts": {
"masterKey": {
"projectId": "devprod-drivers",
"location": "global",
"keyRing": "key-ring-csfle",
"keyName": "key-name-csfle"
}
}
},
"expectResult": {
"$$type": "binData"
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"databaseName": "keyvault",
"command": {
"insert": "datakeys",
"documents": [
{
"_id": {
"$$type": "binData"
},
"keyMaterial": {
"$$type": "binData"
},
"creationDate": {
"$$type": "date"
},
"updateDate": {
"$$type": "date"
},
"status": {
"$$exists": true
},
"masterKey": {
"provider": "gcp:name1",
"projectId": "devprod-drivers",
"location": "global",
"keyRing": "key-ring-csfle",
"keyName": "key-name-csfle"
}
}
],
"writeConcern": {
"w": "majority"
}
}
}
}
]
}
]
},
{
"description": "create datakey with named KMIP KMS provider",
"operations": [
{
"name": "createDataKey",
"object": "clientEncryption0",
"arguments": {
"kmsProvider": "kmip:name1"
},
"expectResult": {
"$$type": "binData"
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"databaseName": "keyvault",
"command": {
"insert": "datakeys",
"documents": [
{
"_id": {
"$$type": "binData"
},
"keyMaterial": {
"$$type": "binData"
},
"creationDate": {
"$$type": "date"
},
"updateDate": {
"$$type": "date"
},
"status": {
"$$exists": true
},
"masterKey": {
"provider": "kmip:name1",
"keyId": {
"$$type": "string"
}
}
}
],
"writeConcern": {
"w": "majority"
}
}
}
}
]
}
]
},
{
"description": "create datakey with named local KMS provider",
"operations": [
{
"name": "createDataKey",
"object": "clientEncryption0",
"arguments": {
"kmsProvider": "local:name1"
},
"expectResult": {
"$$type": "binData"
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"databaseName": "keyvault",
"command": {
"insert": "datakeys",
"documents": [
{
"_id": {
"$$type": "binData"
},
"keyMaterial": {
"$$type": "binData"
},
"creationDate": {
"$$type": "date"
},
"updateDate": {
"$$type": "date"
},
"status": {
"$$exists": true
},
"masterKey": {
"provider": "local:name1"
}
}
],
"writeConcern": {
"w": "majority"
}
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,130 @@
{
"description": "namedKMS-explicit",
"schemaVersion": "1.18",
"runOnRequirements": [
{
"csfle": true
}
],
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"clientEncryption": {
"id": "clientEncryption0",
"clientEncryptionOpts": {
"keyVaultClient": "client0",
"keyVaultNamespace": "keyvault.datakeys",
"kmsProviders": {
"local:name2": {
"key": "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk"
}
}
}
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "keyvault"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "datakeys"
}
}
],
"initialData": [
{
"databaseName": "keyvault",
"collectionName": "datakeys",
"documents": [
{
"_id": {
"$binary": {
"base64": "local+name2+AAAAAAAAAA==",
"subType": "04"
}
},
"keyAltNames": [
"local:name2"
],
"keyMaterial": {
"$binary": {
"base64": "DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==",
"subType": "00"
}
},
"creationDate": {
"$date": {
"$numberLong": "1552949630483"
}
},
"updateDate": {
"$date": {
"$numberLong": "1552949630483"
}
},
"status": {
"$numberInt": "0"
},
"masterKey": {
"provider": "local:name2"
}
}
]
}
],
"tests": [
{
"description": "can explicitly encrypt with a named KMS provider",
"operations": [
{
"name": "encrypt",
"object": "clientEncryption0",
"arguments": {
"value": "foobar",
"opts": {
"keyAltName": "local:name2",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
},
"expectResult": {
"$binary": {
"base64": "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==",
"subType": "06"
}
}
}
]
},
{
"description": "can explicitly decrypt with a named KMS provider",
"operations": [
{
"name": "decrypt",
"object": "clientEncryption0",
"arguments": {
"value": {
"$binary": {
"base64": "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==",
"subType": "06"
}
}
},
"expectResult": "foobar"
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -2090,6 +2090,32 @@ class TestKmsTLSOptions(EncryptionIntegrationTest):
# [WinError 10054] An existing connection was forcibly closed by the remote host
if sys.platform == "win32":
self.cert_error += "|forcibly closed"
# 4, Test named KMS providers.
providers = {
"aws:no_client_cert": AWS_CREDS,
"azure:no_client_cert": {"identityPlatformEndpoint": "127.0.0.1:8002", **AZURE_CREDS},
"gcp:no_client_cert": {"endpoint": "127.0.0.1:8002", **GCP_CREDS},
"kmip:no_client_cert": KMIP_CREDS,
"aws:with_tls": AWS_CREDS,
"azure:with_tls": {"identityPlatformEndpoint": "127.0.0.1:8002", **AZURE_CREDS},
"gcp:with_tls": {"endpoint": "127.0.0.1:8002", **GCP_CREDS},
"kmip:with_tls": KMIP_CREDS,
}
no_cert = {"tlsCAFile": CA_PEM}
with_cert = {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}
kms_tls_opts_4 = {
"aws:no_client_cert": no_cert,
"azure:no_client_cert": no_cert,
"gcp:no_client_cert": no_cert,
"kmip:no_client_cert": no_cert,
"aws:with_tls": with_cert,
"azure:with_tls": with_cert,
"gcp:with_tls": with_cert,
"kmip:with_tls": with_cert,
}
self.client_encryption_with_names = ClientEncryption(
providers, "keyvault.datakeys", self.client, OPTS, kms_tls_options=kms_tls_opts_4
)
def test_01_aws(self):
key = {
@ -2178,6 +2204,43 @@ class TestKmsTLSOptions(EncryptionIntegrationTest):
raise self.skipTest("OCSP not enabled")
self.assertFalse(ctx.check_ocsp_endpoint)
def test_06_named_kms_providers_apply_tls_options_aws(self):
key = {
"region": "us-east-1",
"key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0",
"endpoint": "127.0.0.1:8002",
}
# Missing client cert error.
with self.assertRaisesRegex(EncryptionError, self.cert_error):
self.client_encryption_with_names.create_data_key("aws:no_client_cert", key)
# "parse error" here means that the TLS handshake succeeded.
with self.assertRaisesRegex(EncryptionError, "parse error"):
self.client_encryption_with_names.create_data_key("aws:with_tls", key)
def test_06_named_kms_providers_apply_tls_options_azure(self):
key = {"keyVaultEndpoint": "doesnotexist.local", "keyName": "foo"}
# Missing client cert error.
with self.assertRaisesRegex(EncryptionError, self.cert_error):
self.client_encryption_with_names.create_data_key("azure:no_client_cert", key)
# "HTTP status=404" here means that the TLS handshake succeeded.
with self.assertRaisesRegex(EncryptionError, "HTTP status=404"):
self.client_encryption_with_names.create_data_key("azure:with_tls", key)
def test_06_named_kms_providers_apply_tls_options_gcp(self):
key = {"projectId": "foo", "location": "bar", "keyRing": "baz", "keyName": "foo"}
# Missing client cert error.
with self.assertRaisesRegex(EncryptionError, self.cert_error):
self.client_encryption_with_names.create_data_key("gcp:no_client_cert", key)
# "HTTP status=404" here means that the TLS handshake succeeded.
with self.assertRaisesRegex(EncryptionError, "HTTP status=404"):
self.client_encryption_with_names.create_data_key("gcp:with_tls", key)
def test_06_named_kms_providers_apply_tls_options_kmip(self):
# Missing client cert error.
with self.assertRaisesRegex(EncryptionError, self.cert_error):
self.client_encryption_with_names.create_data_key("kmip:no_client_cert")
self.client_encryption_with_names.create_data_key("kmip:with_tls")
# https://github.com/mongodb/specifications/blob/50e26fe/source/client-side-encryption/tests/README.rst#unique-index-on-keyaltnames
class TestUniqueIndexOnKeyAltNamesProse(EncryptionIntegrationTest):

View File

@ -0,0 +1,29 @@
{
"description": "clientEncryptionOpts-kmsProviders-invalidName",
"schemaVersion": "1.18",
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"clientEncryption": {
"id": "clientEncryption0",
"clientEncryptionOpts": {
"keyVaultClient": "client0",
"keyVaultNamespace": "keyvault.datakeys",
"kmsProviders": {
"aws:name_with_invalid_character*": {}
}
}
}
}
],
"tests": [
{
"description": "",
"operations": []
}
]
}

View File

@ -0,0 +1,25 @@
{
"description": "expectedError-errorResponse-type",
"schemaVersion": "1.12",
"createEntities": [
{
"client": {
"id": "client0"
}
}
],
"tests": [
{
"description": "foo",
"operations": [
{
"name": "foo",
"object": "client0",
"expectError": {
"errorResponse": 0
}
}
]
}
]
}

View File

@ -0,0 +1,278 @@
{
"description": "entity-commandCursor",
"schemaVersion": "1.3",
"createEntities": [
{
"client": {
"id": "client",
"useMultipleMongoses": false,
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "db",
"client": "client",
"databaseName": "db"
}
},
{
"collection": {
"id": "collection",
"database": "db",
"collectionName": "collection"
}
}
],
"initialData": [
{
"collectionName": "collection",
"databaseName": "db",
"documents": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
},
{
"_id": 5,
"x": 55
}
]
}
],
"tests": [
{
"description": "runCursorCommand creates and exhausts cursor by running getMores",
"operations": [
{
"name": "runCursorCommand",
"object": "db",
"arguments": {
"commandName": "find",
"batchSize": 2,
"command": {
"find": "collection",
"filter": {},
"batchSize": 2
}
},
"expectResult": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
},
{
"_id": 5,
"x": 55
}
]
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "collection",
"filter": {},
"batchSize": 2,
"$db": "db",
"lsid": {
"$$exists": true
}
},
"commandName": "find"
}
},
{
"commandStartedEvent": {
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "collection",
"$db": "db",
"lsid": {
"$$exists": true
}
},
"commandName": "getMore"
}
},
{
"commandStartedEvent": {
"command": {
"getMore": {
"$$type": [
"int",
"long"
]
},
"collection": "collection",
"$db": "db",
"lsid": {
"$$exists": true
}
},
"commandName": "getMore"
}
}
]
}
]
},
{
"description": "createCommandCursor creates a cursor and stores it as an entity that can be iterated one document at a time",
"operations": [
{
"name": "createCommandCursor",
"object": "db",
"arguments": {
"commandName": "find",
"batchSize": 2,
"command": {
"find": "collection",
"filter": {},
"batchSize": 2
}
},
"saveResultAsEntity": "myRunCommandCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 1,
"x": 11
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 2,
"x": 22
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 3,
"x": 33
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 4,
"x": 44
}
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 5,
"x": 55
}
}
]
},
{
"description": "createCommandCursor's cursor can be closed and will perform a killCursors operation",
"operations": [
{
"name": "createCommandCursor",
"object": "db",
"arguments": {
"commandName": "find",
"batchSize": 2,
"command": {
"find": "collection",
"filter": {},
"batchSize": 2
}
},
"saveResultAsEntity": "myRunCommandCursor"
},
{
"name": "iterateUntilDocumentOrError",
"object": "myRunCommandCursor",
"expectResult": {
"_id": 1,
"x": 11
}
},
{
"name": "close",
"object": "myRunCommandCursor"
}
],
"expectEvents": [
{
"client": "client",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "collection",
"filter": {},
"batchSize": 2,
"$db": "db",
"lsid": {
"$$exists": true
}
},
"commandName": "find"
}
},
{
"commandStartedEvent": {
"command": {
"killCursors": "collection",
"cursors": {
"$$type": "array"
}
},
"commandName": "killCursors"
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,70 @@
{
"description": "expectedError-errorResponse",
"schemaVersion": "1.12",
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "test"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"tests": [
{
"description": "Unsupported command",
"operations": [
{
"name": "runCommand",
"object": "database0",
"arguments": {
"commandName": "unsupportedCommand",
"command": {
"unsupportedCommand": 1
}
},
"expectError": {
"errorResponse": {
"errmsg": {
"$$type": "string"
}
}
}
}
]
},
{
"description": "Unsupported query operator",
"operations": [
{
"name": "find",
"object": "collection0",
"arguments": {
"filter": {
"$unsupportedQueryOperator": 1
}
},
"expectError": {
"errorResponse": {
"errmsg": {
"$$type": "string"
}
}
}
}
]
}
]
}

View File

@ -32,6 +32,7 @@ import types
from collections import abc
from test import (
AWS_CREDS,
AWS_CREDS_2,
AZURE_CREDS,
CA_PEM,
CLIENT_PEM,
@ -143,10 +144,16 @@ KMS_TLS_OPTS = {
PLACEHOLDER_MAP = {}
for provider_name, provider_data in [
("local", {"key": LOCAL_MASTER_KEY}),
("local:name1", {"key": LOCAL_MASTER_KEY}),
("aws", AWS_CREDS),
("aws:name1", AWS_CREDS),
("aws:name2", AWS_CREDS_2),
("azure", AZURE_CREDS),
("azure:name1", AZURE_CREDS),
("gcp", GCP_CREDS),
("gcp:name1", GCP_CREDS),
("kmip", KMIP_CREDS),
("kmip:name1", KMIP_CREDS),
]:
for key, value in provider_data.items():
placeholder = f"/clientEncryptionOpts/kmsProviders/{provider_name}/{key}"
@ -532,12 +539,18 @@ class EntityMapUtil:
opts = camel_to_snake_args(spec["clientEncryptionOpts"].copy())
if isinstance(opts["key_vault_client"], str):
opts["key_vault_client"] = self[opts["key_vault_client"]]
# Set TLS options for providers like "kmip:name1".
kms_tls_options = {}
for provider in opts["kms_providers"]:
provider_type = provider.split(":")[0]
if provider_type in KMS_TLS_OPTS:
kms_tls_options[provider] = KMS_TLS_OPTS[provider_type]
self[spec["id"]] = ClientEncryption(
opts["kms_providers"],
opts["key_vault_namespace"],
opts["key_vault_client"],
DEFAULT_CODEC_OPTIONS,
opts.get("kms_tls_options", KMS_TLS_OPTS),
opts.get("kms_tls_options", kms_tls_options),
)
return
elif entity_type == "thread":
@ -920,7 +933,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
a class attribute ``TEST_SPEC``.
"""
SCHEMA_VERSION = Version.from_string("1.17")
SCHEMA_VERSION = Version.from_string("1.18")
RUN_ON_LOAD_BALANCER = True
RUN_ON_SERVERLESS = True
TEST_SPEC: Any
@ -1056,8 +1069,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
expect_result = spec.get("expectResult")
error_response = spec.get("errorResponse")
if error_response:
for k in error_response.keys():
self.assertEqual(error_response[k], exception.details[k])
self.match_evaluator.match_result(error_response, exception.details)
if is_error:
# already satisfied because exception was raised
@ -1261,10 +1273,8 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
def _clientEncryptionOperation_createDataKey(self, target, *args, **kwargs):
if "opts" in kwargs:
opts = kwargs.pop("opts")
kwargs["master_key"] = opts.get("masterKey")
kwargs["key_alt_names"] = opts.get("keyAltNames")
kwargs["key_material"] = opts.get("keyMaterial")
kwargs.update(camel_to_snake_args(kwargs.pop("opts")))
return target.create_data_key(*args, **kwargs)
def _clientEncryptionOperation_getKeys(self, target, *args, **kwargs):
@ -1278,14 +1288,17 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
def _clientEncryptionOperation_rewrapManyDataKey(self, target, *args, **kwargs):
if "opts" in kwargs:
opts = kwargs.pop("opts")
kwargs["provider"] = opts.get("provider")
kwargs["master_key"] = opts.get("masterKey")
kwargs.update(camel_to_snake_args(kwargs.pop("opts")))
data = target.rewrap_many_data_key(*args, **kwargs)
if data.bulk_write_result:
return {"bulkWriteResult": parse_bulk_write_result(data.bulk_write_result)}
return {}
def _clientEncryptionOperation_encrypt(self, target, *args, **kwargs):
if "opts" in kwargs:
kwargs.update(camel_to_snake_args(kwargs.pop("opts")))
return target.encrypt(*args, **kwargs)
def _bucketOperation_download(self, target: GridFSBucket, *args: Any, **kwargs: Any) -> bytes:
with target.open_download_stream(*args, **kwargs) as gout:
return gout.read()