@@ -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
@@ -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`.
@@ -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'
@@ -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'
@@ -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'
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(-)