Browse Source

Set security headers in django instead of reverse proxy

master
Gabriel Augendre 4 weeks ago
parent
commit
58b40710f0
  1. 9
      articles/templates/articles/snippets/analytics.html
  2. 2
      articles/tests/test_html_views.py
  3. 17
      blog/settings.py
  4. 9
      docker/nginx-dev.conf
  5. 9
      docker/nginx.conf
  6. 21
      poetry.lock
  7. 1
      pyproject.toml

9
articles/templates/articles/snippets/analytics.html

@ -1,13 +1,8 @@
{% load static %}
{% if not user.is_authenticated and goatcounter_domain is not None %}
<script>
window.goatcounter = {
endpoint: 'https://{{ goatcounter_domain }}/count',
allow_local: true,
}
</script>
<script async defer src="{% static "vendor/goatcounter.js" %}"></script>
<script async defer src="{% static "vendor/goatcounter.js" %}"
data-goatcounter="https://{{ goatcounter_domain }}/count"></script>
<noscript>
<img src="https://{{ goatcounter_domain }}/count?p={{ request.get_full_path }}"
alt="GoatCounter tracking pixel">

2
articles/tests/test_html_views.py

@ -106,7 +106,7 @@ def test_has_goatcounter_if_set(client: Client, settings):
settings.GOATCOUNTER_DOMAIN = "gc.gabnotes.org"
res = client.get(reverse("articles-list"))
content = res.content.decode("utf-8")
assert "window.goatcounter" in content
assert "data-goatcounter" in content
assert f"{settings.GOATCOUNTER_DOMAIN}/count" in content

17
blog/settings.py

@ -83,6 +83,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"csp.middleware.CSPMiddleware",
]
ROOT_URLCONF = "blog.urls"
@ -208,3 +209,19 @@ SHORTPIXEL_RESIZE_HEIGHT = int(os.getenv("SHORTPIXEL_RESIZE_HEIGHT", 10000))
GOATCOUNTER_DOMAIN = os.getenv("GOATCOUNTER_DOMAIN")
LOGIN_URL = "admin:login"
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 63072000
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# CSP
CSP_DEFAULT_SRC = ("'none'",)
https_goatcounter_domain = "https://" + GOATCOUNTER_DOMAIN
CSP_IMG_SRC = ("'self'", https_goatcounter_domain)
CSP_SCRIPT_SRC = ("'self'",)
CSP_CONNECT_SRC = ("'self'", https_goatcounter_domain)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_MANIFEST_SRC = ("'self'",)

9
docker/nginx-dev.conf

@ -36,15 +36,6 @@ server {
proxy_redirect off;
}
add_header Content-Security-Policy "frame-ancestors 'none'; default-src 'none'; img-src 'self' https:; script-src 'self'
https://gc.gabnotes.org; connect-src https://gc.gabnotes.org; style-src 'self' 'unsafe-inline';
font-src 'self'; manifest-src 'self';" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
listen [::]:80;
listen 80;
}

9
docker/nginx.conf

@ -37,15 +37,6 @@ server {
proxy_redirect off;
}
add_header Content-Security-Policy "frame-ancestors 'none'; default-src 'none'; img-src https:; script-src 'self'
https://gc.gabnotes.org; connect-src https://gc.gabnotes.org; style-src 'self' 'unsafe-inline';
font-src 'self'; manifest-src 'self';" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
listen [::]:80;
listen 80;
}

21
poetry.lock

@ -163,6 +163,21 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "django-csp"
version = "3.7"
description = "Django Content Security Policy support."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Django = ">=1.8"
[package.extras]
jinja2 = ["jinja2 (>=2.9.6)"]
tests = ["pytest (<4.0)", "pytest-django", "pytest-flakes (==1.0.1)", "pytest-pep8 (==1.0.6)", "pep8 (==1.4.6)", "mock (==1.0.1)", "six (==1.12.0)", "jinja2 (>=2.9.6)"]
[[package]]
name = "django-debug-toolbar"
version = "3.2"
@ -637,7 +652,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "75428f7191094bbd90f5bb60010d46a7bb1d904cbea3abadfe7c97af6db1b1a3"
content-hash = "b57e76c611e052a1ece53c34764ddbf0e0f33ff9dafa5193bec5384685cebf01"
[metadata.files]
appdirs = [
@ -785,6 +800,10 @@ django-cleanup = [
{file = "django-cleanup-5.1.0.tar.gz", hash = "sha256:8976aec12a22913afb3d1fcb541b1aedde2f5ec243e4260c5ff78bb6aa75a089"},
{file = "django_cleanup-5.1.0-py2.py3-none-any.whl", hash = "sha256:71e098c7d9ac3f3da40b95cff9c0bc51218d40ef419261232f46ba3141c50acc"},
]
django-csp = [
{file = "django_csp-3.7-py2.py3-none-any.whl", hash = "sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a"},
{file = "django_csp-3.7.tar.gz", hash = "sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727"},
]
django-debug-toolbar = [
{file = "django-debug-toolbar-3.2.tar.gz", hash = "sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2"},
{file = "django_debug_toolbar-3.2-py3-none-any.whl", hash = "sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a"},

1
pyproject.toml

@ -40,6 +40,7 @@ pylibmc = "^1.6.1"
django-debug-toolbar = "^3.2"
whitenoise = {extras = ["brotli"], version = "^5.2.0"}
rcssmin = "^1.0.6"
django-csp = "^3.7"
[tool.poetry.dev-dependencies]
pre-commit = "^2.7"

Loading…
Cancel
Save