Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Traefik secuity settings, add security documentation #2854

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,4 @@ tags
.idea/

.pytest_cache/
.vscode/
1 change: 1 addition & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"project_name": "My Awesome Project",
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}",
"_extensions": ["cookiecutter.extensions.RandomStringExtension"],
"description": "Behold My Awesome Project!",
"author_name": "Daniel Roy Greenfeld",
"domain_name": "example.com",
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Contents:
deployment-with-docker
docker-postgres-backups
websocket
security
faq
troubleshooting

Expand Down
52 changes: 52 additions & 0 deletions docs/security.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Security
========

.. index:: Security, Configuration, TLS, SSL, Encrpytion, Compatibility

Overview
--------

There is a reasonable level of security for most use cases configured as standard in cookiecutter-django, as much as is possible without knowing the features of the website you are building. That being said, your security requirements may differ greatly; before any deployment **you should ensure the security of your website is appropriate for your use case**. As a starting point, you should check any security settings used in cookiecutter-django and see if they are right for you, also actioning any TODOs. A good place to start is `config/settings/production.py`_, as well as `compose/production/traefik/traefik.yml`_ if you are using Docker. Naturally if you collect sensitive data, or have reason to believe your site is at all likely be targeted by hackers, a complete security review before allowing any user access is a must.

