diff mbox series

[error-report-web,2/2] Read security settings from the environment and expect a proxy.

Message ID 20261018015615.74684-5-tim.orling@konsulko.com
State New
Headers show
Series Fix deployment security checks | expand

Commit Message

Tim Orling Oct. 18, 2026, 1:54 a.m. UTC
Fixes the remaining check --deploy warnings, in both settings files.
DEBUG, ALLOWED_HOSTS and SECRET_KEY (now required) come from the
environment. Behind a TLS proxy that sets X-Forwarded-Proto, cookies are
secure and HSTS is sent unless DEBUG. W008 (the proxy redirects) and
W005/W021 (the domain owner's call) are silenced. run-tests.sh runs
check --deploy so new warnings fail CI.

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 Post/test.py               |  5 +++++
 README                     |  5 ++++-
 docker/run-tests.sh        |  7 ++++---
 project/settings.py        | 20 ++++++++++++++++++--
 project/settings.py.docker | 20 ++++++++++++++++++--
 5 files changed, 49 insertions(+), 8 deletions(-)
diff mbox series

Patch

diff --git a/Post/test.py b/Post/test.py
index 28982e4..f48beec 100755
--- a/Post/test.py
+++ b/Post/test.py
@@ -273,6 +273,11 @@  class CSPTest(TestCase):
         self.assertEqual(response['X-Content-Type-Options'], 'nosniff')
         self.assertEqual(response['Referrer-Policy'], 'same-origin')
 
+    def test_hsts_only_over_https_from_proxy(self):
+        self.assertNotIn('Strict-Transport-Security', self.client.get('/Statistics/'))
+        response = self.client.get('/Statistics/', HTTP_X_FORWARDED_PROTO='https')
+        self.assertEqual(response['Strict-Transport-Security'], 'max-age=31536000')
+
     def test_csp_report_only(self):
         with override_settings(CONTENT_SECURITY_POLICY_REPORT_ONLY=True):
             # New client so the middleware picks up the setting
diff --git a/README b/README
index fb38a66..a7bb64c 100644
--- a/README
+++ b/README
@@ -23,7 +23,10 @@  Setup instructions:
 1. Initialise and activate a python virtual environment. `virtualenv venv && source ./venv/bin/activate`
 2. Install the dependencies via `pip install -r requirements.txt`
 3. Set DATABASES.default.ENGINE in settings.py to use the database engine of your choice.
-4. Set a SECRET_KEY in settings.py
+4. Set the SECRET_KEY environment variable, for example
+   `export SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")`.
+   Set DEBUG=1 for development, and ALLOWED_HOSTS to a comma-separated list of
+   the host names the site is served as (default: localhost,127.0.0.1).
 5. Setup the database schema with the Django model. Run `python manage.py migrate`.
 
 
diff --git a/docker/run-tests.sh b/docker/run-tests.sh
index 2eafd98..be1faeb 100755
--- a/docker/run-tests.sh
+++ b/docker/run-tests.sh
@@ -25,8 +25,9 @@  until docker exec $DB mysql -h127.0.0.1 -uroot -ptestingpw -e 'SELECT 1' >/dev/n
     sleep 1
 done
 
-# -Wa shows deprecation warnings, i.e. what breaks on the next Django.
+# Fail on deployment warnings (hence the random key); -Wa shows deprecations.
 docker run --rm --network $NET \
-    -e SECRET_KEY=ci-only \
+    -e SECRET_KEY="$(head -c 48 /dev/urandom | base64)" \
     -e MYSQL_DATABASE=errorreport -e MYSQL_USER=root -e MYSQL_PASSWORD=testingpw \
-    "$IMAGE" python3 -Wa manage.py test --noinput Post
+    "$IMAGE" sh -c 'python3 manage.py check --deploy --fail-level WARNING &&
+        python3 -Wa manage.py test --noinput Post'
diff --git a/project/settings.py b/project/settings.py
index 97c179c..12f7f1c 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -6,7 +6,11 @@ 
 
 import os
 
-DEBUG = True
+# DEBUG=1 for development; off by default.
+DEBUG = os.environ.get('DEBUG', '').lower() in ('1', 'true', 'yes')
+
+# Comma-separated.
+ALLOWED_HOSTS = [h for h in os.environ.get('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',') if h]
 
 ADMINS = (
     # ('Your Name', 'your_email@example.com'),
@@ -129,7 +133,7 @@  STATICFILES_FINDERS = (
 )
 
 # Make this unique, and don't share it with anybody.
-SECRET_KEY = 'changeme'
+SECRET_KEY = os.environ['SECRET_KEY']
 
 MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
@@ -209,6 +213,18 @@  LOGGING = {
 
 BUGZILLA_URL = 'https://bugzilla.yoctoproject.org'
 
+# Expects a proxy that terminates TLS, redirects to HTTPS and sets
+# X-Forwarded-Proto, overwriting the client's.
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+SESSION_COOKIE_SECURE = not DEBUG
+CSRF_COOKIE_SECURE = not DEBUG
+SECURE_HSTS_SECONDS = 0 if DEBUG else 31536000
+SILENCED_SYSTEM_CHECKS = [
+    'security.W008',  # the proxy redirects to HTTPS
+    'security.W005',  # HSTS includeSubDomains and preload are
+    'security.W021',  # the domain owner's call
+]
+
 EMAIL_HOST = 'localhost'
 DEFAULT_FROM_EMAIL = 'noreply@example.com'
 
diff --git a/project/settings.py.docker b/project/settings.py.docker
index 70c9253..e1f9812 100644
--- a/project/settings.py.docker
+++ b/project/settings.py.docker
@@ -6,7 +6,11 @@ 
 
 import os
 
-DEBUG = True
+# DEBUG=1 for development; off by default.
+DEBUG = os.environ.get('DEBUG', '').lower() in ('1', 'true', 'yes')
+
+# Comma-separated.
+ALLOWED_HOSTS = [h for h in os.environ.get('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',') if h]
 
 ADMINS = (
     # ('Your Name', 'your_email@example.com'),
@@ -123,7 +127,7 @@  STATICFILES_FINDERS = (
 )
 
 # Make this unique, and don't share it with anybody.
-SECRET_KEY = os.environ.get('SECRET_KEY', 'changeme')
+SECRET_KEY = os.environ['SECRET_KEY']
 
 MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
@@ -203,6 +207,18 @@  LOGGING = {
 
 BUGZILLA_URL = 'https://bugzilla.yoctoproject.org'
 
+# Expects a proxy that terminates TLS, redirects to HTTPS and sets
+# X-Forwarded-Proto, overwriting the client's.
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+SESSION_COOKIE_SECURE = not DEBUG
+CSRF_COOKIE_SECURE = not DEBUG
+SECURE_HSTS_SECONDS = 0 if DEBUG else 31536000
+SILENCED_SYSTEM_CHECKS = [
+    'security.W008',  # the proxy redirects to HTTPS
+    'security.W005',  # HSTS includeSubDomains and preload are
+    'security.W021',  # the domain owner's call
+]
+
 EMAIL_HOST = 'localhost'
 DEFAULT_FROM_EMAIL = 'noreply@example.com'