summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authortschilling <schillingt@better-simple.com>2021-12-13 21:47:03 -0600
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-01 11:12:24 +0100
commit0dcd549bbe36c060f536ec270d34d9e7d4b8e6c7 (patch)
tree8b350cec1ab50d21cf6e3afd03f3db5f02fb1679 /docs
parentba4a6880d1783190de4081bd456d934beb45cb19 (diff)
Fixed #30360 -- Added support for secret key rotation.
Thanks Florian Apolloner for the implementation idea. Co-authored-by: Andreas Pelme <andreas@pelme.se> Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es> Co-authored-by: Vuyisile Ndlovu <terrameijar@gmail.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/howto/deployment/checklist.txt16
-rw-r--r--docs/ref/checks.txt10
-rw-r--r--docs/ref/settings.txt39
-rw-r--r--docs/releases/4.1.txt3
-rw-r--r--docs/topics/auth/default.txt5
-rw-r--r--docs/topics/http/sessions.txt29
-rw-r--r--docs/topics/security.txt3
-rw-r--r--docs/topics/signing.txt28
8 files changed, 106 insertions, 27 deletions
diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt
index 929f19dbfc..45ca2be30e 100644
--- a/docs/howto/deployment/checklist.txt
+++ b/docs/howto/deployment/checklist.txt
@@ -59,6 +59,22 @@ or from a file::
with open('/etc/secret_key.txt') as f:
SECRET_KEY = f.read().strip()
+If rotating secret keys, you may use :setting:`SECRET_KEY_FALLBACKS`::
+
+ import os
+ SECRET_KEY = os.environ['CURRENT_SECRET_KEY']
+ SECRET_KEY_FALLBACKS = [
+ os.environ['OLD_SECRET_KEY'],
+ ]
+
+Ensure that old secret keys are removed from ``SECRET_KEY_FALLBACKS`` in a
+timely manner.
+
+.. versionchanged:: 4.1
+
+ The ``SECRET_KEY_FALLBACKS`` setting was added to support rotating secret
+ keys.
+
:setting:`DEBUG`
----------------
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 6c95b9376d..3228d71f08 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -457,8 +457,8 @@ The following checks are run if you use the :option:`check --deploy` option:
* **security.W009**: Your :setting:`SECRET_KEY` has less than 50 characters,
less than 5 unique characters, or it's prefixed with ``'django-insecure-'``
indicating that it was generated automatically by Django. Please generate a
- long and random ``SECRET_KEY``, otherwise many of Django's security-critical
- features will be vulnerable to attack.
+ long and random value, otherwise many of Django's security-critical features
+ will be vulnerable to attack.
* **security.W010**: You have :mod:`django.contrib.sessions` in your
:setting:`INSTALLED_APPS` but you have not set
:setting:`SESSION_COOKIE_SECURE` to ``True``. Using a secure-only session
@@ -511,6 +511,12 @@ The following checks are run if you use the :option:`check --deploy` option:
to an invalid value.
* **security.E024**: You have set the
:setting:`SECURE_CROSS_ORIGIN_OPENER_POLICY` setting to an invalid value.
+* **security.W025**: Your
+ :setting:`SECRET_KEY_FALLBACKS[n] <SECRET_KEY_FALLBACKS>` has less than 50
+ characters, less than 5 unique characters, or it's prefixed with
+ ``'django-insecure-'`` indicating that it was generated automatically by
+ Django. Please generate a long and random value, otherwise many of Django's
+ security-critical features will be vulnerable to attack.
The following checks verify that your security-related settings are correctly
configured:
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index e65f92b4ff..dac29755d0 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2291,9 +2291,11 @@ The secret key is used for:
* Any usage of :doc:`cryptographic signing </topics/signing>`, unless a
different key is provided.
-If you rotate your secret key, all of the above will be invalidated.
-Secret keys are not used for passwords of users and key rotation will not
-affect them.
+When a secret key is no longer set as :setting:`SECRET_KEY` or contained within
+:setting:`SECRET_KEY_FALLBACKS` all of the above will be invalidated. When
+rotating your secret key, you should move the old key to
+:setting:`SECRET_KEY_FALLBACKS` temporarily. Secret keys are not used for
+passwords of users and key rotation will not affect them.
.. note::
@@ -2301,6 +2303,36 @@ affect them.
startproject <startproject>` creates a unique ``SECRET_KEY`` for
convenience.
+.. setting:: SECRET_KEY_FALLBACKS
+
+``SECRET_KEY_FALLBACKS``
+------------------------
+
+.. versionadded:: 4.1
+
+Default: ``[]``
+
+A list of fallback secret keys for a particular Django installation. These are
+used to allow rotation of the ``SECRET_KEY``.
+
+In order to rotate your secret keys, set a new ``SECRET_KEY`` and move the
+previous value to the beginning of ``SECRET_KEY_FALLBACKS``. Then remove the
+old values from the end of the ``SECRET_KEY_FALLBACKS`` when you are ready to
+expire the sessions, password reset tokens, and so on, that make use of them.
+
+.. note::
+
+ Signing operations are computationally expensive. Having multiple old key
+ values in ``SECRET_KEY_FALLBACKS`` adds additional overhead to all checks
+ that don't match an earlier key.
+
+ As such, fallback values should be removed after an appropriate period,
+ allowing for key rotation.
+
+Uses of the secret key values shouldn't assume that they are text or bytes.
+Every use should go through :func:`~django.utils.encoding.force_str` or
+:func:`~django.utils.encoding.force_bytes` to convert it to the desired type.
+
.. setting:: SECURE_CONTENT_TYPE_NOSNIFF
``SECURE_CONTENT_TYPE_NOSNIFF``
@@ -3725,6 +3757,7 @@ Security
* :setting:`CSRF_USE_SESSIONS`
* :setting:`SECRET_KEY`
+* :setting:`SECRET_KEY_FALLBACKS`
* :setting:`X_FRAME_OPTIONS`
Serialization
diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt
index 6633262255..02d51ed2a5 100644
--- a/docs/releases/4.1.txt
+++ b/docs/releases/4.1.txt
@@ -254,7 +254,8 @@ Requests and Responses
Security
~~~~~~~~
-* ...
+* The new :setting:`SECRET_KEY_FALLBACKS` setting allows providing a list of
+ values for secret key rotation.
Serialization
~~~~~~~~~~~~~
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index d099778836..bef1486cdd 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -912,8 +912,9 @@ function.
Since
:meth:`~django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash()`
- is based on :setting:`SECRET_KEY`, updating your site to use a new secret
- will invalidate all existing sessions.
+ is based on :setting:`SECRET_KEY`, secret key values must be
+ rotated to avoid invalidating existing sessions when updating your site to
+ use a new secret. See :setting:`SECRET_KEY_FALLBACKS` for details.
.. _built-in-auth-views:
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index 20502208a8..2f9f29b84b 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -123,13 +123,15 @@ and the :setting:`SECRET_KEY` setting.
.. warning::
- **If the SECRET_KEY is not kept secret and you are using the**
- ``django.contrib.sessions.serializers.PickleSerializer``, **this can
- lead to arbitrary remote code execution.**
+ **If the ``SECRET_KEY`` or ``SECRET_KEY_FALLBACKS`` are not kept secret and
+ you are using the**
+ ``django.contrib.sessions.serializers.PickleSerializer``, **this can lead
+ to arbitrary remote code execution.**
- An attacker in possession of the :setting:`SECRET_KEY` can not only
- generate falsified session data, which your site will trust, but also
- remotely execute arbitrary code, as the data is serialized using pickle.
+ An attacker in possession of the :setting:`SECRET_KEY` or
+ :setting:`SECRET_KEY_FALLBACKS` can not only generate falsified session
+ data, which your site will trust, but also remotely execute arbitrary code,
+ as the data is serialized using pickle.
If you use cookie-based sessions, pay extra care that your secret key is
always kept completely secret, for any system which might be remotely
@@ -323,11 +325,12 @@ cookie backend*.
For example, here's an attack scenario if you use :mod:`pickle` to serialize
session data. If you're using the :ref:`signed cookie session backend
-<cookie-session-backend>` and :setting:`SECRET_KEY` is known by an attacker
-(there isn't an inherent vulnerability in Django that would cause it to leak),
-the attacker could insert a string into their session which, when unpickled,
-executes arbitrary code on the server. The technique for doing so is simple and
-easily available on the internet. Although the cookie session storage signs the
+<cookie-session-backend>` and :setting:`SECRET_KEY` (or any key of
+:setting:`SECRET_KEY_FALLBACKS`) is known by an attacker (there isn't an
+inherent vulnerability in Django that would cause it to leak), the attacker
+could insert a string into their session which, when unpickled, executes
+arbitrary code on the server. The technique for doing so is simple and easily
+available on the internet. Although the cookie session storage signs the
cookie-stored data to prevent tampering, a :setting:`SECRET_KEY` leak
immediately escalates to a remote code execution vulnerability.
@@ -359,8 +362,8 @@ Bundled serializers
.. class:: serializers.PickleSerializer
Supports arbitrary Python objects, but, as described above, can lead to a
- remote code execution vulnerability if :setting:`SECRET_KEY` becomes known
- by an attacker.
+ remote code execution vulnerability if :setting:`SECRET_KEY` or any key of
+ :setting:`SECRET_KEY_FALLBACKS` becomes known by an attacker.
.. deprecated:: 4.1
diff --git a/docs/topics/security.txt b/docs/topics/security.txt
index ee7c7f542b..abc8d5d6ac 100644
--- a/docs/topics/security.txt
+++ b/docs/topics/security.txt
@@ -296,7 +296,8 @@ security protection of the web server, operating system and other components.
* Django does not throttle requests to authenticate users. To protect against
brute-force attacks against the authentication system, you may consider
deploying a Django plugin or web server module to throttle these requests.
-* Keep your :setting:`SECRET_KEY` a secret.
+* Keep your :setting:`SECRET_KEY`, and :setting:`SECRET_KEY_FALLBACKS` if in
+ use, secret.
* It is a good idea to limit the accessibility of your caching system and
database using a firewall.
* Take a look at the Open Web Application Security Project (OWASP) `Top 10
diff --git a/docs/topics/signing.txt b/docs/topics/signing.txt
index 8791697b85..9ae5ca3351 100644
--- a/docs/topics/signing.txt
+++ b/docs/topics/signing.txt
@@ -25,8 +25,8 @@ You may also find signing useful for the following:
protected resource, for example a downloadable file that a user has
paid for.
-Protecting the ``SECRET_KEY``
-=============================
+Protecting ``SECRET_KEY`` and ``SECRET_KEY_FALLBACKS``
+======================================================
When you create a new Django project using :djadmin:`startproject`, the
``settings.py`` file is generated automatically and gets a random
@@ -34,6 +34,14 @@ When you create a new Django project using :djadmin:`startproject`, the
data -- it is vital you keep this secure, or attackers could use it to
generate their own signed values.
+:setting:`SECRET_KEY_FALLBACKS` can be used to rotate secret keys. The
+values will not be used to sign data, but if specified, they will be used to
+validate signed data and must be kept secure.
+
+.. versionchanged:: 4.1
+
+ The ``SECRET_KEY_FALLBACKS`` setting was added.
+
Using the low-level API
=======================
@@ -93,13 +101,19 @@ generate signatures. You can use a different secret by passing it to the
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
-.. class:: Signer(key=None, sep=':', salt=None, algorithm=None)
+.. class:: Signer(key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)
Returns a signer which uses ``key`` to generate signatures and ``sep`` to
separate values. ``sep`` cannot be in the :rfc:`URL safe base64 alphabet
<4648#section-5>`. This alphabet contains alphanumeric characters, hyphens,
and underscores. ``algorithm`` must be an algorithm supported by
- :py:mod:`hashlib`, it defaults to ``'sha256'``.
+ :py:mod:`hashlib`, it defaults to ``'sha256'``. ``fallback_keys`` is a list
+ of additional values used to validate signed data, defaults to
+ :setting:`SECRET_KEY_FALLBACKS`.
+
+ .. versionchanged:: 4.1
+
+ The ``fallback_keys`` argument was added.
Using the ``salt`` argument
---------------------------
@@ -221,7 +235,11 @@ and tuples) if you pass in a tuple, you will get a list from
Returns URL-safe, signed base64 compressed JSON string. Serialized object
is signed using :class:`~TimestampSigner`.
-.. function:: loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None)
+.. function:: loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)
Reverse of ``dumps()``, raises ``BadSignature`` if signature fails.
Checks ``max_age`` (in seconds) if given.
+
+ .. versionchanged:: 4.1
+
+ The ``fallback_keys`` argument was added.