Skip to content

Commit 8659109

Browse files
committed
update documentation for new encryption scheme
1 parent 3e85ba4 commit 8659109

4 files changed

Lines changed: 62 additions & 35 deletions

File tree

docs/customization/encrypted_model_fields.rst

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ Encrypted Model Fields
22
======================
33

44
If your ``custom_code`` or reusable app contains a model field storing
5-
user-specific sensitive data, you may want to encrypt that data at rest.
5+
user-specific sensitive data, TOM Toolkit provides a way to encrypt
6+
that data in in the database ("at rest").
67

78
Examples of user-specific sensitive data include passwords or API keys
89
for external services that your TOM uses on the user's behalf. TOM
@@ -16,18 +17,15 @@ How encryption works
1617
--------------------
1718

1819
Encrypted fields are protected by a single Fernet cipher derived from
19-
``settings.SECRET_KEY`` using HKDF (RFC 5869) with a domain-separator
20-
label. There is no per-user key material and no additional environment
21-
variable to manage. For the operator-side concerns (rotating
20+
``settings.SECRET_KEY``. (For TOM administrator concerns (rotating
2221
``SECRET_KEY`` without losing data, etc.) see
23-
:doc:`/deployment/encryption`.
22+
:doc:`/deployment/encryption`).
2423

2524
.. note::
2625

2726
Encryption protects data from passive database exposure. It does NOT
28-
protect against a server administrator with access to ``SECRET_KEY``
29-
— by design, since the same admin needs to be able to run the
30-
``rotate_encryption_key`` command. If you need user-level isolation
27+
protect against a server administrator with access to the
28+
``settings.SECRET_KEY``. If you need user-level isolation
3129
from administrators, the toolkit's current scheme is not sufficient.
3230

3331
Adding an encrypted field to a model
@@ -45,19 +43,21 @@ encryption on write and decryption on read.
4543
4644
4745
class MyAppProfile(models.Model):
48-
user = models.OneToOneField(settings.AUTH_USER_MODEL,
49-
on_delete=models.CASCADE)
46+
# you probably have a user OneToOneField here
47+
48+
# this is the encryption part:
5049
_api_key_encrypted = models.BinaryField(null=True, blank=True) # ciphertext (private)
5150
api_key = EncryptedProperty('_api_key_encrypted') # descriptor (public)
5251
5352
By convention, the ``BinaryField``'s name starts with an underscore —
54-
its only consumer is the :class:`EncryptedProperty` descriptor; plugin
55-
code should never read or write it directly.
53+
it is only referenced by the :class:`EncryptedProperty` descriptor; never
54+
read or write the ``BinaryField`` directly.
5655

5756
Reading and writing the field
5857
-----------------------------
5958

60-
Plain Python attribute access on the descriptor handles everything:
59+
Access the encrypted field just like a regular Python attribute.
60+
The ``EncryptedProperty`` descriptor handles everything:
6161

6262
.. code-block:: python
6363
@@ -67,23 +67,27 @@ Plain Python attribute access on the descriptor handles everything:
6767
# later, possibly in a different process / request:
6868
value = profile.api_key # 'something-secret'
6969
70-
On assignment, the descriptor calls
70+
71+
Some explanations
72+
-----------------
73+
74+
That (above) is really all you need to know. However, here's what's
75+
going on "under the hood": On assignment, the descriptor calls
7176
:func:`tom_common.encryption.encrypt`, which builds a Fernet cipher
7277
from ``settings.SECRET_KEY`` and encrypts the value, then stores the
7378
ciphertext bytes in the underlying ``BinaryField``. On read, the
7479
descriptor calls :func:`tom_common.encryption.decrypt`, which
75-
transparently honours ``settings.SECRET_KEY_FALLBACKS`` (see
76-
:doc:`/deployment/encryption` for the rotation procedure).
80+
transparently honours ``settings.SECRET_KEY_FALLBACKS``. However,
81+
that's really just for admins to worry about (see
82+
:doc:`/deployment/encryption` for admin/deployment concerns).
7783

7884
An empty string assignment clears the ciphertext (stores ``None`` in
7985
the ``BinaryField``). Reading an unset / empty field yields ``''``,
8086
not ``None`` — so consumers don't need to special-case the empty case.
8187

82-
Some explanations
83-
-----------------
8488

8589
:class:`EncryptedProperty` (`source <https://github.com/TOMToolkit/tom_base/blob/dev/tom_common/models.py>`__)
86-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8791

8892
A *property descriptor* implementing the Python descriptor protocol
8993
(``__get__``, ``__set__``, ``__set_name__``). It handles the details of

docs/deployment/deployment_tips.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
General Deployment Tips
22
-----------------------
33

4-
When it comes to deploying your tom for general use, there are a few
5-
things you might want to consider.
4+
Your TOM is a Django project. First and foremost, you should be following
5+
`Django's deployment checklist <https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/#deployment-checklist>`_.
6+
Subsequently, when it comes to deploying your TOM for general use, there are a
7+
few things you might want to consider.
68

