diff options
| author | tschilling <schillingt@better-simple.com> | 2021-12-13 21:47:03 -0600 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-01 11:12:24 +0100 |
| commit | 0dcd549bbe36c060f536ec270d34d9e7d4b8e6c7 (patch) | |
| tree | 8b350cec1ab50d21cf6e3afd03f3db5f02fb1679 /docs | |
| parent | ba4a6880d1783190de4081bd456d934beb45cb19 (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.txt | 16 | ||||
| -rw-r--r-- | docs/ref/checks.txt | 10 | ||||
| -rw-r--r-- | docs/ref/settings.txt | 39 | ||||
| -rw-r--r-- | docs/releases/4.1.txt | 3 | ||||
| -rw-r--r-- | docs/topics/auth/default.txt | 5 | ||||
| -rw-r--r-- | docs/topics/http/sessions.txt | 29 | ||||
| -rw-r--r-- | docs/topics/security.txt | 3 | ||||
| -rw-r--r-- | docs/topics/signing.txt | 28 |
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. |
