mirror of
https://github.com/certbot/certbot.git
synced 2026-08-06 21:34:58 +02:00
Merged in master
This commit is contained in:
+78
-21
@@ -16,6 +16,7 @@ from letsencrypt_compatibility_test.configurators import common as configurators
|
||||
|
||||
|
||||
APACHE_VERSION_REGEX = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
||||
APACHE_COMMANDS = ["apachectl", "a2enmod"]
|
||||
|
||||
|
||||
class Proxy(configurators_common.Proxy):
|
||||
@@ -29,24 +30,58 @@ class Proxy(configurators_common.Proxy):
|
||||
super(Proxy, self).__init__(args)
|
||||
self.le_config.apache_le_vhost_ext = "-le-ssl.conf"
|
||||
|
||||
self._patches = list()
|
||||
subprocess_patch = mock.patch(
|
||||
"letsencrypt_apache.configurator.subprocess")
|
||||
subprocess_mock = subprocess_patch.start()
|
||||
subprocess_mock.check_call = self.check_call_in_docker
|
||||
subprocess_mock.Popen = self.popen_in_docker
|
||||
self._patches.append(subprocess_patch)
|
||||
|
||||
display_patch = mock.patch(
|
||||
"letsencrypt_apache.configurator.display_ops.select_vhost")
|
||||
display_mock = display_patch.start()
|
||||
display_mock.side_effect = le_errors.PluginError(
|
||||
"Unable to determine vhost")
|
||||
self._patches.append(display_mock)
|
||||
self._setup_mock()
|
||||
|
||||
self.modules = self.server_root = self.test_conf = self.version = None
|
||||
self._apache_configurator = self._all_names = self._test_names = None
|
||||
|
||||
def _setup_mock(self):
|
||||
"""Replaces specific modules with mock.MagicMock"""
|
||||
mock_subprocess = mock.MagicMock()
|
||||
mock_subprocess.check_call = self.check_call
|
||||
mock_subprocess.Popen = self.popen
|
||||
|
||||
mock.patch(
|
||||
"letsencrypt_apache.configurator.subprocess",
|
||||
mock_subprocess).start()
|
||||
mock.patch(
|
||||
"letsencrypt_apache.parser.subprocess",
|
||||
mock_subprocess).start()
|
||||
mock.patch(
|
||||
"letsencrypt.le_util.subprocess",
|
||||
mock_subprocess).start()
|
||||
mock.patch(
|
||||
"letsencrypt_apache.configurator.le_util.exe_exists",
|
||||
_is_apache_command).start()
|
||||
|
||||
patch = mock.patch(
|
||||
"letsencrypt_apache.configurator.display_ops.select_vhost")
|
||||
mock_display = patch.start()
|
||||
mock_display.side_effect = le_errors.PluginError(
|
||||
"Unable to determine vhost")
|
||||
|
||||
def check_call(self, command, *args, **kwargs):
|
||||
"""If command is an Apache command, command is executed in the
|
||||
running docker image. Otherwise, subprocess.check_call is used.
|
||||
|
||||
"""
|
||||
if _is_apache_command(command):
|
||||
command = _modify_command(command)
|
||||
return super(Proxy, self).check_call(command, *args, **kwargs)
|
||||
else:
|
||||
return subprocess.check_call(command, *args, **kwargs)
|
||||
|
||||
def popen(self, command, *args, **kwargs):
|
||||
"""If command is an Apache command, command is executed in the
|
||||
running docker image. Otherwise, subprocess.Popen is used.
|
||||
|
||||
"""
|
||||
if _is_apache_command(command):
|
||||
command = _modify_command(command)
|
||||
return super(Proxy, self).popen(command, *args, **kwargs)
|
||||
else:
|
||||
return subprocess.Popen(command, *args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Wraps the Apache Configurator methods"""
|
||||
method = getattr(self._apache_configurator, name, None)
|
||||
@@ -59,8 +94,7 @@ class Proxy(configurators_common.Proxy):
|
||||
"""Loads the next configuration for the plugin to test"""
|
||||
if hasattr(self.le_config, "apache_init_script"):
|
||||
try:
|
||||
self.check_call_in_docker(
|
||||
[self.le_config.apache_init_script, "stop"])
|
||||
self.check_call([self.le_config.apache_init_script, "stop"])
|
||||
except errors.Error:
|
||||
raise errors.Error(
|
||||
"Failed to stop previous apache config from running")
|
||||
@@ -79,9 +113,8 @@ class Proxy(configurators_common.Proxy):
|
||||
self._prepare_configurator(server_root, config_file)
|
||||
|
||||
try:
|
||||
self.check_call_in_docker(
|
||||
"apachectl -d {0} -f {1} -k start".format(
|
||||
server_root, config_file))
|
||||
self.check_call("apachectl -d {0} -f {1} -k start".format(
|
||||
server_root, config_file))
|
||||
except errors.Error:
|
||||
raise errors.Error(
|
||||
"Apache failed to load {0} before tests started".format(
|
||||
@@ -115,6 +148,7 @@ class Proxy(configurators_common.Proxy):
|
||||
self.le_config.apache_ctl = "apachectl -d {0} -f {1}".format(
|
||||
server_root, config_file)
|
||||
self.le_config.apache_enmod = "a2enmod.sh {0}".format(server_root)
|
||||
self.le_config.apache_dismod = self.le_config.apache_enmod
|
||||
self.le_config.apache_init_script = self.le_config.apache_ctl + " -k"
|
||||
|
||||
self._apache_configurator = configurator.ApacheConfigurator(
|
||||
@@ -125,8 +159,7 @@ class Proxy(configurators_common.Proxy):
|
||||
def cleanup_from_tests(self):
|
||||
"""Performs any necessary cleanup from running plugin tests"""
|
||||
super(Proxy, self).cleanup_from_tests()
|
||||
for patch in self._patches:
|
||||
patch.stop()
|
||||
mock.patch.stopall()
|
||||
|
||||
def get_all_names_answer(self):
|
||||
"""Returns the set of domain names that the plugin should find"""
|
||||
@@ -150,6 +183,30 @@ class Proxy(configurators_common.Proxy):
|
||||
domain, cert_path, key_path, chain_path)
|
||||
|
||||
|
||||
def _is_apache_command(command):
|
||||
"""Returns true if command is an Apache command"""
|
||||
if isinstance(command, list):
|
||||
command = command[0]
|
||||
|
||||
for apache_command in APACHE_COMMANDS:
|
||||
if command.startswith(apache_command):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _modify_command(command):
|
||||
"""Modifies command so configtest works inside the docker image"""
|
||||
if isinstance(command, list):
|
||||
for i in xrange(len(command)):
|
||||
if command[i] == "configtest":
|
||||
command[i] = "-t"
|
||||
else:
|
||||
command = command.replace("configtest", "-t")
|
||||
|
||||
return command
|
||||
|
||||
|
||||
def _create_test_conf(server_root, apache_config):
|
||||
"""Creates a test config file and adds it to the Apache config"""
|
||||
test_conf = os.path.join(server_root, "test.conf")
|
||||
|
||||
+6
-16
@@ -3,7 +3,6 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
import docker
|
||||
|
||||
@@ -47,7 +46,7 @@ class Proxy(object):
|
||||
self._docker_client = docker.Client(
|
||||
base_url=self.args.docker_url, version="auto")
|
||||
self.http_port, self.https_port = util.get_two_free_ports()
|
||||
self._container_id = self._log_thread = None
|
||||
self._container_id = None
|
||||
|
||||
def has_more_configs(self):
|
||||
"""Returns true if there are more configs to test"""
|
||||
@@ -56,7 +55,6 @@ class Proxy(object):
|
||||
def cleanup_from_tests(self):
|
||||
"""Performs any necessary cleanup from running plugin tests"""
|
||||
self._docker_client.stop(self._container_id, 0)
|
||||
self._log_thread.join()
|
||||
if not self.args.no_remove:
|
||||
self._docker_client.remove_container(self._container_id)
|
||||
|
||||
@@ -87,26 +85,18 @@ class Proxy(object):
|
||||
self._container_id = container["Id"]
|
||||
self._docker_client.start(self._container_id)
|
||||
|
||||
self._log_thread = threading.Thread(target=self._start_log_thread)
|
||||
self._log_thread.start()
|
||||
|
||||
def _start_log_thread(self):
|
||||
client = docker.Client(base_url=self.args.docker_url, version="auto")
|
||||
for line in client.logs(self._container_id, stream=True):
|
||||
logger.debug(line.rstrip())
|
||||
|
||||
def check_call_in_docker(
|
||||
self, command, *args, **kwargs): # pylint: disable=unused-argument
|
||||
def check_call(self, command, *args, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
"""Simulates a call to check_call but executes the command in the
|
||||
running docker image
|
||||
|
||||
"""
|
||||
if self.popen_in_docker(command).returncode:
|
||||
if self.popen(command).returncode:
|
||||
raise errors.Error(
|
||||
"{0} exited with a nonzero value".format(command))
|
||||
|
||||
def popen_in_docker(
|
||||
self, command, *args, **kwargs): # pylint: disable=unused-argument
|
||||
def popen(self, command, *args, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
"""Simulates a call to Popen but executes the command in the
|
||||
running docker image
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import OpenSSL
|
||||
|
||||
@@ -60,11 +61,12 @@ def test_authenticator(plugin, config, temp_dir):
|
||||
type(achalls[i]), achalls[i].domain, config)
|
||||
success = False
|
||||
elif isinstance(responses[i], challenges.DVSNIResponse):
|
||||
if responses[i].simple_verify(achalls[i],
|
||||
achalls[i].domain,
|
||||
util.JWK.key.public_key(),
|
||||
host="127.0.0.1",
|
||||
port=plugin.https_port):
|
||||
verify = functools.partial(responses[i].simple_verify, achalls[i],
|
||||
achalls[i].domain,
|
||||
util.JWK.key.public_key(),
|
||||
host="127.0.0.1",
|
||||
port=plugin.https_port)
|
||||
if _try_until_true(verify):
|
||||
logger.info(
|
||||
"DVSNI verification for %s succeeded", achalls[i].domain)
|
||||
else:
|
||||
@@ -149,10 +151,11 @@ def test_deploy_cert(plugin, temp_dir, domains):
|
||||
if not _save_and_restart(plugin, "deployed"):
|
||||
return False
|
||||
|
||||
verify_cert = validator.Validator().certificate
|
||||
success = True
|
||||
for domain in domains:
|
||||
if not verify_cert(cert, domain, "127.0.0.1", plugin.https_port):
|
||||
verify = functools.partial(validator.Validator().certificate, cert,
|
||||
domain, "127.0.0.1", plugin.https_port)
|
||||
if not _try_until_true(verify):
|
||||
logger.error("Could not verify certificate for domain %s", domain)
|
||||
success = False
|
||||
|
||||
@@ -175,18 +178,18 @@ def test_enhancements(plugin, domains):
|
||||
try:
|
||||
plugin.enhance(domain, "redirect")
|
||||
except le_errors.Error as error:
|
||||
logger.error("Plugin failed to enable redirect for %s:", domain)
|
||||
logger.exception(error)
|
||||
return False
|
||||
# Don't immediately fail because a redirect may already be enabled
|
||||
logger.warning("Plugin failed to enable redirect for %s:", domain)
|
||||
logger.warning("%s", error)
|
||||
|
||||
if not _save_and_restart(plugin, "enhanced"):
|
||||
return False
|
||||
|
||||
verify_redirect = functools.partial(
|
||||
validator.Validator().redirect, "localhost", plugin.http_port)
|
||||
success = True
|
||||
for domain in domains:
|
||||
if not verify_redirect(headers={"Host" : domain}):
|
||||
verify = functools.partial(validator.Validator().redirect, "localhost",
|
||||
plugin.http_port, headers={"Host" : domain})
|
||||
if not _try_until_true(verify):
|
||||
logger.error("Improper redirect for domain %s", domain)
|
||||
success = False
|
||||
|
||||
@@ -196,6 +199,17 @@ def test_enhancements(plugin, domains):
|
||||
return success
|
||||
|
||||
|
||||
def _try_until_true(func, max_tries=3):
|
||||
"""Calls func up to max_tries times until it returns True"""
|
||||
for _ in xrange(0, max_tries):
|
||||
if func():
|
||||
return True
|
||||
else:
|
||||
time.sleep(1)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _save_and_restart(plugin, title=None):
|
||||
"""Saves and restart the plugin, returning True if no errors occurred"""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user