==================== Queryable Encryption ==================== .. versionadded:: 6.0.1 :doc:`manual:core/queryable-encryption` is a powerful MongoDB feature that allows you to encrypt sensitive fields in your database while still being able to query those fields. This document will guide you through the process of configuring Queryable Encryption in your Django project. .. admonition:: MongoDB requirements Queryable Encryption can be used with MongoDB replica sets or sharded clusters running version 7.0 or later. Standalone instances are not supported. The :ref:`manual:qe-compatibility-reference` table summarizes which MongoDB server products support Queryable Encryption. Installation ============ In addition to Django MongoDB Backend's regular :doc:`installation ` and :doc:`configuration ` steps, Queryable Encryption requires PyMongo 4.16.0 or later, as well as some additional Python dependencies that can be installed like so: .. code-block:: console $ pip install 'django-mongodb-backend[encryption]' You'll also have to download the :ref:`Automatic Encryption Shared Library `. You can choose the latest version, even if it doesn't match your MongoDB server version. Extract the shared library from the archive. You'll use the path to it in the next step (see ``crypt_shared_lib_path``). .. _qe-configuring-databases-setting: Configuring the ``DATABASES`` setting ===================================== In addition to the :ref:`database settings ` required to use Django MongoDB Backend, Queryable Encryption typically requires configuring a separate database connection that uses use PyMongo's :class:`~pymongo.encryption_options.AutoEncryptionOpts`. Here's a sample configuration using a local KMS provider:: from pymongo.encryption_options import AutoEncryptionOpts DATABASES = { "default": { "ENGINE": "django_mongodb_backend", "HOST": "mongodb+srv://cluster0.example.mongodb.net", "NAME": "my_db", # ... }, "encrypted": { "ENGINE": "django_mongodb_backend", "HOST": "mongodb+srv://cluster0.example.mongodb.net", "NAME": "encrypted_db", # ... "OPTIONS": { "auto_encryption_opts": AutoEncryptionOpts( key_vault_namespace="encrypted_db.__keyVault", kms_providers={ "local": { # Generated by os.urandom(96) "key": ( b'-\xc3\x0c\xe3\x93\xc3\x8b\xc0\xf8\x12\xc5#b' b'\x19\xf3\xbc\xccR\xc8\xedI\xda\\ \xfb\x9cB' b'\x7f\xab5\xe7\xb5\xc9x\xb8\xd4d\xba\xdc\x9c' b'\x9a\xdb9J]\xe6\xce\x104p\x079q.=\xeb\x9dK*' b'\x97\xea\xf8\x1e\xc3\xd49K\x18\x81\xc3\x1a"' b'\xdc\x00U\xc4u"X\xe7xy\xa5\xb2\x0e\xbc\xd6+-' b'\x80\x03\xef\xc2\xc4\x9bU' ) }, }, # Exact file name depends on your operating system. crypt_shared_lib_path="/path/to/mongo_crypt_v1.so", crypt_shared_lib_required=True, ) }, }, } ``key_vault_namespace`` specifies where to store the data encryption keys. The database name of the key vault must be the same as in ``"NAME"``. The vault's collection name can be whatever you wish, but by convention, it's often ``__keyVault``. .. admonition:: Why is a second connection recommended? Connections that use ``AutoEncryptionOpts`` have some :ref:`query limitations `, regardless of whether or not a model is encrypted. Creating two separate entries in :setting:`DATABASES` avoids imposing these query limitations on your non-encrypted models. .. _qe-configuring-database-routers-setting: Configuring the ``DATABASE_ROUTERS`` setting ============================================ Similar to configuring the :ref:`DATABASE_ROUTERS ` setting for :doc:`embedded models `, Queryable Encryption requires a :setting:`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): # Create myapp's models only in the encrypted database. if app_label == "myapp": return db == "encrypted" # Don't create collections for other apps in the encrypted db. if db == "encrypted": return False return None def db_for_read(self, model, **hints): # All reads and writes for myapp's models go to the encrypted db. 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 :setting:`django:DATABASE_ROUTERS` setting:: # settings.py DATABASE_ROUTERS = [ "django_mongodb_backend.routers.MongoRouter", "myapp.routers.EncryptedRouter", ] Encrypted fields ================ Now you can start using encrypted fields in your Django models. :doc:`Encrypted fields ` may be used to protect sensitive data like social security numbers, credit card information, or personal health information. Import the encrypted fields from ``django_mongodb_backend.fields``, and use them to define your models as usual. Here are models based on the `Python Queryable Encryption Tutorial `_:: # myapp/models.py from django.db import models from django_mongodb_backend.models import EmbeddedModel from django_mongodb_backend.fields import ( EmbeddedModelField, EncryptedCharField, EncryptedEmbeddedModelField, ) class Patient(models.Model): name = models.CharField(max_length=255) patient_id = models.BigIntegerField() patient_record = EmbeddedModelField("PatientRecord") def __str__(self): return f"{self.name} ({self.patient_id})" class PatientRecord(EmbeddedModel): ssn = EncryptedCharField(max_length=11, queries={"queryType": "equality"}) billing = EncryptedEmbeddedModelField("Billing") bill_amount = models.DecimalField(max_digits=10, decimal_places=2) class Billing(EmbeddedModel): cc_type = models.CharField(max_length=50) cc_number = models.CharField(max_length=20) .. _qe-migrations: Migrations ========== Once you have defined your models, create a migration as usual: .. code-block:: console $ python manage.py makemigrations Then run the migrations on the encrypted database: .. code-block:: console $ python manage.py migrate --database encrypted .. warning:: Be aware that MongoDB does not allow adding encrypted fields to existing collections, nor can you change the definition of an encrypted field, for example, to make it queryable. If you wish to add or change an encrypted field, you must create a new collection. Creating encrypted data ======================= Now create and manipulate instances of the data just like any other Django model data. The data is automatically encrypted and decrypted, ensuring that :ref:`sensitive data is stored securely in the database `. Here's an example of creating a new ``Patient`` instance: .. code-block:: pycon >>> from myapp.models import Patient, PatientRecord, Billing >>> billing = Billing(cc_type="Visa", cc_number="4111111111111111") >>> record = PatientRecord(ssn="123-45-6789", billing=billing, bill_amount=250.75) >>> patient = Patient(name="John Doe", patient_id=1001, patient_record=record) >>> patient.save() .. _querying-encrypted-fields: Querying encrypted fields ========================= In order to query encrypted fields, you must include the :ref:`queries ` argument. For example, notice ``PatientRecord``\'s ``ssn`` field:: class PatientRecord(EmbeddedModel): ssn = EncryptedCharField(max_length=11, queries={"queryType": "equality"}) You can perform a equality query just like you would on a non-encrypted field: .. code-block:: pycon >>> patient = Patient.objects.get(patient_record__ssn="123-45-6789") >>> patient.name 'John Doe' See :ref:`encrypted-fields-queries` for more details. .. _configuring-kms: Configuring a Key Management Service (KMS) ========================================== To use Queryable Encryption, you must configure a Key Management Service (KMS) to store and manage the encryption keys used to encrypt and decrypt data. A local KMS provider with a hardcoded key is suitable for development and testing, but in a production environment, you should securely store and manage encryption keys using a :ref:`KMS Provider `. There are two primary configuration points: #. The ``kms_providers`` parameter of :class:`~pymongo.encryption_options.AutoEncryptionOpts`. See the ``kms_providers`` parameter in :class:`~pymongo.encryption_options.AutoEncryptionOpts` for the available providers (``aws``, ``azure``, ``gcp``, etc.) as well as the provider options). #. The :setting:`KMS_CREDENTIALS ` inner option of :setting:`DATABASES`. The keys for each provider are documented under the ``master_key`` parameter of :meth:`~pymongo.encryption.ClientEncryption.create_data_key`. Here's an example of KMS configuration with ``aws``:: 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": "...", # Amazon Resource Name "region": "...", # AWS region }, }, }, } .. _qe-configuring-encrypted-fields-map: Configuring the ``encrypted_fields_map`` option =============================================== Encryption keys are created when you :ref:`run migrations for models that have encrypted fields `. To see the encrypted fields map for your models (which includes the encryption key IDs), run the :djadmin:`showencryptedfieldsmap` command:: $ python manage.py showencryptedfieldsmap --database encrypted .. admonition:: Didn't work? If you get the error ``Unknown command: 'showencryptedfieldsmap'``, ensure ``"django_mongodb_backend"`` is in your :setting:`INSTALLED_APPS` setting. It's recommended to include this map in your production settings to protect against a malicious server advertising a false encrypted fields map:: 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" } }, ] }}""" ), ), }, }, } Do not include this in your development and testing settings since the data encryption keys will be different from those in your production database. Typical deployment workflow =========================== A typical development and deployment workflow for a project that uses Queryable Encryption could be something like this: #. Develop and test your project locally. #. Deploy your project and run :djadmin:`migrate` to create the encrypted collections and data encryption keys. #. Run :djadmin:`showencryptedfieldsmap` in production and :ref:`use the output ` to set ``encrypted_fields_map`` in your production settings.