From 846ad3c800437306af144119bf548f7acd9518b4 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:11:21 +0000 Subject: [PATCH] Enable redirection of HTTP requests to HTTPS Using Django's `SECURE_SSL_REDIRECT`: https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-SECURE_SSL_REDIRECT Also configures gunicorn's `forwarded_allow_ips` setting to `"*"` so that gunicorn trusts the `X-Forwarded-Proto` header set by the Heroku Router during TLS termination, to ensure that HTTPS requests are correctly marked as secure in the WSGI metadata passed to the WSGI app (in this case, Django). See: https://docs.gunicorn.org/en/stable/settings.html#forwarded-allow-ips https://devcenter.heroku.com/articles/http-routing#heroku-headers (Whilst the classic Python buildpack already configures this by setting the env var `FORWARDED_ALLOW_IPS`, the Python CNB doesn't yet do so, and it's clearer to have the config explicitly set in the app source.) GUS-W-17482732. --- gettingstarted/settings.py | 22 +++++++++++++++++----- gunicorn.conf.py | 7 +++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/gettingstarted/settings.py b/gettingstarted/settings.py index 6ed26f4fa..e5228022f 100644 --- a/gettingstarted/settings.py +++ b/gettingstarted/settings.py @@ -49,14 +49,26 @@ # The `DYNO` env var is set on Heroku CI, but it's not a real Heroku app, so we have to # also explicitly exclude CI: # https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables -IS_HEROKU_APP = "DYNO" in os.environ and not "CI" in os.environ +IS_HEROKU_APP = "DYNO" in os.environ and "CI" not in os.environ -# On Heroku, it's safe to use a wildcard for `ALLOWED_HOSTS`, since the Heroku router performs -# validation of the Host header in the incoming HTTP request. On other platforms you may need to -# list the expected hostnames explicitly in production to prevent HTTP Host header attacks. See: -# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-ALLOWED_HOSTS if IS_HEROKU_APP: + # On Heroku, it's safe to use a wildcard for `ALLOWED_HOSTS`, since the Heroku router performs + # validation of the Host header in the incoming HTTP request. On other platforms you may need to + # list the expected hostnames explicitly in production to prevent HTTP Host header attacks. See: + # https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-ALLOWED_HOSTS ALLOWED_HOSTS = ["*"] + + # Redirect all non-HTTPS requests to HTTPS. This requires that: + # 1. Your app has a TLS/SSL certificate, which all `*.herokuapp.com` domains do by default. + # When using a custom domain, you must configure one. See: + # https://devcenter.heroku.com/articles/automated-certificate-management + # 2. Your app's WSGI web server is configured to use the `X-Forwarded-Proto` headers set by + # the Heroku Router (otherwise you may encounter infinite HTTP 301 redirects). See this + # app's `gunicorn.conf.py` for how this is done when using gunicorn. + # + # For maximum security, consider enabling HTTP Strict Transport Security (HSTS) headers too: + # https://docs.djangoproject.com/en/5.1/ref/middleware/#http-strict-transport-security + SECURE_SSL_REDIRECT = True else: ALLOWED_HOSTS = [".localhost", "127.0.0.1", "[::1]", "0.0.0.0", "[::]"] diff --git a/gunicorn.conf.py b/gunicorn.conf.py index f17967105..09e78a5d6 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -1,6 +1,7 @@ # Gunicorn configuration file: # https://docs.gunicorn.org/en/stable/configure.html # https://docs.gunicorn.org/en/stable/settings.html +# # Note: The classic Python buildpack currently sets a few gunicorn settings automatically via # the `GUNICORN_CMD_ARGS` env var (which take priority over the settings in this file): # https://github.com/heroku/heroku-buildpack-python/blob/main/vendor/python.gunicorn.sh @@ -70,3 +71,9 @@ # duplicate gunicorn processes have accidentally been launched (eg in different # terminals), since the "address already in use" error no longer occurs. reuse_port = True + + # Trust the `X-Forwarded-Proto` header set by the Heroku Router during TLS termination, + # (https://devcenter.heroku.com/articles/http-routing#heroku-headers) so that HTTPS requests + # are correctly marked as secure. This allows the WSGI app (in our case, Django) to distinguish + # between HTTP and HTTPS requests for features like HTTP->HTTPS URL redirection. + forwarded_allow_ips = "*"