From 0473c67c4808a8f2a6ed3aa083b94040b67b75a8 Mon Sep 17 00:00:00 2001 From: sagi Date: Fri, 6 Nov 2015 22:31:30 +0000 Subject: [PATCH] Add HSTS header enhancement to Apache --- .../letsencrypt_apache/configurator.py | 70 ++++++++++++++++++- .../letsencrypt_apache/constants.py | 6 ++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index d376fe4b6..ea6f80fae 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -122,7 +122,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.parser = None self.version = version self.vhosts = None - self._enhance_func = {"redirect": self._enable_redirect} + self._enhance_func = {"redirect": self._enable_redirect, + "hsts": self._enable_hsts} @property def mod_ssl_conf(self): @@ -681,7 +682,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): ############################################################################ def supported_enhancements(self): # pylint: disable=no-self-use """Returns currently supported enhancements.""" - return ["redirect"] + return ["redirect", "hsts"] def enhance(self, domain, enhancement, options=None): """Enhance configuration. @@ -708,6 +709,71 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): logger.warn("Failed %s for %s", enhancement, domain) raise + def _enable_hsts(self, ssl_vhost, unused_options): + """Enables the HSTS header on all HTTP responses. + + .. note:: HSTS defends against SSL stripping attacks. + + + Adds the Strict-Transport-Security header with max-age=31536000 (1 year) + and includeSubDomains (all subdomains are also set with HSTS). + + .. note:: This function saves the configuration + + :param ssl_vhost: Destination of traffic, an ssl enabled vhost + :type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost` + + :param unused_options: Not currently used + :type unused_options: Not Available + + :returns: Success, general_vhost (HTTP vhost) + :rtype: (bool, :class:`~letsencrypt_apache.obj.VirtualHost`) + + :raises .errors.PluginError: If no viable HTTP host can be created or + used for the HSTS. + + """ + if "headers_module" not in self.parser.modules: + self.enable_mod("headers") + + # Check if HSTS header is already set + self._verify_no_hsts_header(ssl_vhost) + + # Add directives to server + self.parser.add_dir(ssl_vhost.path, "Header", constants.HSTS_ARGS) + self.save_notes += ("Adding HSTS header to every response from ssl " + "vhost in %s\n" % (ssl_vhost.filep)) + self.save() + logger.info("Adding HSTS header to every response from ssl vhost in %s", + ssl_vhost.filep) + + def _verify_no_hsts_header(self, ssl_vhost): + """Checks to see if existing HSTS settings is in place. + + Checks to see if virtualhost already contains a HSTS header + + :param vhost: vhost to check + :type vhost: :class:`~letsencrypt_apache.obj.VirtualHost` + + :returns: boolean + :rtype: (bool) + + :raises errors.PluginError: When an HSTS header exists + + """ + header_path = self.parser.find_dir("Header", None, start=ssl_vhost.path) + if header_path: + # "Existing Header directive for virtualhost" + for match in header_path: + if match == "Strict-Transport-Security": + raise errors.PluginError("Existing HSTS header") + + for match, arg in itertools.izip(header_path, constants.HSTS_ARGS): + if self.aug.get(match) != arg: + raise errors.PluginError("Unknown Existing HSTS header") + raise errors.PluginError("Let's Encrypt has already enabled HSTS") + + def _enable_redirect(self, ssl_vhost, unused_options): """Redirect all equivalent HTTP traffic to ssl_vhost. diff --git a/letsencrypt-apache/letsencrypt_apache/constants.py b/letsencrypt-apache/letsencrypt_apache/constants.py index 1c17eacc3..05e1bb0e7 100644 --- a/letsencrypt-apache/letsencrypt_apache/constants.py +++ b/letsencrypt-apache/letsencrypt_apache/constants.py @@ -27,3 +27,9 @@ AUGEAS_LENS_DIR = pkg_resources.resource_filename( REWRITE_HTTPS_ARGS = [ "^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"] """Apache rewrite rule arguments used for redirections to https vhost""" + +HSTS_ARGS = [ + "always", "set", "Strict-Transport-Security", + "\"max-age=31536000; includeSubDomains\""] +"""Apache header arguments for HSTS""" +