mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Merge pull request #5417 from certbot/apache-http
HTTP01 support in Apache
This commit is contained in:
commit
5d58a3d847
7 changed files with 427 additions and 50 deletions
|
|
@ -24,9 +24,10 @@ from certbot_apache import apache_util
|
|||
from certbot_apache import augeas_configurator
|
||||
from certbot_apache import constants
|
||||
from certbot_apache import display_ops
|
||||
from certbot_apache import tls_sni_01
|
||||
from certbot_apache import http_01
|
||||
from certbot_apache import obj
|
||||
from certbot_apache import parser
|
||||
from certbot_apache import tls_sni_01
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
|
@ -163,6 +164,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
"ensure-http-header": self._set_http_header,
|
||||
"staple-ocsp": self._enable_ocsp_stapling}
|
||||
|
||||
# This will be set during the perform function
|
||||
self.http_doer = None
|
||||
|
||||
@property
|
||||
def mod_ssl_conf(self):
|
||||
"""Full absolute path to SSL configuration file."""
|
||||
|
|
@ -736,31 +740,43 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
|
||||
"""
|
||||
|
||||
# If nonstandard port, add service definition for matching
|
||||
if port != "443":
|
||||
self.prepare_https_modules(temp)
|
||||
self.ensure_listen(port, https=True)
|
||||
|
||||
def ensure_listen(self, port, https=False):
|
||||
"""Make sure that Apache is listening on the port. Checks if the
|
||||
Listen statement for the port already exists, and adds it to the
|
||||
configuration if necessary.
|
||||
|
||||
:param str port: Port number to check and add Listen for if not in
|
||||
place already
|
||||
:param bool https: If the port will be used for HTTPS
|
||||
|
||||
"""
|
||||
|
||||
# If HTTPS requested for nonstandard port, add service definition
|
||||
if https and port != "443":
|
||||
port_service = "%s %s" % (port, "https")
|
||||
else:
|
||||
port_service = port
|
||||
|
||||
self.prepare_https_modules(temp)
|
||||
# Check for Listen <port>
|
||||
# Note: This could be made to also look for ip:443 combo
|
||||
listens = [self.parser.get_arg(x).split()[0] for
|
||||
x in self.parser.find_dir("Listen")]
|
||||
|
||||
# In case no Listens are set (which really is a broken apache config)
|
||||
if not listens:
|
||||
listens = ["80"]
|
||||
|
||||
# Listen already in place
|
||||
if self._has_port_already(listens, port):
|
||||
return
|
||||
|
||||
listen_dirs = set(listens)
|
||||
|
||||
if not listens:
|
||||
listen_dirs.add(port_service)
|
||||
|
||||
for listen in listens:
|
||||
# For any listen statement, check if the machine also listens on
|
||||
# Port 443. If not, add such a listen statement.
|
||||
# the given port. If not, add such a listen statement.
|
||||
if len(listen.split(":")) == 1:
|
||||
# Its listening to all interfaces
|
||||
if port not in listen_dirs and port_service not in listen_dirs:
|
||||
|
|
@ -772,11 +788,39 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
if "%s:%s" % (ip, port_service) not in listen_dirs and (
|
||||
"%s:%s" % (ip, port_service) not in listen_dirs):
|
||||
listen_dirs.add("%s:%s" % (ip, port_service))
|
||||
self._add_listens(listen_dirs, listens, port)
|
||||
if https:
|
||||
self._add_listens_https(listen_dirs, listens, port)
|
||||
else:
|
||||
self._add_listens_http(listen_dirs, listens, port)
|
||||
|
||||
def _add_listens(self, listens, listens_orig, port):
|
||||
"""Helper method for prepare_server_https to figure out which new
|
||||
listen statements need adding
|
||||
def _add_listens_http(self, listens, listens_orig, port):
|
||||
"""Helper method for ensure_listen to figure out which new
|
||||
listen statements need adding for listening HTTP on port
|
||||
|
||||
:param set listens: Set of all needed Listen statements
|
||||
:param list listens_orig: List of existing listen statements
|
||||
:param string port: Port number we're adding
|
||||
"""
|
||||
|
||||
new_listens = listens.difference(listens_orig)
|
||||
|
||||
if port in new_listens:
|
||||
# We have wildcard, skip the rest
|
||||
self.parser.add_dir(parser.get_aug_path(self.parser.loc["listen"]),
|
||||
"Listen", port)
|
||||
self.save_notes += "Added Listen %s directive to %s\n" % (
|
||||
port, self.parser.loc["listen"])
|
||||
else:
|
||||
for listen in new_listens:
|
||||
self.parser.add_dir(parser.get_aug_path(
|
||||
self.parser.loc["listen"]), "Listen", listen.split(" "))
|
||||
self.save_notes += ("Added Listen %s directive to "
|
||||
"%s\n") % (listen,
|
||||
self.parser.loc["listen"])
|
||||
|
||||
def _add_listens_https(self, listens, listens_orig, port):
|
||||
"""Helper method for ensure_listen to figure out which new
|
||||
listen statements need adding for listening HTTPS on port
|
||||
|
||||
:param set listens: Set of all needed Listen statements
|
||||
:param list listens_orig: List of existing listen statements
|
||||
|
|
@ -1855,7 +1899,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
###########################################################################
|
||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||
"""Return list of challenge preferences."""
|
||||
return [challenges.TLSSNI01]
|
||||
return [challenges.TLSSNI01, challenges.HTTP01]
|
||||
|
||||
def perform(self, achalls):
|
||||
"""Perform the configuration related challenge.
|
||||
|
|
@ -1867,16 +1911,21 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
"""
|
||||
self._chall_out.update(achalls)
|
||||
responses = [None] * len(achalls)
|
||||
chall_doer = tls_sni_01.ApacheTlsSni01(self)
|
||||
self.http_doer = http_01.ApacheHttp01(self)
|
||||
sni_doer = tls_sni_01.ApacheTlsSni01(self)
|
||||
|
||||
for i, achall in enumerate(achalls):
|
||||
# Currently also have chall_doer hold associated index of the
|
||||
# challenge. This helps to put all of the responses back together
|
||||
# when they are all complete.
|
||||
chall_doer.add_chall(achall, i)
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
self.http_doer.add_chall(achall, i)
|
||||
else: # tls-sni-01
|
||||
sni_doer.add_chall(achall, i)
|
||||
|
||||
sni_response = chall_doer.perform()
|
||||
if sni_response:
|
||||
http_response = self.http_doer.perform()
|
||||
sni_response = sni_doer.perform()
|
||||
if http_response or sni_response:
|
||||
# Must reload in order to activate the challenges.
|
||||
# Handled here because we may be able to load up other challenge
|
||||
# types
|
||||
|
|
@ -1886,14 +1935,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
# of identifying when the new configuration is being used.
|
||||
time.sleep(3)
|
||||
|
||||
# Go through all of the challenges and assign them to the proper
|
||||
# place in the responses return value. All responses must be in the
|
||||
# same order as the original challenges.
|
||||
for i, resp in enumerate(sni_response):
|
||||
responses[chall_doer.indices[i]] = resp
|
||||
self._update_responses(responses, http_response, self.http_doer)
|
||||
self._update_responses(responses, sni_response, sni_doer)
|
||||
|
||||
return responses
|
||||
|
||||
def _update_responses(self, responses, chall_response, chall_doer):
|
||||
# Go through all of the challenges and assign them to the proper
|
||||
# place in the responses return value. All responses must be in the
|
||||
# same order as the original challenges.
|
||||
for i, resp in enumerate(chall_response):
|
||||
responses[chall_doer.indices[i]] = resp
|
||||
|
||||
def cleanup(self, achalls):
|
||||
"""Revert all challenges."""
|
||||
self._chall_out.difference_update(achalls)
|
||||
|
|
@ -1903,6 +1956,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
self.revert_challenge_config()
|
||||
self.restart()
|
||||
self.parser.reset_modules()
|
||||
self.http_doer.cleanup()
|
||||
|
||||
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
|
||||
"""Copy Certbot's SSL options file into the system's config dir if required."""
|
||||
|
|
|
|||
112
certbot-apache/certbot_apache/http_01.py
Normal file
112
certbot-apache/certbot_apache/http_01.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""A class that performs HTTP-01 challenges for Apache"""
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from certbot.plugins import common
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ApacheHttp01(common.TLSSNI01):
|
||||
"""Class that performs HTPP-01 challenges within the Apache configurator."""
|
||||
|
||||
CONFIG_TEMPLATE24 = """\
|
||||
Alias /.well-known/acme-challenge {0}
|
||||
|
||||
<Directory {0} >
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
"""
|
||||
|
||||
CONFIG_TEMPLATE22 = """\
|
||||
Alias /.well-known/acme-challenge {0}
|
||||
|
||||
<Directory {0} >
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApacheHttp01, self).__init__(*args, **kwargs)
|
||||
self.challenge_conf = os.path.join(
|
||||
self.configurator.conf("challenge-location"),
|
||||
"le_http_01_challenge.conf")
|
||||
self.challenge_dir = None
|
||||
|
||||
def perform(self):
|
||||
"""Perform all HTTP-01 challenges."""
|
||||
if not self.achalls:
|
||||
return []
|
||||
# Save any changes to the configuration as a precaution
|
||||
# About to make temporary changes to the config
|
||||
self.configurator.save("Changes before challenge setup", True)
|
||||
|
||||
self.configurator.ensure_listen(str(
|
||||
self.configurator.config.http01_port))
|
||||
self.prepare_http01_modules()
|
||||
|
||||
responses = self._set_up_challenges()
|
||||
self._mod_config()
|
||||
# Save reversible changes
|
||||
self.configurator.save("HTTP Challenge", True)
|
||||
|
||||
return responses
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup the challenge directory."""
|
||||
if self.challenge_dir:
|
||||
shutil.rmtree(self.challenge_dir, ignore_errors=True)
|
||||
self.challenge_dir = None
|
||||
|
||||
def prepare_http01_modules(self):
|
||||
"""Make sure that we have the needed modules available for http01"""
|
||||
|
||||
if self.configurator.conf("handle-modules"):
|
||||
needed_modules = ["alias"]
|
||||
if self.configurator.version < (2, 4):
|
||||
needed_modules.append("authz_host")
|
||||
else:
|
||||
needed_modules.append("authz_core")
|
||||
for mod in needed_modules:
|
||||
if mod + "_module" not in self.configurator.parser.modules:
|
||||
self.configurator.enable_mod(mod, temp=True)
|
||||
|
||||
def _mod_config(self):
|
||||
self.configurator.parser.add_include(
|
||||
self.configurator.parser.loc["default"], self.challenge_conf)
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
if self.configurator.version < (2, 4):
|
||||
config_template = self.CONFIG_TEMPLATE22
|
||||
else:
|
||||
config_template = self.CONFIG_TEMPLATE24
|
||||
config_text = config_template.format(self.challenge_dir)
|
||||
|
||||
logger.debug("writing a config file with text:\n %s", config_text)
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
new_conf.write(config_text)
|
||||
|
||||
def _set_up_challenges(self):
|
||||
self.challenge_dir = tempfile.mkdtemp()
|
||||
os.chmod(self.challenge_dir, 0o755)
|
||||
|
||||
responses = []
|
||||
for achall in self.achalls:
|
||||
responses.append(self._set_up_challenge(achall))
|
||||
|
||||
return responses
|
||||
|
||||
def _set_up_challenge(self, achall):
|
||||
response, validation = achall.response_and_validation()
|
||||
|
||||
name = os.path.join(self.challenge_dir, achall.chall.encode("token"))
|
||||
with open(name, 'wb') as f:
|
||||
f.write(validation.encode())
|
||||
os.chmod(name, 0o644)
|
||||
|
||||
return response
|
||||
|
|
@ -140,5 +140,5 @@ class DebianConfigurator(configurator.ApacheConfigurator):
|
|||
"a2dismod are configured correctly for certbot.")
|
||||
|
||||
self.reverter.register_undo_command(
|
||||
temp, [self.conf("dismod"), mod_name])
|
||||
temp, [self.conf("dismod"), "-f", mod_name])
|
||||
util.run_script([self.conf("enmod"), mod_name])
|
||||
|
|
|
|||
|
|
@ -424,6 +424,43 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.assertTrue(self.config.parser.find_dir(
|
||||
"NameVirtualHost", "*:80"))
|
||||
|
||||
def test_add_listen_80(self):
|
||||
mock_find = mock.Mock()
|
||||
mock_add_dir = mock.Mock()
|
||||
mock_find.return_value = []
|
||||
self.config.parser.find_dir = mock_find
|
||||
self.config.parser.add_dir = mock_add_dir
|
||||
self.config.ensure_listen("80")
|
||||
self.assertTrue(mock_add_dir.called)
|
||||
self.assertTrue(mock_find.called)
|
||||
self.assertEqual(mock_add_dir.call_args[0][1], "Listen")
|
||||
self.assertEqual(mock_add_dir.call_args[0][2], "80")
|
||||
|
||||
def test_add_listen_80_named(self):
|
||||
mock_find = mock.Mock()
|
||||
mock_find.return_value = ["test1", "test2", "test3"]
|
||||
mock_get = mock.Mock()
|
||||
mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
|
||||
mock_add_dir = mock.Mock()
|
||||
|
||||
self.config.parser.find_dir = mock_find
|
||||
self.config.parser.get_arg = mock_get
|
||||
self.config.parser.add_dir = mock_add_dir
|
||||
|
||||
self.config.ensure_listen("80")
|
||||
self.assertEqual(mock_add_dir.call_count, 0)
|
||||
|
||||
# Reset return lists and inputs
|
||||
mock_add_dir.reset_mock()
|
||||
mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
|
||||
|
||||
# Test
|
||||
self.config.ensure_listen("8080")
|
||||
self.assertEqual(mock_add_dir.call_count, 3)
|
||||
self.assertTrue(mock_add_dir.called)
|
||||
self.assertEqual(mock_add_dir.call_args[0][1], "Listen")
|
||||
self.assertEqual(mock_add_dir.call_args[0][2], ['1.2.3.4:8080'])
|
||||
|
||||
def test_prepare_server_https(self):
|
||||
mock_enable = mock.Mock()
|
||||
self.config.enable_mod = mock_enable
|
||||
|
|
@ -435,7 +472,6 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
# This will test the Add listen
|
||||
self.config.parser.find_dir = mock_find
|
||||
self.config.parser.add_dir_to_ifmodssl = mock_add_dir
|
||||
|
||||
self.config.prepare_server_https("443")
|
||||
# Changing the order these modules are enabled breaks the reverter
|
||||
self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb")
|
||||
|
|
@ -676,23 +712,33 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.config._add_name_vhost_if_necessary(self.vh_truth[0])
|
||||
self.assertEqual(self.config.add_name_vhost.call_count, 2)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform")
|
||||
@mock.patch("certbot_apache.configurator.tls_sni_01.ApacheTlsSni01.perform")
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
|
||||
def test_perform(self, mock_restart, mock_perform):
|
||||
def test_perform(self, mock_restart, mock_tls_perform, mock_http_perform):
|
||||
# Only tests functionality specific to configurator.perform
|
||||
# Note: As more challenges are offered this will have to be expanded
|
||||
account_key, achall1, achall2 = self.get_achalls()
|
||||
account_key, achalls = self.get_key_and_achalls()
|
||||
|
||||
expected = [
|
||||
achall1.response(account_key),
|
||||
achall2.response(account_key),
|
||||
]
|
||||
all_expected = []
|
||||
http_expected = []
|
||||
tls_expected = []
|
||||
for achall in achalls:
|
||||
response = achall.response(account_key)
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
http_expected.append(response)
|
||||
else:
|
||||
tls_expected.append(response)
|
||||
all_expected.append(response)
|
||||
|
||||
mock_perform.return_value = expected
|
||||
responses = self.config.perform([achall1, achall2])
|
||||
mock_http_perform.return_value = http_expected
|
||||
mock_tls_perform.return_value = tls_expected
|
||||
|
||||
self.assertEqual(mock_perform.call_count, 1)
|
||||
self.assertEqual(responses, expected)
|
||||
responses = self.config.perform(achalls)
|
||||
|
||||
self.assertEqual(mock_http_perform.call_count, 1)
|
||||
self.assertEqual(mock_tls_perform.call_count, 1)
|
||||
self.assertEqual(responses, all_expected)
|
||||
|
||||
self.assertEqual(mock_restart.call_count, 1)
|
||||
|
||||
|
|
@ -700,30 +746,38 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
|
||||
def test_cleanup(self, mock_cfg, mock_restart):
|
||||
mock_cfg.return_value = ""
|
||||
_, achall1, achall2 = self.get_achalls()
|
||||
_, achalls = self.get_key_and_achalls()
|
||||
self.config.http_doer = mock.MagicMock()
|
||||
|
||||
self.config._chall_out.add(achall1) # pylint: disable=protected-access
|
||||
self.config._chall_out.add(achall2) # pylint: disable=protected-access
|
||||
for achall in achalls:
|
||||
self.config._chall_out.add(achall) # pylint: disable=protected-access
|
||||
|
||||
self.config.cleanup([achall1])
|
||||
self.assertFalse(mock_restart.called)
|
||||
|
||||
self.config.cleanup([achall2])
|
||||
self.assertTrue(mock_restart.called)
|
||||
for i, achall in enumerate(achalls):
|
||||
self.config.cleanup([achall])
|
||||
if i == len(achalls) - 1:
|
||||
self.assertTrue(mock_restart.called)
|
||||
self.assertTrue(self.config.http_doer.cleanup.called)
|
||||
else:
|
||||
self.assertFalse(mock_restart.called)
|
||||
self.assertFalse(self.config.http_doer.cleanup.called)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
|
||||
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
|
||||
def test_cleanup_no_errors(self, mock_cfg, mock_restart):
|
||||
mock_cfg.return_value = ""
|
||||
_, achall1, achall2 = self.get_achalls()
|
||||
_, achalls = self.get_key_and_achalls()
|
||||
self.config.http_doer = mock.MagicMock()
|
||||
|
||||
self.config._chall_out.add(achall1) # pylint: disable=protected-access
|
||||
for achall in achalls:
|
||||
self.config._chall_out.add(achall) # pylint: disable=protected-access
|
||||
|
||||
self.config.cleanup([achall2])
|
||||
self.config.cleanup([achalls[-1]])
|
||||
self.assertFalse(mock_restart.called)
|
||||
self.assertFalse(self.config.http_doer.cleanup.called)
|
||||
|
||||
self.config.cleanup([achall1, achall2])
|
||||
self.config.cleanup(achalls)
|
||||
self.assertTrue(mock_restart.called)
|
||||
self.assertTrue(self.config.http_doer.cleanup.called)
|
||||
|
||||
@mock.patch("certbot.util.run_script")
|
||||
def test_get_version(self, mock_script):
|
||||
|
|
@ -1151,7 +1205,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
not_rewriterule = "NotRewriteRule ^ ..."
|
||||
self.assertFalse(self.config._sift_rewrite_rule(not_rewriterule))
|
||||
|
||||
def get_achalls(self):
|
||||
def get_key_and_achalls(self):
|
||||
"""Return testing achallenges."""
|
||||
account_key = self.rsa512jwk
|
||||
achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
|
|
@ -1166,8 +1220,12 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"),
|
||||
"pending"),
|
||||
domain="certbot.demo", account_key=account_key)
|
||||
achall3 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.HTTP01(token=(b'x' * 16)), "pending"),
|
||||
domain="example.org", account_key=account_key)
|
||||
|
||||
return account_key, achall1, achall2
|
||||
return account_key, (achall1, achall2, achall3)
|
||||
|
||||
def test_make_addrs_sni_ready(self):
|
||||
self.config.version = (2, 2)
|
||||
|
|
|
|||
152
certbot-apache/certbot_apache/tests/http_01_test.py
Normal file
152
certbot-apache/certbot_apache/tests/http_01_test.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""Test for certbot_apache.http_01."""
|
||||
import mock
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import achallenges
|
||||
|
||||
from certbot.tests import acme_util
|
||||
|
||||
from certbot_apache.tests import util
|
||||
|
||||
|
||||
NUM_ACHALLS = 3
|
||||
|
||||
|
||||
class ApacheHttp01TestMeta(type):
|
||||
"""Generates parmeterized tests for testing perform."""
|
||||
def __new__(mcs, name, bases, class_dict):
|
||||
|
||||
def _gen_test(num_achalls, minor_version):
|
||||
def _test(self):
|
||||
achalls = self.achalls[:num_achalls]
|
||||
self.config.version = (2, minor_version)
|
||||
self.common_perform_test(achalls)
|
||||
return _test
|
||||
|
||||
for i in range(1, NUM_ACHALLS + 1):
|
||||
for j in (2, 4):
|
||||
test_name = "test_perform_{0}_{1}".format(i, j)
|
||||
class_dict[test_name] = _gen_test(i, j)
|
||||
return type.__new__(mcs, name, bases, class_dict)
|
||||
|
||||
|
||||
class ApacheHttp01Test(util.ApacheTest):
|
||||
"""Test for certbot_apache.http_01.ApacheHttp01."""
|
||||
|
||||
__metaclass__ = ApacheHttp01TestMeta
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
super(ApacheHttp01Test, self).setUp(*args, **kwargs)
|
||||
|
||||
self.account_key = self.rsa512jwk
|
||||
self.achalls = []
|
||||
for i in range(NUM_ACHALLS):
|
||||
self.achalls.append(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.HTTP01(token=((chr(ord('a') + i) * 16))),
|
||||
"pending"),
|
||||
domain="example{0}.com".format(i),
|
||||
account_key=self.account_key))
|
||||
|
||||
modules = ["alias", "authz_core", "authz_host"]
|
||||
for mod in modules:
|
||||
self.config.parser.modules.add("mod_{0}.c".format(mod))
|
||||
self.config.parser.modules.add(mod + "_module")
|
||||
|
||||
from certbot_apache.http_01 import ApacheHttp01
|
||||
self.http = ApacheHttp01(self.config)
|
||||
|
||||
def test_empty_perform(self):
|
||||
self.assertFalse(self.http.perform())
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_22(self, mock_enmod):
|
||||
self.config.version = (2, 2)
|
||||
self.config.parser.modules.remove("authz_host_module")
|
||||
self.config.parser.modules.remove("mod_authz_host.c")
|
||||
|
||||
enmod_calls = self.common_enable_modules_test(mock_enmod)
|
||||
self.assertEqual(enmod_calls[0][0][0], "authz_host")
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_24(self, mock_enmod):
|
||||
self.config.parser.modules.remove("authz_core_module")
|
||||
self.config.parser.modules.remove("mod_authz_core.c")
|
||||
|
||||
enmod_calls = self.common_enable_modules_test(mock_enmod)
|
||||
self.assertEqual(enmod_calls[0][0][0], "authz_core")
|
||||
|
||||
def common_enable_modules_test(self, mock_enmod):
|
||||
"""Tests enabling mod_alias and other modules."""
|
||||
self.config.parser.modules.remove("alias_module")
|
||||
self.config.parser.modules.remove("mod_alias.c")
|
||||
|
||||
self.http.prepare_http01_modules()
|
||||
|
||||
self.assertTrue(mock_enmod.called)
|
||||
calls = mock_enmod.call_args_list
|
||||
other_calls = []
|
||||
for call in calls:
|
||||
if "alias" != call[0][0]:
|
||||
other_calls.append(call)
|
||||
|
||||
# If these lists are equal, we never enabled mod_alias
|
||||
self.assertNotEqual(calls, other_calls)
|
||||
return other_calls
|
||||
|
||||
def common_perform_test(self, achalls):
|
||||
"""Tests perform with the given achalls."""
|
||||
for achall in achalls:
|
||||
self.http.add_chall(achall)
|
||||
|
||||
expected_response = [
|
||||
achall.response(self.account_key) for achall in achalls]
|
||||
self.assertEqual(self.http.perform(), expected_response)
|
||||
|
||||
self.assertTrue(os.path.isdir(self.http.challenge_dir))
|
||||
self._has_min_permissions(self.http.challenge_dir, 0o755)
|
||||
self._test_challenge_conf()
|
||||
|
||||
for achall in achalls:
|
||||
self._test_challenge_file(achall)
|
||||
|
||||
challenge_dir = self.http.challenge_dir
|
||||
self.http.cleanup()
|
||||
self.assertFalse(os.path.exists(challenge_dir))
|
||||
|
||||
def _test_challenge_conf(self):
|
||||
self.assertEqual(
|
||||
len(self.config.parser.find_dir(
|
||||
"Include", self.http.challenge_conf)), 1)
|
||||
|
||||
with open(self.http.challenge_conf) as f:
|
||||
conf_contents = f.read()
|
||||
|
||||
alias_fmt = "Alias /.well-known/acme-challenge {0}"
|
||||
alias = alias_fmt.format(self.http.challenge_dir)
|
||||
self.assertTrue(alias in conf_contents)
|
||||
if self.config.version < (2, 4):
|
||||
self.assertTrue("Allow from all" in conf_contents)
|
||||
else:
|
||||
self.assertTrue("Require all granted" in conf_contents)
|
||||
|
||||
def _test_challenge_file(self, achall):
|
||||
name = os.path.join(self.http.challenge_dir, achall.chall.encode("token"))
|
||||
validation = achall.validation(self.account_key)
|
||||
|
||||
self._has_min_permissions(name, 0o644)
|
||||
with open(name, 'rb') as f:
|
||||
self.assertEqual(f.read(), validation.encode())
|
||||
|
||||
def _has_min_permissions(self, path, min_mode):
|
||||
"""Tests the given file has at least the permissions in mode."""
|
||||
st_mode = os.stat(path).st_mode
|
||||
self.assertEqual(st_mode, st_mode | min_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"""Test for certbot_apache.tls_sni_01."""
|
||||
import unittest
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
|
|||
apache_challenge_location=config_path,
|
||||
backup_dir=backups,
|
||||
config_dir=config_dir,
|
||||
http01_port=80,
|
||||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
work_dir=work_dir)
|
||||
|
|
|
|||
Loading…
Reference in a new issue