Distributing Django projects with unique SECRET_KEYs
I have a Django project that I'd like to distribute on a public repository like bitbucket or github. I'd like it to be as easy to install as possible, so I'm including the full project, not just the pluggable apps. This means that the settings.py
file will be included as well.
How can I avoid the problem of settings.SECRET_KEY
being the same for every installation?
Is the only simple solution to have the user manually modify settings.py
?
Should I store the key in the default database and have settings开发者_运维问答.py
initialize it if it doesn't exist? That would solve the problem, but I'm wondering if there is already a standard way of doing this.
Thanks!
To add to what Carles Barrobés said, you can generate a new key using the method that Django uses in startproject
:
from django.utils.crypto import get_random_string
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
get_random_string(50, chars)
For Django 1.10 and above, the above code snippet is nicely wrapped up in a function.
from django.core.management.utils import get_random_secret_key
get_random_secret_key()
Link to GitHub repo
I'd go about it this way:
Have the secret key in a separate file "secret_key.py". This file does not exist for a pristine installation. In your settings.py include something like:
try:
from .secret_key import SECRET_KEY
except ImportError:
SETTINGS_DIR = os.path.abspath(os.path.dirname(__file__))
generate_secret_key(os.path.join(SETTINGS_DIR, 'secret_key.py'))
from .secret_key import SECRET_KEY
The function generate_secret_key(filename)
that you will write generates a file called filename
(which, as we call it, will be secret_key.py
in the same dir as settings.py
) with the contents:
SECRET_KEY = '....random string....'
Where random string is the generated key based on a random number.
For key generation you can use Umang's suggestion https://stackoverflow.com/a/16630719/166761.
Open a Django shell with python manage.py shell
and do the following to create a secure random secret key in Django 2.1:
>>> from django.core.management.utils import get_random_secret_key
>>> get_random_secret_key()
'[GENERATED KEY]'
>>>
Note: The >>>
represents the shell prompt, and should not be typed.
Edit: Some answers here suggest automatically generating a file with a secret key in it from within the Django settings file itself. This is unsuitable for a production environment for a couple reasons. First of all, future deployments to new machines will create mismatching keys. Secondly, you'll need to take extra care to ensure there is no read access to that file from other programs or users. For these reasons it is generally advisable and common practice to store secrets on production machines as environment variables.
Generally speaking, you can divide Django configuration into things that are app-specific and things that are server-specific. This falls into the latter category.
There are a number of ways you can tackle the problem of server-specific configuration, it is discussed more in this question.
For this particular instance, using the approach I outline in my answer to the other question, I'd put a placeholder in settings_local.py.sample
for distribution, and during installation, I'd copy that over to settings_local.py
and edit to suit.
Carles Barrobés made an excellent answer but it is incomplete, here is my version for python 3 with the missing function to work.
from django.core.management.utils import get_random_secret_key
def generate_secret_key (filepath):
secret_file = open(filepath, "w")
secret = "SECRET_KEY= " + "\""+ get_random_secret_key() + "\"" + "\n"
secret_file.write(secret)
secret_file.close()
try:
from .secret_key import SECRET_KEY
except ModuleNotFoundError:
SETTINGS_DIR = os.path.abspath(os.path.dirname(__file__))
generate_secret_key(os.path.join(SETTINGS_DIR, 'secret_key.py'))
from .secret_key import SECRET_KEY
Take notice that I changed the ImportError
for ModuleNotFoundError
and creates the python file secret_key.py
to gather the SECRET_KEY like a variable instead to parse a txt file.
I would solve the problem like this:
- Provide a dummy secret key like:
I_AM_A_DUMMY_KEY_CHANGE_ME
- Create a manage command to generate a new one:
./manage.py gen_secret_key
- In the documentation, STRONGLY advise users to run the command as soon as possible
In my code I have three levels of settings file inspired by Two Scoops of Django, so a middle one goes like this where BASE_PRIVATE_DIR is set up in the base template. In my case this is from the django directory ../../mysite_private but somewhere ouside the normal files under the application git.:
from .base import *
ALLOWED_HOSTS = ['staging.django.site']
#Allow local override which is per deployment instance. There should probably then be
# an instance git for version control of the production data
try:
import sys
private_path = BASE_PRIVATE_DIR.child('production')
sys.path.append(private_path)
from private_settings import *
except ImportError:
print(" No production overide private_settings.py found. This is probably an error = {}".format(private_path))
# If it doesnt' exist that is fine and just use system and environment defaults
If you create a new project using template, like django-admin.py startproject --template=path_to_template project_name
just put {{ secret_key }}
into your project template settings file (e.g. settings.py) like SECRET_KEY = '{{ secret_key }}'
and Django will generate it for you.
In this solution I use django-dotenv, which is one of the dependencies of my project, as listed in requirements.txt
like django-dotenv==1.4.1
. The advantage of this approach is you have a different .env
file for each environment where the application is installed.
Create the file utils.py
in the same directory of settings.py
with the following content:
from django.utils.crypto import get_random_string
def generate_secret_key(env_file_name):
env_file = open(env_file_name, "w+")
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
generated_secret_key = get_random_string(50, chars)
env_file.write("SECRET_KEY = '{}'\n".format(generated_secret_key))
env_file.close()
Then modify the settings.py
file as follows:
import dotenv
from [project-folder-name] import utils
...
try:
SECRET_KEY = os.environ['SECRET_KEY']
except KeyError:
path_env = os.path.join(BASE_DIR, '.env')
utils.generate_secret_key(path_env)
dotenv.read_dotenv(path_env)
SECRET_KEY = os.environ['SECRET_KEY']
For those who don't use django-dotenv
, all you have to do it to add it as a dependency and change the manage.py
to load it at startup:
import dotenv
if __name__ == "__main__":
dotenv.read_dotenv()
I found this block of code on pypi.org which almost works like Umang's answer.
Right in your project directory run
python manage.py generate_secret_key [--replace] [secretkey.txt]
This will generate a new file secretkey.txt
containing a random Django secret key. In your production settings file go and replace the secret key with the generated key.
Or in order to avoid hard coding the secret key. Add the following code snippet so that when you always run the program a new secret key will generate an updated key for you.
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Use a separate file for the secret key
with open(os.path.join(BASE_DIR, 'secretkey.txt')) as f:
SECRET_KEY = f.read().strip()
OR
# Use a separate file for the secret key
with open('/path/to/the/secretkey.txt') as f:
SECRET_KEY = f.read().strip()
精彩评论