From 3c2447dd13e495d57700ca8447896acd85044444 Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Thu, 28 Jul 2016 17:48:07 +0100 Subject: [PATCH] Fixed #26947 -- Added an option to enable the HSTS header preload directive. --- django/conf/global_settings.py | 1 + django/middleware/security.py | 5 +++-- docs/ref/middleware.txt | 6 ++++++ docs/ref/settings.txt | 20 ++++++++++++++++++++ docs/releases/1.11.txt | 3 +++ docs/topics/security.txt | 5 +++-- tests/middleware/test_security.py | 31 +++++++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 4 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 94cfcf25e57..a745b9cc4dc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -629,6 +629,7 @@ SILENCED_SYSTEM_CHECKS = [] SECURE_BROWSER_XSS_FILTER = False SECURE_CONTENT_TYPE_NOSNIFF = False SECURE_HSTS_INCLUDE_SUBDOMAINS = False +SECURE_HSTS_PRELOAD = False SECURE_HSTS_SECONDS = 0 SECURE_REDIRECT_EXEMPT = [] SECURE_SSL_HOST = None diff --git a/django/middleware/security.py b/django/middleware/security.py index 02a59ad6e45..7bcb72738e4 100644 --- a/django/middleware/security.py +++ b/django/middleware/security.py @@ -9,6 +9,7 @@ class SecurityMiddleware(MiddlewareMixin): def __init__(self, get_response=None): self.sts_seconds = settings.SECURE_HSTS_SECONDS self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS + self.sts_preload = settings.SECURE_HSTS_PRELOAD self.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF self.xss_filter = settings.SECURE_BROWSER_XSS_FILTER self.redirect = settings.SECURE_SSL_REDIRECT @@ -30,10 +31,10 @@ class SecurityMiddleware(MiddlewareMixin): if (self.sts_seconds and request.is_secure() and 'strict-transport-security' not in response): sts_header = "max-age=%s" % self.sts_seconds - if self.sts_include_subdomains: sts_header = sts_header + "; includeSubDomains" - + if self.sts_preload: + sts_header = sts_header + "; preload" response["strict-transport-security"] = sts_header if self.content_type_nosniff and 'x-content-type-options' not in response: diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index cee18ea0587..8334c442165 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -226,6 +226,7 @@ enabled or disabled with a setting. * :setting:`SECURE_BROWSER_XSS_FILTER` * :setting:`SECURE_CONTENT_TYPE_NOSNIFF` * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS` +* :setting:`SECURE_HSTS_PRELOAD` * :setting:`SECURE_HSTS_SECONDS` * :setting:`SECURE_REDIRECT_EXEMPT` * :setting:`SECURE_SSL_HOST` @@ -260,6 +261,10 @@ to the ``Strict-Transport-Security`` header. This is recommended (assuming all subdomains are served exclusively using HTTPS), otherwise your site may still be vulnerable via an insecure connection to a subdomain. +If you wish to submit your site to the `browser preload list`_, set the +:setting:`SECURE_HSTS_PRELOAD` setting to ``True``. That appends the +``preload`` directive to the ``Strict-Transport-Security`` header. + .. warning:: The HSTS policy applies to your entire domain, not just the URL of the response that you set the header on. Therefore, you should only use it if @@ -277,6 +282,7 @@ be vulnerable via an insecure connection to a subdomain. you may need to set the :setting:`SECURE_PROXY_SSL_HEADER` setting. .. _"Strict-Transport-Security" header: https://en.wikipedia.org/wiki/Strict_Transport_Security +.. _browser preload list: https://hstspreload.appspot.com/ .. _x-content-type-options: diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 9e0d8b8d413..445e504e981 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2062,6 +2062,25 @@ non-zero value. :setting:`SECURE_HSTS_SECONDS`) break your site. Read the :ref:`http-strict-transport-security` documentation first. +.. setting:: SECURE_HSTS_PRELOAD + +``SECURE_HSTS_PRELOAD`` +----------------------- + +.. versionadded:: 1.11 + +Default: ``False`` + +If ``True``, the :class:`~django.middleware.security.SecurityMiddleware` adds +the ``preload`` directive to the :ref:`http-strict-transport-security` +header. It has no effect unless :setting:`SECURE_HSTS_SECONDS` is set to a +non-zero value. + +.. warning:: + Setting this incorrectly can irreversibly (for at least several months, + depending on browser releases) break your site. Read the + :ref:`http-strict-transport-security` documentation first. + .. setting:: SECURE_HSTS_SECONDS ``SECURE_HSTS_SECONDS`` @@ -3334,6 +3353,7 @@ HTTP * :setting:`SECURE_BROWSER_XSS_FILTER` * :setting:`SECURE_CONTENT_TYPE_NOSNIFF` * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS` + * :setting:`SECURE_HSTS_PRELOAD` * :setting:`SECURE_HSTS_SECONDS` * :setting:`SECURE_PROXY_SSL_HEADER` * :setting:`SECURE_REDIRECT_EXEMPT` diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index d8534c0d118..64a5fc27808 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -229,6 +229,9 @@ Requests and Responses * :class:`~django.middleware.common.CommonMiddleware` now sets the ``Content-Length`` response header for non-streaming responses. +* Added the :setting:`SECURE_HSTS_PRELOAD` setting to allow appending the + ``preload`` directive to the ``Strict-Transport-Security`` header. + Serialization ~~~~~~~~~~~~~ diff --git a/docs/topics/security.txt b/docs/topics/security.txt index ff33e8be6d8..8d7b9c91f1d 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -160,8 +160,9 @@ server, there are some additional steps you may need: to a particular site should always use HTTPS. Combined with redirecting requests over HTTP to HTTPS, this will ensure that connections always enjoy the added security of SSL provided one successful connection has occurred. - HSTS may either be configured with :setting:`SECURE_HSTS_SECONDS` and - :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS` or on the Web server. + HSTS may either be configured with :setting:`SECURE_HSTS_SECONDS`, + :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`, and :setting:`SECURE_HSTS_PRELOAD`, + or on the Web server. .. _host-headers-virtual-hosting: diff --git a/tests/middleware/test_security.py b/tests/middleware/test_security.py index f671600c494..b8e77f76e44 100644 --- a/tests/middleware/test_security.py +++ b/tests/middleware/test_security.py @@ -99,6 +99,37 @@ class SecurityMiddlewareTest(SimpleTestCase): response = self.process_response(secure=True) self.assertEqual(response["strict-transport-security"], "max-age=600") + @override_settings(SECURE_HSTS_SECONDS=10886400, SECURE_HSTS_PRELOAD=True) + def test_sts_preload(self): + """ + With HSTS_SECONDS non-zero and SECURE_HSTS_PRELOAD True, the middleware + adds a "strict-transport-security" header with the "preload" directive + to the response. + """ + response = self.process_response(secure=True) + self.assertEqual(response["strict-transport-security"], "max-age=10886400; preload") + + @override_settings(SECURE_HSTS_SECONDS=10886400, SECURE_HSTS_INCLUDE_SUBDOMAINS=True, SECURE_HSTS_PRELOAD=True) + def test_sts_subdomains_and_preload(self): + """ + With HSTS_SECONDS non-zero, SECURE_HSTS_INCLUDE_SUBDOMAINS and + SECURE_HSTS_PRELOAD True, the middleware adds a "strict-transport-security" + header containing both the "includeSubDomains" and "preload" directives + to the response. + """ + response = self.process_response(secure=True) + self.assertEqual(response["strict-transport-security"], "max-age=10886400; includeSubDomains; preload") + + @override_settings(SECURE_HSTS_SECONDS=10886400, SECURE_HSTS_PRELOAD=False) + def test_sts_no_preload(self): + """ + With HSTS_SECONDS non-zero and SECURE_HSTS_PRELOAD + False, the middleware adds a "strict-transport-security" header without + the "preload" directive to the response. + """ + response = self.process_response(secure=True) + self.assertEqual(response["strict-transport-security"], "max-age=10886400") + @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True) def test_content_type_on(self): """