-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
f97d911
commit 8a8313d
Showing
4 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import os | ||
import asyncio | ||
|
||
from bson.codec_options import CodecOptions | ||
from bson import json_util | ||
|
||
from motor.motor_asyncio import AsyncIOMotorClient | ||
from pymongo.encryption import (Algorithm, | ||
ClientEncryption) | ||
from pymongo.encryption_options import AutoEncryptionOpts | ||
|
||
|
||
def create_json_schema_file(kms_providers, key_vault_namespace, | ||
key_vault_client): | ||
client_encryption = ClientEncryption( | ||
kms_providers, | ||
key_vault_namespace, | ||
key_vault_client, | ||
# The CodecOptions class used for encrypting and decrypting. | ||
# This should be the same CodecOptions instance you have configured | ||
# on MongoClient, Database, or Collection. We will not be calling | ||
# encrypt() or decrypt() in this example so we can use any | ||
# CodecOptions. | ||
CodecOptions()) | ||
|
||
# Create a new data key and json schema for the encryptedField. | ||
# https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules | ||
data_key_id = client_encryption.create_data_key( | ||
'local', key_alt_names=['pymongo_encryption_example_1']) | ||
schema = { | ||
"properties": { | ||
"encryptedField": { | ||
"encrypt": { | ||
"keyId": [data_key_id], | ||
"bsonType": "string", | ||
"algorithm": | ||
Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic | ||
} | ||
} | ||
}, | ||
"bsonType": "object" | ||
} | ||
# Use CANONICAL_JSON_OPTIONS so that other drivers and tools will be | ||
# able to parse the MongoDB extended JSON file. | ||
json_schema_string = json_util.dumps( | ||
schema, json_options=json_util.CANONICAL_JSON_OPTIONS) | ||
|
||
with open('jsonSchema.json', 'w') as file: | ||
file.write(json_schema_string) | ||
|
||
|
||
async def main(): | ||
# The MongoDB namespace (db.collection) used to store the | ||
# encrypted documents in this example. | ||
encrypted_namespace = "test.coll" | ||
|
||
# This must be the same master key that was used to create | ||
# the encryption key. | ||
local_master_key = os.urandom(96) | ||
kms_providers = {"local": {"key": local_master_key}} | ||
|
||
# The MongoDB namespace (db.collection) used to store | ||
# the encryption data keys. | ||
key_vault_namespace = "encryption.__pymongoTestKeyVault" | ||
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1) | ||
|
||
# The MongoClient used to access the key vault (key_vault_namespace). | ||
key_vault_client = AsyncIOMotorClient() | ||
key_vault = key_vault_client[key_vault_db_name][key_vault_coll_name] | ||
# Ensure that two data keys cannot share the same keyAltName. | ||
await key_vault.drop() | ||
await key_vault.create_index( | ||
"keyAltNames", | ||
unique=True, | ||
partialFilterExpression={"keyAltNames": {"$exists": True}}) | ||
|
||
create_json_schema_file( | ||
kms_providers, key_vault_namespace, key_vault_client) | ||
|
||
# Load the JSON Schema and construct the local schema_map option. | ||
with open('jsonSchema.json', 'r') as file: | ||
json_schema_string = file.read() | ||
json_schema = json_util.loads(json_schema_string) | ||
schema_map = {encrypted_namespace: json_schema} | ||
|
||
auto_encryption_opts = AutoEncryptionOpts( | ||
kms_providers, key_vault_namespace, schema_map=schema_map) | ||
|
||
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts) | ||
db_name, coll_name = encrypted_namespace.split(".", 1) | ||
coll = client[db_name][coll_name] | ||
# Clear old data | ||
await coll.drop() | ||
|
||
await coll.insert_one({"encryptedField": "123456789"}) | ||
decrypted_doc = await coll.find_one() | ||
print('Decrypted document: %s' % (decrypted_doc,)) | ||
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name] | ||
encrypted_doc = await unencrypted_coll.find_one() | ||
print('Encrypted document: %s' % (encrypted_doc,)) | ||
|
||
|
||
if __name__ == "__main__": | ||
loop = asyncio.get_event_loop() | ||
loop.run_until_complete(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
.. _Client-Side Field Level Encryption: | ||
|
||
Client-Side Field Level Encryption | ||
================================== | ||
|
||
Starting in MongoDB 4.2, client-side field level encryption allows an application | ||
to encrypt specific data fields in addition to pre-existing MongoDB | ||
encryption features such as `Encryption at Rest | ||
<https://dochub.mongodb.org/core/security-encryption-at-rest>`_ and | ||
`TLS/SSL (Transport Encryption) | ||
<https://dochub.mongodb.org/core/security-tls-transport-encryption>`_. | ||
|
||
With field level encryption, applications can encrypt fields in documents | ||
*prior* to transmitting data over the wire to the server. Client-side field | ||
level encryption supports workloads where applications must guarantee that | ||
unauthorized parties, including server administrators, cannot read the | ||
encrypted data. | ||
|
||
.. mongodoc:: client-side-field-level-encryption | ||
|
||
Dependencies | ||
------------ | ||
|
||
To get started using client-side field level encryption in your project, | ||
you will need to install the | ||
`pymongocrypt <https://pypi.org/project/pymongocrypt/>`_ library | ||
as well as the driver itself. Install both the driver and a compatible | ||
version of pymongocrypt like this:: | ||
|
||
$ python -m pip install 'pymongo[encryption]' | ||
|
||
Note that installing on Linux requires pip 19 or later for manylinux2010 wheel | ||
support. For more information about installing pymongocrypt see | ||
`the installation instructions on the project's PyPI page | ||
<https://pypi.org/project/pymongocrypt/>`_. | ||
|
||
mongocryptd | ||
----------- | ||
|
||
The ``mongocryptd`` binary is required for automatic client-side encryption | ||
and is included as a component in the `MongoDB Enterprise Server package | ||
<https://dochub.mongodb.org/core/install-mongodb-enterprise>`_. For more | ||
information on this binary, see the `PyMongo documentation on mongocryptd | ||
<https://pymongo.readthedocs.io/en/stable/examples/encryption.html>`_. | ||
|
||
Automatic Client-Side Field Level Encryption | ||
-------------------------------------------- | ||
|
||
Automatic client-side field level encryption is enabled by creating a | ||
:class:`~pymongo.mongo_client.MongoClient` with the ``auto_encryption_opts`` | ||
option set to an instance of | ||
:class:`~pymongo.encryption_options.AutoEncryptionOpts`. The following | ||
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 | ||
server supports automatic decryption as well as | ||
:ref:`explicit-client-side-encryption`. | ||
|
||
Providing Local Automatic Encryption Rules | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The following example shows how to specify automatic encryption rules via the | ||
``schema_map`` option. The automatic encryption rules are expressed using a | ||
`strict subset of the JSON Schema syntax | ||
<https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules>`_. | ||
|
||
Supplying a ``schema_map`` provides more security than relying on | ||
JSON Schemas obtained from the server. It protects against a | ||
malicious server advertising a false JSON Schema, which could trick | ||
the client into sending unencrypted data that should be encrypted. | ||
|
||
JSON Schemas supplied in the ``schema_map`` only apply to configuring | ||
automatic client-side field level encryption. Other validation | ||
rules in the JSON schema will not be enforced by the driver and | ||
will result in an error. | ||
|
||
|
||
.. literalinclude:: auto_csfle_example.py | ||
:language: python3 | ||
|
||
Server-Side Field Level Encryption Enforcement | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The MongoDB 4.2+ server 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. | ||
|
||
The following example shows how to setup automatic client-side field level | ||
encryption using | ||
:class:`~pymongo.encryption.ClientEncryption` to create a new encryption | ||
data key and create a collection with the | ||
`Automatic Encryption JSON Schema Syntax | ||
<https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules>`_. | ||
|
||
.. literalinclude:: server_fle_enforcement_example.py | ||
:language: python3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import os | ||
import asyncio | ||
|
||
from bson.codec_options import CodecOptions | ||
from bson.binary import STANDARD | ||
|
||
from motor.motor_asyncio import AsyncIOMotorClient | ||
from pymongo.encryption import (Algorithm, | ||
ClientEncryption) | ||
from pymongo.encryption_options import AutoEncryptionOpts | ||
from pymongo.errors import OperationFailure | ||
from pymongo.write_concern import WriteConcern | ||
|
||
|
||
async def main(): | ||
# The MongoDB namespace (db.collection) used to store the | ||
# encrypted documents in this example. | ||
encrypted_namespace = "test.coll" | ||
|
||
# This must be the same master key that was used to create | ||
# the encryption key. | ||
local_master_key = os.urandom(96) | ||
kms_providers = {"local": {"key": local_master_key}} | ||
|
||
# The MongoDB namespace (db.collection) used to store | ||
# the encryption data keys. | ||
key_vault_namespace = "encryption.__pymongoTestKeyVault" | ||
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1) | ||
|
||
# The MongoClient used to access the key vault (key_vault_namespace). | ||
key_vault_client = AsyncIOMotorClient() | ||
key_vault = key_vault_client[key_vault_db_name][key_vault_coll_name] | ||
# Ensure that two data keys cannot share the same keyAltName. | ||
await key_vault.drop() | ||
await key_vault.create_index( | ||
"keyAltNames", | ||
unique=True, | ||
partialFilterExpression={"keyAltNames": {"$exists": True}}) | ||
|
||
client_encryption = ClientEncryption( | ||
kms_providers, | ||
key_vault_namespace, | ||
key_vault_client, | ||
# The CodecOptions class used for encrypting and decrypting. | ||
# This should be the same CodecOptions instance you have configured | ||
# on MongoClient, Database, or Collection. We will not be calling | ||
# encrypt() or decrypt() in this example so we can use any | ||
# CodecOptions. | ||
CodecOptions()) | ||
|
||
# Create a new data key and json schema for the encryptedField. | ||
data_key_id = client_encryption.create_data_key( | ||
'local', key_alt_names=['pymongo_encryption_example_2']) | ||
json_schema = { | ||
"properties": { | ||
"encryptedField": { | ||
"encrypt": { | ||
"keyId": [data_key_id], | ||
"bsonType": "string", | ||
"algorithm": | ||
Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic | ||
} | ||
} | ||
}, | ||
"bsonType": "object" | ||
} | ||
|
||
auto_encryption_opts = AutoEncryptionOpts( | ||
kms_providers, key_vault_namespace) | ||
client = AsyncIOMotorClient(auto_encryption_opts=auto_encryption_opts) | ||
db_name, coll_name = encrypted_namespace.split(".", 1) | ||
db = client[db_name] | ||
# Clear old data | ||
await db.drop_collection(coll_name) | ||
# Create the collection with the encryption JSON Schema. | ||
await db.create_collection( | ||
coll_name, | ||
# uuid_representation=STANDARD is required to ensure that any | ||
# UUIDs in the $jsonSchema document are encoded to BSON Binary | ||
# with the standard UUID subtype 4. This is only needed when | ||
# running the "create" collection command with an encryption | ||
# JSON Schema. | ||
codec_options=CodecOptions(uuid_representation=STANDARD), | ||
write_concern=WriteConcern(w="majority"), | ||
validator={"$jsonSchema": json_schema}) | ||
coll = client[db_name][coll_name] | ||
|
||
await coll.insert_one({"encryptedField": "123456789"}) | ||
decrypted_doc = await coll.find_one() | ||
print('Decrypted document: %s' % (decrypted_doc,)) | ||
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name] | ||
encrypted_doc = await unencrypted_coll.find_one() | ||
print('Encrypted document: %s' % (encrypted_doc,)) | ||
try: | ||
await unencrypted_coll.insert_one({"encryptedField": "123456789"}) | ||
except OperationFailure as exc: | ||
print('Unencrypted insert failed: %s' % (exc.details,)) | ||
|
||
|
||
if __name__ == "__main__": | ||
loop = asyncio.get_event_loop() | ||
loop.run_until_complete(main()) |