Configuring Queryable Encryption

Added in version 5.2.3.

Queryable Encryption is a powerful MongoDB feature that allows you to encrypt sensitive fields in your database while still supporting queries on that encrypted data.

This section will guide you through the process of configuring Queryable Encryption in your Django project.

MongoDB requirements

Queryable Encryption can be used with MongoDB replica sets or sharded clusters running version 8.0 or later. Standalone instances are not supported. The Queryable Encryption Compatibility table summarizes which MongoDB server products support Queryable Encryption.

Installation

In addition to Django MongoDB Backend’s regular installation and configuration steps, Queryable Encryption has additional Python dependencies:

$ pip install django-mongodb-backend[encryption]

Configuring the DATABASES setting

In addition to the database settings required to use Django MongoDB Backend, Queryable Encryption requires you to configure a separate encrypted database connection in your DATABASES setting.

Encrypted database

An encrypted database is a separate database connection in your DATABASES setting that is configured to use PyMongo’s automatic encryption.

Here’s how to configure an encrypted database using a local KMS provider and encryption keys stored in the encryption.__keyVault collection:

import os

from pymongo.encryption_options import AutoEncryptionOpts

DATABASES = {
    "default": {
        "ENGINE": "django_mongodb_backend",
        "HOST": "mongodb+srv://cluster0.example.mongodb.net",
        "NAME": "my_database",
        # ...
    },
    "encrypted": {
        "ENGINE": "django_mongodb_backend",
        "HOST": "mongodb+srv://cluster0.example.mongodb.net",
        "NAME": "my_database_encrypted",
        "USER": "my_user",
        "PASSWORD": "my_password",
        "PORT": 27017,
        "OPTIONS": {
            "auto_encryption_opts": AutoEncryptionOpts(
                key_vault_namespace="encryption.__keyVault",
                kms_providers={"local": {"key": os.urandom(96)}},
            )
        },
    },
}

Local KMS provider key

In the example above, a random key is generated for the local KMS provider using os.urandom(96). In a production environment, you should securely store and manage your encryption keys.

Configuring the DATABASE_ROUTERS setting

Similar to configuring the DATABASE_ROUTERS setting for embedded models, Queryable Encryption requires a DATABASE_ROUTERS setting to route database operations to the encrypted database.

The following example shows how to configure a router for the “myapp” application that routes database operations to the encrypted database for all models in that application:

# myapp/routers.py
class EncryptedRouter:
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == "myapp":
            return db == "encrypted"
        # Prevent migrations on the encrypted database for other apps
        if db == "encrypted":
            return False
        return None

    def db_for_read(self, model, **hints):
        if model._meta.app_label == "myapp":
            return "encrypted"
        return None

    db_for_write = db_for_read

Then in your Django settings, add the custom database router to the DATABASE_ROUTERS setting:

# settings.py
DATABASE_ROUTERS = ["myapp.routers.EncryptedRouter"]

Configuring the Key Management Service (KMS)

To use Queryable Encryption, you must configure a Key Management Service (KMS) to store and manage your encryption keys. Django MongoDB Backend allows you to configure multiple KMS providers and select the appropriate provider for each model using a custom database router.

The KMS is responsible for managing the encryption keys used to encrypt and decrypt data. The following table summarizes the available KMS configuration options followed by an example of how to use them.

KMS_CREDENTIALS

A dictionary of Key Management Service (KMS) credentials configured in the DATABASES setting.

kms_providers

A dictionary of KMS provider credentials used to access the KMS with kms_provider.

kms_provider

A single KMS provider name configured in your custom database router.

Example of KMS configuration with aws in your kms_providers setting:

from pymongo.encryption_options import AutoEncryptionOpts

DATABASES = {
    "encrypted": {
        # ...
        "OPTIONS": {
            "auto_encryption_opts": AutoEncryptionOpts(
                # ...
                kms_providers={
                    "aws": {
                        "accessKeyId": "your-access-key-id",
                        "secretAccessKey": "your-secret-access-key",
                    },
                },
            ),
        },
        "KMS_CREDENTIALS": {
            "aws": {
                "key": os.getenv("AWS_KEY_ARN", ""),
                "region": os.getenv("AWS_KEY_REGION", ""),
            },
        },
    },
}

(TODO: If there’s a use case for multiple providers, motivate with a use case and add a test.)

If you’ve configured multiple KMS providers, you must define logic to determine the provider for each model in your database router:

class EncryptedRouter:
    # ...
    def kms_provider(self, model, **hints):
        return "aws"

Configuring the encrypted_fields_map option

When you configure the DATABASES setting for Queryable Encryption without specifying an encrypted_fields_map, Django MongoDB Backend will create encrypted collections, including encryption keys, when you run migrations for models that have encrypted fields.

Encryption keys for encrypted fields are stored in the key vault specified in the DATABASES setting. To see the keys created by Django MongoDB Backend, along with the entire schema, you can run the showencryptedfieldsmap command:

$ python manage.py showencryptedfieldsmap --database encrypted

Use the output of showencryptedfieldsmap to set the encrypted_fields_map in AutoEncryptionOpts in your Django settings:

from bson import json_util
from pymongo.encryption_options import AutoEncryptionOpts

DATABASES = {
    "encrypted": {
        # ...
        "OPTIONS": {
            "auto_encryption_opts": AutoEncryptionOpts(
                # ...
                encrypted_fields_map=json_util.loads(
                    """{
                    "encrypt_patient": {
                        "fields": [
                             {
                                 "bsonType": "string",
                                 "path": "patient_record.ssn",
                                 "keyId": {
                                      "$binary": {
                                          "base64": "2MA29LaARIOqymYHGmi2mQ==",
                                          "subType": "04"
                                      }
                                 },
                                 "queries": {
                                     "queryType": "equality"
                                 }
                             },
                        ]
                    }}"""
                ),
            ),
        },
    },
}

Security consideration

Supplying an encrypted fields map provides more security than relying on an encrypted fields map obtained from the server. It protects against a malicious server advertising a false encrypted fields map.

Configuring the Automatic Encryption Shared Library

The Automatic Encryption Shared Library is a preferred alternative to mongocryptd and does not require you to start another process to perform automatic encryption.

In practice, if you use Atlas or Enterprise MongoDB, mongocryptd is already configured for you, however in such cases the shared library is still recommended for use with Queryable Encryption.

You can download the shared library from the Official MongoDB Packages and configure it in your Django settings using the crypt_shared_lib_path option in AutoEncryptionOpts.

The following example shows how to configure the shared library in your Django settings:

from pymongo.encryption_options import AutoEncryptionOpts

DATABASES = {
    "encrypted": {
        # ...
        "OPTIONS": {
            "auto_encryption_opts": AutoEncryptionOpts(
                # ...
                crypt_shared_lib_path="/path/to/mongo_crypt_shared_v1.dylib",
            )
        },
        # ...
    },
}

You are now ready to start developing applications with Queryable Encryption!