Bobcares

Harden the security of Django – Different methods to do it

PDF Header PDF Footer

Need help to harden the security of Django? We can help you.

Developing a Django application can be quick and clean because its approach is flexible and scalable.

However, when it comes to production deployment, there are several ways to further secure the project.

As part of our Server Management Services, we assist our customers with several Django queries.

Today, let us see how to harden the security of Django.

 

Security of Django

Even though developing a Django application is a quick and clean experience, the production deployment requires several ways to further secure the project.

In order to secure the project, we can restructure it by breaking up the settings. This will allow us to easily set up different configurations based on the environment.

Similarly, leveraging

dotenv
for hiding environment variables or confidential settings will ensure that we do not release any details that may compromise the project.

Implementation of these strategies and features might seem time-consuming. However, developing a practical workflow will allow us to deploy releases of the project without compromising on security or productivity.

In this article, we will leverage a security-oriented workflow for Django development by implementing and configuring environment-based settings,

dotenv
, and Django’s built-in security settings.

All these features complement each other and will result in a version of the Django project that is ready for different approaches we may take to deployment.

In order to begin, our Support Techs suggest having a pre-existing Django project.

 

Harden the security of Django

In this article, we will use the

testsite
project as an example. An existing Django project may have different requirements. Let us discuss in detail how to harden the security of Django.

 

Step 1: Restructure Django’s Settings

Initially, let us rearrange the

settings.py
file into environment-specific configurations.

This arrangement will mean less reconfiguration for different environments; instead, we will use an environment variable to switch between configurations.

We need to create a new directory called settings in the project’s sub-directory:

$ mkdir testsite/testsite/settings

This directory will replace the current

settings.py
configuration file.

Once done, we create three Python files:

$ cd testsite/testsite/settings
$ touch base.py development.py production.py

The

development.py
file contains settings we use during development. The
production.py
contains settings for use on a production server.

We need to keep these separate since the production configuration will use settings that will not work in a development environment.

The

base.py
settings file contains settings that
development.py
and
production.py
will inherit from. This is to reduce redundancy and to help keep the code cleaner.

These Python files will be replacing

settings.py
, so we will now remove
settings.py
to avoid confusing Django.

We rename

settings.py
to
base.py
with the following command:

$ mv ../settings.py base.py

We have now come to the end of creating the outline of the new environment-based settings directory.

 

Step 2: Use python-dotenv

At the moment, Django will fail to recognize the new settings directory or its internal files. So, before we continue, we need to make Django work with

python-dotenv
.

This is a dependency that loads environment variables from a

.env
file. Django looks inside a .env file in the project’s root directory to determine which settings configuration it will use.

Initially, we go to the project’s root directory:

$ cd ../../

Then we install

python-dotenv
:

$ pip install python-dotenv

Now we need to configure Django to use

dotenv
. In order to do this, we will edit,
manage.py
, for development, and
wsgi.py
, for production.

Let us start by editing manage.py:
$ nano manage.py

Add the following code:

import os
import sys
import dotenv

def main():
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘testsite.settings.development’)

if os.getenv(‘DJANGO_SETTINGS_MODULE’):
os.environ[‘DJANGO_SETTINGS_MODULE’] = os.getenv(‘DJANGO_SETTINGS_MODULE’)

try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
“Couldn’t import Django. Are you sure it’s installed and ”
“available on your PYTHONPATH environment variable? Did you ”
“forget to activate a virtual environment?”
) from exc
execute_from_command_line(sys.argv)

if __name__ == ‘__main__’:
main()

dotenv.load_dotenv(
os.path.join(os.path.dirname(__file__), ‘.env’)
)
Save and close manage.py and then open wsgi.py for editing:
$ nano testsite/wsgi.py

Add the following highlighted lines:

import os
import dotenv

from django.core.wsgi import get_wsgi_application

dotenv.load_dotenv(
os.path.join(os.path.dirname(os.path.dirname(__file__)), ‘.env’)
)

os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘testsite.settings.development’)

if os.getenv(‘DJANGO_SETTINGS_MODULE’):
os.environ[‘DJANGO_SETTINGS_MODULE’] = os.getenv(‘DJANGO_SETTINGS_MODULE’)

application = get_wsgi_application()

The code we have added looks for

.env
file.

If the file exists, we instruct Django to use the settings file that .env recommends, otherwise, we use the development configuration by default.

Save and close the file.

Finally, let us create a .env in the project’s root directory:

$ nano .env

Now add in the following line to set the environment to development:

DJANGO_SETTINGS_MODULE=”testsite.settings.development”

Add

.env
to
.gitignore
file, to use this file to contain data such as passwords and API keys that we do not want visible publicly. Every environment the project is running on will have its own
.env
with settings for that specific environment.