.. _`config/settings/production.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/config/settings/production.py
.. _`compose/production/traefik/traefik.yml`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/compose/production/traefik/traefik.yml



Security Checklist
---------------------------------
This list is by no means comprehensive, but is a good starting point for those less well-versed in website security:

- Run ``python manage.py check --deploy`` and ensure any issues listed are fixed (make sure you are checking the production settings by setting ``DJANGO_SETTINGS_MODULE=config.settings.production`` in the environment). Note: if you are using Docker, some settings flagged as missing by Django may be managed by Traefik's config.
- Test your SSL security `using SSL Labs`_, a full score may be overkill and will likely cause compatibility issues with older browsers, but try to action anything flagged in red. **Note**: full marks here does not necessarily make your website 'secure', this tool merely tests the strength of your TLS encryption settings.
- Check how your site stacks up against Mozilla's `Web Security Cheat Sheet`_. Again, completing everything listed here may be overkill but it is good as a reference.
- Consider creating a `Content Security Policy`_ for your site. In short, this is a HTTP header that instructs client browsers where they should load resources from (scripts, stylesheets etc.), and what web features should be enabled (inline HTML script and style tags, allowed domains for JavaScript AJAX fetches etc.). An ideal policy is to disable everything by default then just enable the features you use on your site.

.. _`using SSL Labs`: https://www.ssllabs.com/ssltest/
.. _`Web Security Cheat Sheet`: https://infosec.mozilla.org/guidelines/web_security.html#web-security-cheat-sheet
.. _`Content Security Policy`: https://content-security-policy.com/

Compatibility Issues
--------------------
By default, cookiecutter-django uses security settings that may break compatibility with very old browsers. Before changing your settings to allow for these older browsers, consider asking your clients to update. There is a trade-off between security and compatibility, and weakening your security to support a minority of users can make things less safe for those who are up to date.

Some settings of note when using Docker (in `compose/production/traefik/traefik.yml`_):

- ``tls.options.default.minVersion: VersionTLS12``: `TLS 1.2 supported browsers`_
- ``tls.options.default.sniStrict: true``: `SNI supported browsers`_
- ``tls.options.default.cipherSuites``: only ECDHE ciphers are used, this will not work in old browsers. Internet Explorer <11 does not work and IE11 seems to only work on Windows 10. Edge is fine though, and is a better alternative for Windows users.

.. _`TLS 1.2 supported browsers`: https://caniuse.com/tls1-2
.. _`SNI supported browsers`: https://caniuse.com/sni

Useful Links
------------
- `Django Deployment Checklist`_
- `SSL and TLS Deployment Best Practices`_
- `Content Security Policy (CSP) Quick Reference Guide`_
- `Mozilla Web Security Cheat Sheet`_

.. _`Django Deployment Checklist`: https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
.. _`SSL and TLS Deployment Best Practices`: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
.. _`Content Security Policy (CSP) Quick Reference Guide`: https://content-security-policy.com/
.. _`Mozilla Web Security Cheat Sheet`: https://infosec.mozilla.org/guidelines/web_security.html#web-security-cheat-sheet
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
FROM traefik:v2.2.11
RUN mkdir -p /etc/traefik/acme \
&& touch /etc/traefik/acme/acme.json \
&& chmod 600 /etc/traefik/acme/acme.json
COPY ./compose/production/traefik/traefik.yml /etc/traefik
FROM traefik:2.4

WORKDIR /etc/traefik/

RUN addgroup --system traefik \
&& adduser \
--disabled-password \
--gecos '' \
--no-create-home \
--ingroup traefik \
traefik \
&& mkdir acme/ \
&& touch acme/acme.json \
&& chmod 0600 acme/acme.json \
&& chown -R traefik:traefik .

EXPOSE 8080/tcp 8443/tcp

VOLUME /etc/traefik/acme/

USER traefik
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
log:
level: INFO

entryPoints:
web:
# http
address: ":80"
address: ":8080"
http:
# https://docs.traefik.io/routing/entrypoints/#entrypoint
redirections:
Expand All @@ -13,7 +14,7 @@ entryPoints:

web-secure:
# https
address: ":443"
address: ":8443"
{%- if cookiecutter.use_celery == 'y' %}

flower:
Expand All @@ -30,6 +31,20 @@ certificatesResolvers:
httpChallenge:
entryPoint: web

tls:
# https://docs.traefik.io/https/tls/#tls-options
options:
default:
# see https://caniuse.com/tls1-2 for browsers that support TLS 1.2
minVersion: VersionTLS12
# see https://caniuse.com/sni for browsers that support SNI
sniStrict: true
# https://wiki.mozilla.org/Security/Server_Side_TLS
cipherSuites: # use only elliptic curve (the strongest) ciphers
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256

http:
routers:
web-secure-router:
Expand All @@ -42,6 +57,7 @@ http:
- web-secure
middlewares:
- csrf
- security-headers
service: django
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
Expand All @@ -52,6 +68,8 @@ http:
rule: "Host(`{{ cookiecutter.domain_name }}`)"
entryPoints:
- flower
middlewares:
- security-headers
service: flower
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
Expand All @@ -64,6 +82,19 @@ http:
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
headers:
hostsProxyHeaders: ["X-CSRFToken"]
security-headers:
# https://docs.traefik.io/middlewares/headers/#general
headers:
# TODO increase stsSeconds to *at least* 31536000 (1 year) once HTTPS works
stsSeconds: 60
stsIncludeSubdomains: true
stsPreload: true
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
customResponseHeaders:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server
server: {{ random_ascii_string(10) }} # hide server version

services:
django:
Expand Down
37 changes: 29 additions & 8 deletions {{cookiecutter.project_slug}}/config/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,30 @@

# SECURITY
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
{% if cookiecutter.use_docker -%}
# NOTE headers are managed by the security-headers middleware in traefik.yml
jameswilliams1 marked this conversation as resolved.
Show resolved Hide resolved
# Uncomment the following if you are not using Traefik
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
# SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
# TODO increase this to *at least* 31536000 (1 year) once HTTPS works
# SECURE_HSTS_SECONDS = 60
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
# SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
# "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True
# )
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
# SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
# SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
# "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
# )
{% else -%}
# TODO set security headers in your load balancer if possible and remove these
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
CSRF_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
# TODO: set this to 60 seconds first and then to 518400 once you prove the former works
# TODO increase this to *at least* 31536000 (1 year) once HTTPS works
SECURE_HSTS_SECONDS = 60
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
Expand All @@ -65,6 +78,14 @@
SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
"DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
)
{% endif -%}
# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
CSRF_COOKIE_SECURE = True

{% if cookiecutter.cloud_provider != 'None' -%}
# STORAGES
Expand Down
1 change: 1 addition & 0 deletions {{cookiecutter.project_slug}}/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ services:
- django
volumes:
- production_traefik:/etc/traefik/acme:z
- ./compose/production/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
Expand Down