Browse Source

Fixed #26947 -- Added an option to enable the HSTS header preload directive.

Ed Morley 8 years ago
parent
commit
3c2447dd13

+ 1 - 0
django/conf/global_settings.py

@@ -629,6 +629,7 @@ SILENCED_SYSTEM_CHECKS = []
 SECURE_BROWSER_XSS_FILTER = False
 SECURE_BROWSER_XSS_FILTER = False
 SECURE_CONTENT_TYPE_NOSNIFF = False
 SECURE_CONTENT_TYPE_NOSNIFF = False
 SECURE_HSTS_INCLUDE_SUBDOMAINS = False
 SECURE_HSTS_INCLUDE_SUBDOMAINS = False
+SECURE_HSTS_PRELOAD = False
 SECURE_HSTS_SECONDS = 0
 SECURE_HSTS_SECONDS = 0
 SECURE_REDIRECT_EXEMPT = []
 SECURE_REDIRECT_EXEMPT = []
 SECURE_SSL_HOST = None
 SECURE_SSL_HOST = None

+ 3 - 2
django/middleware/security.py

@@ -9,6 +9,7 @@ class SecurityMiddleware(MiddlewareMixin):
     def __init__(self, get_response=None):
     def __init__(self, get_response=None):
         self.sts_seconds = settings.SECURE_HSTS_SECONDS
         self.sts_seconds = settings.SECURE_HSTS_SECONDS
         self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS
         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.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF
         self.xss_filter = settings.SECURE_BROWSER_XSS_FILTER
         self.xss_filter = settings.SECURE_BROWSER_XSS_FILTER
         self.redirect = settings.SECURE_SSL_REDIRECT
         self.redirect = settings.SECURE_SSL_REDIRECT
@@ -30,10 +31,10 @@ class SecurityMiddleware(MiddlewareMixin):
         if (self.sts_seconds and request.is_secure() and
         if (self.sts_seconds and request.is_secure() and
                 'strict-transport-security' not in response):
                 'strict-transport-security' not in response):
             sts_header = "max-age=%s" % self.sts_seconds
             sts_header = "max-age=%s" % self.sts_seconds
-
             if self.sts_include_subdomains:
             if self.sts_include_subdomains:
                 sts_header = sts_header + "; includeSubDomains"
                 sts_header = sts_header + "; includeSubDomains"
-
+            if self.sts_preload:
+                sts_header = sts_header + "; preload"
             response["strict-transport-security"] = sts_header
             response["strict-transport-security"] = sts_header
 
 
         if self.content_type_nosniff and 'x-content-type-options' not in response:
         if self.content_type_nosniff and 'x-content-type-options' not in response:

+ 6 - 0
docs/ref/middleware.txt

@@ -226,6 +226,7 @@ enabled or disabled with a setting.
 * :setting:`SECURE_BROWSER_XSS_FILTER`
 * :setting:`SECURE_BROWSER_XSS_FILTER`
 * :setting:`SECURE_CONTENT_TYPE_NOSNIFF`
 * :setting:`SECURE_CONTENT_TYPE_NOSNIFF`
 * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`
 * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`
+* :setting:`SECURE_HSTS_PRELOAD`
 * :setting:`SECURE_HSTS_SECONDS`
 * :setting:`SECURE_HSTS_SECONDS`
 * :setting:`SECURE_REDIRECT_EXEMPT`
 * :setting:`SECURE_REDIRECT_EXEMPT`
 * :setting:`SECURE_SSL_HOST`
 * :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
 subdomains are served exclusively using HTTPS), otherwise your site may still
 be vulnerable via an insecure connection to a subdomain.
 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::
 .. warning::
     The HSTS policy applies to your entire domain, not just the URL of the
     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
     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.
     you may need to set the :setting:`SECURE_PROXY_SSL_HEADER` setting.
 
 
 .. _"Strict-Transport-Security" header: https://en.wikipedia.org/wiki/Strict_Transport_Security
 .. _"Strict-Transport-Security" header: https://en.wikipedia.org/wiki/Strict_Transport_Security
+.. _browser preload list: https://hstspreload.appspot.com/
 
 
 .. _x-content-type-options:
 .. _x-content-type-options:
 
 

+ 20 - 0
docs/ref/settings.txt

@@ -2062,6 +2062,25 @@ non-zero value.
     :setting:`SECURE_HSTS_SECONDS`) break your site. Read the
     :setting:`SECURE_HSTS_SECONDS`) break your site. Read the
     :ref:`http-strict-transport-security` documentation first.
     :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
 .. setting:: SECURE_HSTS_SECONDS
 
 
 ``SECURE_HSTS_SECONDS``
 ``SECURE_HSTS_SECONDS``
@@ -3334,6 +3353,7 @@ HTTP
   * :setting:`SECURE_BROWSER_XSS_FILTER`
   * :setting:`SECURE_BROWSER_XSS_FILTER`
   * :setting:`SECURE_CONTENT_TYPE_NOSNIFF`
   * :setting:`SECURE_CONTENT_TYPE_NOSNIFF`
   * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`
   * :setting:`SECURE_HSTS_INCLUDE_SUBDOMAINS`
+  * :setting:`SECURE_HSTS_PRELOAD`
   * :setting:`SECURE_HSTS_SECONDS`
   * :setting:`SECURE_HSTS_SECONDS`
   * :setting:`SECURE_PROXY_SSL_HEADER`
   * :setting:`SECURE_PROXY_SSL_HEADER`
   * :setting:`SECURE_REDIRECT_EXEMPT`
   * :setting:`SECURE_REDIRECT_EXEMPT`

+ 3 - 0
docs/releases/1.11.txt

@@ -229,6 +229,9 @@ Requests and Responses
 * :class:`~django.middleware.common.CommonMiddleware` now sets the
 * :class:`~django.middleware.common.CommonMiddleware` now sets the
   ``Content-Length`` response header for non-streaming responses.
   ``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
 Serialization
 ~~~~~~~~~~~~~
 ~~~~~~~~~~~~~
 
 

+ 3 - 2
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
   to a particular site should always use HTTPS. Combined with redirecting
   requests over HTTP to HTTPS, this will ensure that connections always enjoy
   requests over HTTP to HTTPS, this will ensure that connections always enjoy
   the added security of SSL provided one successful connection has occurred.
   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:
 .. _host-headers-virtual-hosting:
 
 

+ 31 - 0
tests/middleware/test_security.py

@@ -99,6 +99,37 @@ class SecurityMiddlewareTest(SimpleTestCase):
         response = self.process_response(secure=True)
         response = self.process_response(secure=True)
         self.assertEqual(response["strict-transport-security"], "max-age=600")
         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)
     @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True)
     def test_content_type_on(self):
     def test_content_type_on(self):
         """
         """