Our Support Techs recommend creating a

.env.example
to include in the project. It will help to easily create a new
.env
wherever we need one.

So, by default Django will use

testsite.settings.development
, but if we change DJANGO_SETTINGS_MODULE to
testsite.settings.production
, it will start using the production configuration.

 

Step 3: Create Development and Production Settings

Moving ahead, we will open

base.py
and add the configurations to modify for each environment in separate
development.py
and
production.py
files.

Ensure that the production.py has the production database credentials.

We can determine the settings to configure, based on the environment. Here, we will only cover a common example for production and development settings.

Initially, we will move settings from base.py to development.py. To do that, open development.py:

$ nano testsite/settings/development.py

First, we will import from

base.py
– this file inherits settings from
base.py
. Then we will transfer the settings we want to modify for the development environment:

from .base import *

DEBUG = True

DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.sqlite3’,
‘NAME’: os.path.join(BASE_DIR, ‘db.sqlite3’),
}
}

In this case, the settings specific to development are:

DEBUG
, we need this
True
in development, but not in production; and
DATABASES
, a local database instead of a production database. We are using an SQLite database here for development.

For security purposes, Django’s DEBUG output will never display any settings that may contain the strings: API, KEY, PASS, SECRET, SIGNATURE, or TOKEN.

Next, let us add to production.py:
$ nano testsite/settings/production.py

Production will be similar to

development.py
, but with a different database configuration and
DEBUG
set to
False
:

from .base import *

DEBUG = False

ALLOWED_HOSTS = []

DATABASES = {
‘default’: {
‘ENGINE’: os.environ.get(‘SQL_ENGINE’, ‘django.db.backends.sqlite3’),
‘NAME’: os.environ.get(‘SQL_DATABASE’, os.path.join(BASE_DIR, ‘db.sqlite3’)),
‘USER’: os.environ.get(‘SQL_USER’, ‘user’),
‘PASSWORD’: os.environ.get(‘SQL_PASSWORD’, ‘password’),
‘HOST’: os.environ.get(‘SQL_HOST’, ‘localhost’),
‘PORT’: os.environ.get(‘SQL_PORT’, ”),
}
}

For the example database configuration given, we can use

dotenv
to configure each of the given credentials, with defaults included.

Hence we have configured the project to use different settings based on

DJANGO_SETTINGS_MODULE
in
.env
.

Given the example settings here, when we set the project to use production settings,

DEBUG
becomes
False
,
ALLOWED_HOSTS
is defined, and we start using a different database configured on the server.

 

Step 4: Work with Django’s Security Settings

Django includes security settings ready for us to add to the project. These settings are intended for use when the project is available to the public.

Our Support Engineers does not recommend using any of these settings in the development environment. Hence, we limit these settings to the

production.py
configuration.

These settings are going to enforce the use of HTTPS for various web features, such as session cookies, upgrading HTTP to HTTPS, and so on. Therefore, if the server is not set up with a domain pointing to it, hold off on this section for now.

First, we open production.py:

$ nano production.py

In the file, add the highlighted settings that work for the project, according to the explanations following the code:

from .base import *

DEBUG = False

ALLOWED_HOSTS = [‘your_domain’, ‘www.your_domain’]

DATABASES = {
‘default’: {
‘ENGINE’: os.environ.get(‘SQL_ENGINE’, ‘django.db.backends.sqlite3’),
‘NAME’: os.environ.get(‘SQL_DATABASE’, os.path.join(BASE_DIR, ‘db.sqlite3’)),
‘USER’: os.environ.get(‘SQL_USER’, ‘user’),
‘PASSWORD’: os.environ.get(‘SQL_PASSWORD’, ‘password’),
‘HOST’: os.environ.get(‘SQL_HOST’, ‘localhost’),
‘PORT’: os.environ.get(‘SQL_PORT’, ”),
}
}

SECURE_SSL_REDIRECT = True

SESSION_COOKIE_SECURE = True

CSRF_COOKIE_SECURE = True

SECURE_BROWSER_XSS_FILTER = True
  • SECURE_SSL_REDIRECT

This redirects all HTTP requests to HTTPS. Hence, the project will always try to use an encrypted connection. We need SSL configured on the server for this to work. However, if we have Nginx or Apache configured to do this already, this setting will be redundant.

  • SESSION_COOKIE_SECURE

This tells the browser that cookies can only be handled over HTTPS. Hence, cookies that the project produces for activities, such as logins, will only work over an encrypted connection.

  • CSRF_COOKIE_SECURE

It is the same as SESSION_COOKIE_SECURE but applies to the CSRF token. Django CSRF protection protects against Cross-Site Request Forgery by ensuring that the forms submitted to the project were created by the project and not a third party.

  • SECURE_BROWSER_XSS_FILTER

