Generalized http-header enhancement

This commit is contained in:
sagi 2015-11-08 04:37:57 +00:00
parent 2988a09087
commit 04136cfbf2
3 changed files with 56 additions and 23 deletions

View file

@ -123,7 +123,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.version = version
self.vhosts = None
self._enhance_func = {"redirect": self._enable_redirect,
"hsts": self._enable_hsts}
"http-header": self._set_http_header}
@property
def mod_ssl_conf(self):
@ -682,7 +682,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
############################################################################
def supported_enhancements(self): # pylint: disable=no-self-use
"""Returns currently supported enhancements."""
return ["redirect", "hsts"]
return ["redirect", "http-header"]
def enhance(self, domain, enhancement, options=None):
"""Enhance configuration.
@ -709,7 +709,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.warn("Failed %s for %s", enhancement, domain)
raise
def _enable_hsts(self, ssl_vhost, unused_options):
def _set_http_header(self, ssl_vhost, header_name):
# TODO REWRITE COMMENT
"""Enables the HSTS header on all HTTP responses.
.. note:: HSTS defends against SSL stripping attacks.
@ -736,18 +737,22 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
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)
# Check if selected header is already set
self._verify_no_http_header(ssl_vhost, header_name)
# 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.parser.add_dir(ssl_vhost.path, "Header",
constants.HEADER_ARGS[header_name])
self.save_notes += ("Adding %s header to ssl vhost in %s\n" %
(header_name, ssl_vhost.filep))
self.save()
logger.info("Adding HSTS header to every response from ssl vhost in %s",
logger.info("Adding %s header to ssl vhost in %s", header_name,
ssl_vhost.filep)
def _verify_no_hsts_header(self, ssl_vhost):
def _verify_no_http_header(self, ssl_vhost, header_name):
# TODO revise comment
"""Checks to see if existing HSTS settings is in place.
Checks to see if virtualhost already contains a HSTS header
@ -765,15 +770,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
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")
if self.aug.get(match) == header_name.lower():
raise errors.PluginError("Existing %s header" %
(header_name))
def _enable_redirect(self, ssl_vhost, unused_options):
"""Redirect all equivalent HTTP traffic to ssl_vhost.

View file

@ -28,8 +28,14 @@ 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",
HSTS_ARGS = ["always", "set", "Strict-Transport-Security",
"\"max-age=31536000; includeSubDomains\""]
"""Apache header arguments for HSTS"""
UIR_ARGS = ["always", "set", "Content-Security-Policy",
"upgrade-insecure-requests"]
HEADER_ARGS = {"Strict-Transport-Security" : HSTS_ARGS,
"Upgrade-Insecure-Requests" : UIR_ARGS}

View file

@ -15,6 +15,7 @@ from letsencrypt import errors
from letsencrypt.tests import acme_util
from letsencrypt_apache import configurator
from letsencrypt_apache import constants
from letsencrypt_apache import obj
from letsencrypt_apache.tests import util
@ -509,13 +510,14 @@ class TwoVhost80Test(util.ApacheTest):
@mock.patch("letsencrypt.le_util.run_script")
@mock.patch("letsencrypt.le_util.exe_exists")
def test_hsts(self, mock_exe, _):
def test_http_header_hsts(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
mock_exe.return_value = True
# This will create an ssl vhost for letsencrypt.demo
self.config.enhance("letsencrypt.demo", "hsts")
self.config.enhance("letsencrypt.demo", "http-header",
"Strict-Transport-Security")
self.assertTrue("headers_module" in self.config.parser.modules)
@ -526,6 +528,31 @@ class TwoVhost80Test(util.ApacheTest):
# load(). They must be found in sites-available
hsts_header = self.config.parser.find_dir(
"Header", None, ssl_vhost.path)
# four args to HSTS header
self.assertEqual(len(hsts_header), 4)
@mock.patch("letsencrypt.le_util.run_script")
@mock.patch("letsencrypt.le_util.exe_exists")
def test_http_header_hsts_with_conflict(self, mock_exe, _):
mock_exe.return_value = True
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
self.config.parser.add_dir(
ssl_vhost.path, "Header", constants.HEADER_ARGS[
"Strict-Transport-Security"])
# This will create an ssl vhost for letsencrypt.demo
self.config.enhance(self.vh_truth[3].name, "http-header",
"Strict-Transport-Security")
# These are not immediately available in find_dir even with save() and
# load(). They must be found in sites-available
hsts_header = self.config.parser.find_dir(
"Header", None, ssl_vhost.path)
# four args to HSTS header
self.assertEqual(len(hsts_header), 4)