Andreas Pelme
2018-11-10 10:12:25 UTC
Hi,
settings.SECRET_KEY can be used for sessions, password resets, form wizards and
other cryptographic signatures via the signing APIs. Changing SECRET_KEY means
that all of those will be invalidated and the users will be affected in weird
ways without really knowing what happened. (Why am I logged out? Where did my
form submission go? Why does not this password reset link work?). This is
desirable in case the key is compromised and swift action must be taken.
There are other situations when it would be nice to change the SECRET_KEY when
this sudden invalidation is not desirable:
- When someone leaves a project/company that had access to the production
system. After SSH keys/login credentials is revoked the developer could
potentially have a copy of the secret key. It is essentially a backdoor with
full remote access. It would be wise to rotate the key in those cases.
- Periodic and automatic rotations of keys to make it less useful in the
future.
The current situation of a single SECRET_KEY makes key rotation impractical. If
you run a busy site with active users 24/7, there is never a nice time to
change the SECRET_KEY.
A solution for this problem would be sign new secrets with a new key while
still allow signatures made with the old key to be considered valid at the same
time. Changing keys and having a couple of hours of overlap where signatures
from both keys are accepted would mitigate most of the user facing problems
with invalidating sessions, password reset links and form wizard progress.
You could do this today by implementing your own session backend, message
storage backend and password reset token generator but that is cumbersome and
does not work across reusable apps that directly use low level Django signing
APIs unless they too provide hooks to provide your own secret.
I propose a pluggable project wide secret key backend
(settings.SECRET_KEY_BACKEND maybe?) with an API something like:
class SecretKeyBackend:
def get_signing_key(self): …
def get_verification_keys(self): ...
The default (and backward compatible) backend would then be implemented as
something like:
class SecretKeySettingsBackend:
def get_signing_key(self):
return settings.SECRET_KEY
def get_verification_keys(self):
return [settings.SECRET_KEY]
django.core.signing.Signer.{sign,unsign} would need to be updated to use this
backend instead of directly using settings.SECRET_KEY.
That would solve the problem project wide and work across any third party
application that uses django.core.signing directly.
This would open the door for third party secrets backend packages that
retrieves keys from systems such as Hashicorp Vault, AWS Secrets Manager,
Google Cloud KMS, Docker Secrets etc.
Having a method that retrieves the key would allow changes to secret key during
run time instead of relying on a hard coded setting would allow the key to
change without restarting the server process.
Would something like this be worth pursuing? Could it be designed in som other
way? I could not find any previous discussion/tickets on this and thought it
would be a good idea to discuss it here before opening a ticket or making an
attempt at a PR. :)
Cheers,
Andreas
settings.SECRET_KEY can be used for sessions, password resets, form wizards and
other cryptographic signatures via the signing APIs. Changing SECRET_KEY means
that all of those will be invalidated and the users will be affected in weird
ways without really knowing what happened. (Why am I logged out? Where did my
form submission go? Why does not this password reset link work?). This is
desirable in case the key is compromised and swift action must be taken.
There are other situations when it would be nice to change the SECRET_KEY when
this sudden invalidation is not desirable:
- When someone leaves a project/company that had access to the production
system. After SSH keys/login credentials is revoked the developer could
potentially have a copy of the secret key. It is essentially a backdoor with
full remote access. It would be wise to rotate the key in those cases.
- Periodic and automatic rotations of keys to make it less useful in the
future.
The current situation of a single SECRET_KEY makes key rotation impractical. If
you run a busy site with active users 24/7, there is never a nice time to
change the SECRET_KEY.
A solution for this problem would be sign new secrets with a new key while
still allow signatures made with the old key to be considered valid at the same
time. Changing keys and having a couple of hours of overlap where signatures
from both keys are accepted would mitigate most of the user facing problems
with invalidating sessions, password reset links and form wizard progress.
You could do this today by implementing your own session backend, message
storage backend and password reset token generator but that is cumbersome and
does not work across reusable apps that directly use low level Django signing
APIs unless they too provide hooks to provide your own secret.
I propose a pluggable project wide secret key backend
(settings.SECRET_KEY_BACKEND maybe?) with an API something like:
class SecretKeyBackend:
def get_signing_key(self): …
def get_verification_keys(self): ...
The default (and backward compatible) backend would then be implemented as
something like:
class SecretKeySettingsBackend:
def get_signing_key(self):
return settings.SECRET_KEY
def get_verification_keys(self):
return [settings.SECRET_KEY]
django.core.signing.Signer.{sign,unsign} would need to be updated to use this
backend instead of directly using settings.SECRET_KEY.
That would solve the problem project wide and work across any third party
application that uses django.core.signing directly.
This would open the door for third party secrets backend packages that
retrieves keys from systems such as Hashicorp Vault, AWS Secrets Manager,
Google Cloud KMS, Docker Secrets etc.
Having a method that retrieves the key would allow changes to secret key during
run time instead of relying on a hard coded setting would allow the key to
change without restarting the server process.
Would something like this be worth pursuing? Could it be designed in som other
way? I could not find any previous discussion/tickets on this and thought it
would be a good idea to discuss it here before opening a ticket or making an
attempt at a PR. :)
Cheers,
Andreas
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+***@googlegroups.com.
To post to this group, send email to django-***@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/20D8A2BD-BC9C-4F02-9038-044687165DE9%40pelme.se.
For more options, visit https://groups.google.com/d/optout.
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+***@googlegroups.com.
To post to this group, send email to django-***@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/20D8A2BD-BC9C-4F02-9038-044687165DE9%40pelme.se.
For more options, visit https://groups.google.com/d/optout.