This sets the X-XSS-Protection: 1; mode=block header on all responses that do not already have it. This ensures third parties cannot inject scripts into the project.

 

Step 5: Use python-dotenv for Secrets

The final section will help us leverage

python-dotenv
. This allows us to hide certain information such as the project’s SECRET_KEY or the admin’s login URL.

This is a great idea if we intend to publish the code on platforms like GitHub or GitLab since these secrets will not be published.

Instead, whenever we initially set up the project on a local environment or a server, we can create a new .env file and define those secret variables.

We must hide SECRET_KEY so we will start working on that in this section.

Initially, open .env file:

$ nano .env

And add the following line:

DJANGO_SETTINGS_MODULE=”django_hardening.settings.development”
SECRET_KEY=”your_secret_key”

Then in base.py:

$ nano testsite/settings/base.py

Let us update the SECRET_KEY variable:

. . .
SECRET_KEY = os.getenv(‘SECRET_KEY’)
. . .

The project will now use the SECRET_KEY located in .env.

Finally, we will hide the admin URL by adding a long string of random characters to it. This will ensure bots cannot brute force the login fields and strangers cannot try guessing the login.

Open .env again:
$ nano .env

Then add a SECRET_ADMIN_URL variable:

DJANGO_SETTINGS_MODULE=”django_hardening.settings.development”
SECRET_KEY=”your_secret_key”
SECRET_ADMIN_URL=”very_secret_url”

Now we will tell Django to hide the admin URL with SECRET_ADMIN_URL:

$ nano /testsite/urls.py

Do not forget to replace

your_secret_key
and
very_secret_url
with our own secret strings.

Python provides a fantastic secrets.py library for generating random strings for these variables.

Edit the admin URL like so:

import os
from django.contrib import admin
from django.urls import path

urlpatterns = [
path(os.getenv(‘SECRET_ADMIN_URL’) + ‘/admin/’, admin.site.urls),
]

We can now find the admin login page at

very_secret_url/admin/ instead of just /admin/
.

[Stuck with the procedures? We’d be happy to assist you]

 

Conclusion

To conclude, the project now leverages python-dotenv for handling secrets and settings. Today, we saw how our Support Techs go about to harden the security of Django

PREVENT YOUR SERVER FROM CRASHING!

Never again lose customers to poor server speed! Let us help you.

Our server experts will monitor & maintain your server 24/7 so that it remains lightning fast and secure.

GET STARTED

var google_conversion_label = "owonCMyG5nEQ0aD71QM";
0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

server management

Spend time on your business, not on your servers.

TALK TO US

Or click here to learn more.

Speed issues driving customers away?
We’ve got your back!

Privacy Preference Center

Necessary

Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.

PHPSESSID - Preserves user session state across page requests.

gdpr[consent_types] - Used to store user consents.

gdpr[allowed_cookies] - Used to store user allowed cookies.

PHPSESSID, gdpr[consent_types], gdpr[allowed_cookies]
PHPSESSID
WHMCSpKDlPzh2chML

Statistics

Statistic cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.

_ga - Preserves user session state across page requests.

_gat - Used by Google Analytics to throttle request rate

_gid - Registers a unique ID that is used to generate statistical data on how you use the website.

smartlookCookie - Used to collect user device and location information of the site visitors to improve the websites User Experience.

_ga, _gat, _gid
_ga, _gat, _gid
smartlookCookie
_clck, _clsk, CLID, ANONCHK, MR, MUID, SM

Marketing

Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.

IDE - Used by Google DoubleClick to register and report the website user's actions after viewing or clicking one of the advertiser's ads with the purpose of measuring the efficacy of an ad and to present targeted ads to the user.

test_cookie - Used to check if the user's browser supports cookies.

1P_JAR - Google cookie. These cookies are used to collect website statistics and track conversion rates.

NID - Registers a unique ID that identifies a returning user's device. The ID is used for serving ads that are most relevant to the user.

DV - Google ad personalisation

_reb2bgeo - The visitor's geographical location

_reb2bloaded - Whether or not the script loaded for the visitor

_reb2bref - The referring URL for the visit

_reb2bsessionID - The visitor's RB2B session ID

_reb2buid - The visitor's RB2B user ID

IDE, test_cookie, 1P_JAR, NID, DV, NID
IDE, test_cookie
1P_JAR, NID, DV
NID
hblid
_reb2bgeo, _reb2bloaded, _reb2bref, _reb2bsessionID, _reb2buid

Security

These are essential site cookies, used by the google reCAPTCHA. These cookies use an unique identifier to verify if a visitor is human or a bot.

SID, APISID, HSID, NID, PREF
SID, APISID, HSID, NID, PREF