79
Choosing a database
810
~~~~~~~~~~~~~~~~~~~
@@ -71,4 +73,4 @@ If you provide the path to a file that does not exist, TOM Toolkit will still
7173
serve the default ``robots.txt`` file and log a warning message to that effect.
7274

7375
Additional background on the ``robots.txt`` file can be found
74-
`here <https://en.wikipedia.org/wiki/Robots_exclusion_standard>`_.
76+
`here <https://en.wikipedia.org/wiki/Robots_exclusion_standard>`_.

docs/deployment/encryption.rst

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
1-
Encryption at Rest and the SECRET_KEY
1+
Encryption and the SECRET_KEY
22
=====================================
33

4-
TOM Toolkit encrypts sensitive user data at rest (API keys, observatory
4+
This section is for TOM administrators and describes the relationship
5+
between the ``settings.SECRET_KEY`` and the way TOMToolkit encrypts
6+
sensitive user data. If you are a TOM developer looking for how to
7+
create an encrypted database field, your documentation is here:
8+
:doc:`/customization/encrypted_model_fields`
9+
10+
------
11+
12+
TOM Toolkit encrypts sensitive user data (API keys, observatory
513
credentials, anything declared with :class:`EncryptedProperty`) using a
614
single Fernet cipher derived from Django's ``settings.SECRET_KEY``.
7-
There is no additional environment variable to manage — your
8-
``SECRET_KEY`` is both your signing key (cookies, password-reset
9-
tokens, etc.) and the source of your encryption key.
10-
11-
The derivation uses HKDF (RFC 5869) with a domain-separator label, so
12-
the encryption key is cryptographically independent of the way Django
13-
uses ``SECRET_KEY`` for HMAC signing. See
14-
:mod:`tom_common.encryption` for the implementation; for the plugin-
15-
developer-facing API see :doc:`/customization/encrypted_model_fields`.
15+
That means when an encrypted field is written to or read from the database,
16+
a cipher is created. The cipher can encrypt unencrypted plaintext (in) and decrypt
17+
encrypted ciphertext (out). Cipher creation requires an encryption key.
18+
TOMToolkit creates an encryption key that is based upon, but not identical
19+
to, Django's ``settings.SECRET_KEY``. That's how the ``SECRET_KEY`` is related
20+
to TOMToolkit's encryption.
1621

1722
Treat ``SECRET_KEY`` like an encryption key
1823
-------------------------------------------
1924

2025
If you lose ``SECRET_KEY`` (and any active
2126
``SECRET_KEY_FALLBACKS`` entries), every encrypted field becomes
2227
unrecoverable. Keep ``SECRET_KEY`` secret, never commit it, and back
23-
it up through whatever channel your other production secrets travel.
28+
it up through whatever channel your other production secrets use.
2429

2530
The standard Django guidance applies — see the
2631
`Django deployment checklist
@@ -35,9 +40,16 @@ and the
3540
below will leave every previously-encrypted field unreadable. Follow
3641
the procedure exactly — don't just edit ``SECRET_KEY`` in your env.
3742

43+
Should
44+
3845
Graceful ``SECRET_KEY`` rotation
3946
--------------------------------
4047

48+
Because TOMToolkit's encryption scheme depends on the value of ``settings.SECRET_KEY``,
49+
if you need to change your ``SECRET_KEY``, we must decrypt the encrypted data
50+
with a cipher derived from the old ``SECRET_KEY`` and re-encrypt it with a cipher derived
51+
from the new ``SECRET_KEY``. The following proceed explains the process in full.
52+
4153
We use Django's built-in
4254
`SECRET_KEY_FALLBACKS <https://docs.djangoproject.com/en/stable/ref/settings/#secret-key-fallbacks>`_
4355
mechanism to rotate keys without an outage and without data loss. The
@@ -112,6 +124,15 @@ If you lose ``SECRET_KEY`` and have no backup:
112124
Treat ``SECRET_KEY`` backup with the same seriousness as your database
113125
backup.
114126

127+
Key Derivation Function Implementation Details
128+
----------------------------------------------
129+
The derivation uses `HKDF <https://en.wikipedia.org/wiki/HKDF>`_
130+
(RFC 5869) with a domain-separator label, so the encryption key is
131+
cryptographically independent of the way Django
132+
uses ``SECRET_KEY`` for HMAC signing. See
133+
:mod:`tom_common.encryption` for the implementation.
134+
135+
115136
See also
116137
--------
117138

docs/deployment/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ accessible by you and your colleagues.
1919

2020
:doc:`Using Amazon S3 to Store Data for a TOM <amazons3>` - Enable storing data on the cloud storage service Amazon S3 instead of your local disk.
2121

22-
:doc:`Configuring the Master Encryption Key <encryption>` - How to set ``TOMTOOLKIT_DEK_ENCRYPTION_KEY`` across deploy environments and migrate off the public default.
22+
:doc:`Encryption and the SECRET_KEY <encryption>` - How Django's ``settings.SECRET_KEY`` relates to TOM Toolkit encryption.

0 commit comments

Comments
 (0)