mirror of
https://github.com/certbot/certbot.git
synced 2026-07-30 18:04:31 +02:00
Finished authenticator tests
This commit is contained in:
@@ -935,7 +935,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
subprocess.check_call([self.conf("enmod"), mod_name],
|
subprocess.check_call([self.conf("enmod"), mod_name],
|
||||||
stdout=open("/dev/null", "w"),
|
stdout=open("/dev/null", "w"),
|
||||||
stderr=open("/dev/null", "w"))
|
stderr=open("/dev/null", "w"))
|
||||||
apache_restart(self.conf("init"))
|
apache_restart(self.conf("init-script"))
|
||||||
except (OSError, subprocess.CalledProcessError):
|
except (OSError, subprocess.CalledProcessError):
|
||||||
logger.exception("Error enabling mod_%s", mod_name)
|
logger.exception("Error enabling mod_%s", mod_name)
|
||||||
raise errors.MisconfigurationError(
|
raise errors.MisconfigurationError(
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
include compatibility/testdata/rsa1024_key.pem
|
||||||
@@ -12,4 +12,6 @@ ENV APACHE_RUN_USER=daemon \
|
|||||||
|
|
||||||
COPY tests/compatibility/configurators/apache/a2enmod.sh /usr/local/bin/
|
COPY tests/compatibility/configurators/apache/a2enmod.sh /usr/local/bin/
|
||||||
|
|
||||||
CMD [ "httpd-foreground" ]
|
# Note: this only exposes the port to other docker containers. You
|
||||||
|
# still have to bind to 443@host at runtime.
|
||||||
|
EXPOSE 443
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
"""Proxies ApacheConfigurator for Apache 2.4 tests"""
|
"""Proxies ApacheConfigurator for Apache 2.4 tests"""
|
||||||
|
|
||||||
|
import zope.interface
|
||||||
|
|
||||||
from tests.compatibility import errors
|
from tests.compatibility import errors
|
||||||
|
from tests.compatibility import interfaces
|
||||||
from tests.compatibility.configurators.apache import common as apache_common
|
from tests.compatibility.configurators.apache import common as apache_common
|
||||||
|
|
||||||
|
|
||||||
@@ -33,10 +37,14 @@ SHARED_MODULES = {
|
|||||||
class Proxy(apache_common.Proxy):
|
class Proxy(apache_common.Proxy):
|
||||||
"""Wraps the ApacheConfigurator for Apache 2.4 tests"""
|
"""Wraps the ApacheConfigurator for Apache 2.4 tests"""
|
||||||
|
|
||||||
|
zope.interface.implements(interfaces.IConfiguratorProxy)
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Initializes the plugin with the given command line args"""
|
"""Initializes the plugin with the given command line args"""
|
||||||
super(Proxy, self).__init__(args)
|
super(Proxy, self).__init__(args)
|
||||||
self.start_docker("bradmw/apache2.4")
|
# Running init isn't ideal, but the Docker container needs to survive
|
||||||
|
# Apache restarts
|
||||||
|
self.start_docker("bradmw/apache2.4", "init")
|
||||||
|
|
||||||
def preprocess_config(self, server_root):
|
def preprocess_config(self, server_root):
|
||||||
"""Prepares the configuration for use in the Docker"""
|
"""Prepares the configuration for use in the Docker"""
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ class Proxy(configurators_common.Proxy):
|
|||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
"""Loads the next configuration for the plugin to test"""
|
"""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"])
|
||||||
|
except errors.Error:
|
||||||
|
raise errors.Error(
|
||||||
|
"Failed to stop previous apache config from running")
|
||||||
|
|
||||||
config = super(Proxy, self).load_config()
|
config = super(Proxy, self).load_config()
|
||||||
self.modules = _get_modules(config)
|
self.modules = _get_modules(config)
|
||||||
self.version = _get_version(config)
|
self.version = _get_version(config)
|
||||||
@@ -63,7 +71,7 @@ class Proxy(configurators_common.Proxy):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.check_call_in_docker(
|
self.check_call_in_docker(
|
||||||
"apachectl -d {0} -f {1} -k restart".format(
|
"apachectl -d {0} -f {1} -k start".format(
|
||||||
server_root, config_file))
|
server_root, config_file))
|
||||||
except errors.Error:
|
except errors.Error:
|
||||||
raise errors.Error(
|
raise errors.Error(
|
||||||
@@ -93,7 +101,7 @@ class Proxy(configurators_common.Proxy):
|
|||||||
self.le_config.apache_ctl = "apachectl -d {0} -f {1}".format(
|
self.le_config.apache_ctl = "apachectl -d {0} -f {1}".format(
|
||||||
server_root, config_file)
|
server_root, config_file)
|
||||||
self.le_config.apache_enmod = "a2enmod.sh {0}".format(server_root)
|
self.le_config.apache_enmod = "a2enmod.sh {0}".format(server_root)
|
||||||
self.le_config.apache_init = self.le_config.apache_ctl + " -k"
|
self.le_config.apache_init_script = self.le_config.apache_ctl + " -k"
|
||||||
|
|
||||||
self._apache_configurator = configurator.ApacheConfigurator(
|
self._apache_configurator = configurator.ApacheConfigurator(
|
||||||
config=configuration.NamespaceConfig(self.le_config),
|
config=configuration.NamespaceConfig(self.le_config),
|
||||||
@@ -119,6 +127,13 @@ class Proxy(configurators_common.Proxy):
|
|||||||
else:
|
else:
|
||||||
raise errors.Error("No configuration file loaded")
|
raise errors.Error("No configuration file loaded")
|
||||||
|
|
||||||
|
def deploy_cert(self, domain, cert_path, key_path, chain_path=None):
|
||||||
|
"""Installs cert"""
|
||||||
|
cert_path, key_path, chain_path = self.copy_certs_and_keys(
|
||||||
|
cert_path, key_path, chain_path)
|
||||||
|
self._apache_configurator.deploy_cert(
|
||||||
|
domain, cert_path, key_path, chain_path)
|
||||||
|
|
||||||
|
|
||||||
def _create_test_conf(server_root, apache_config):
|
def _create_test_conf(server_root, apache_config):
|
||||||
"""Creates a test config file and adds it to the Apache config"""
|
"""Creates a test config file and adds it to the Apache config"""
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Provides a common base for configurator proxies"""
|
"""Provides a common base for configurator proxies"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -34,10 +35,12 @@ class Proxy(object):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Initializes the plugin with the given command line args"""
|
"""Initializes the plugin with the given command line args"""
|
||||||
temp_dir = tempfile.mkdtemp()
|
self._temp_dir = tempfile.mkdtemp()
|
||||||
self.le_config = util.create_le_config(temp_dir)
|
self.le_config = util.create_le_config(self._temp_dir)
|
||||||
self._config_dir = util.extract_configs(args.configs, temp_dir)
|
config_dir = util.extract_configs(args.configs, self._temp_dir)
|
||||||
self._configs = os.listdir(self._config_dir)
|
self._configs = [
|
||||||
|
os.path.join(config_dir, config)
|
||||||
|
for config in os.listdir(config_dir)]
|
||||||
|
|
||||||
self.args = args
|
self.args = args
|
||||||
self._docker_client = docker.Client(
|
self._docker_client = docker.Client(
|
||||||
@@ -58,21 +61,22 @@ class Proxy(object):
|
|||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
"""Returns the next config directory to be tested"""
|
"""Returns the next config directory to be tested"""
|
||||||
return os.path.join(self._config_dir, self._configs.pop())
|
return self._configs.pop()
|
||||||
|
|
||||||
def start_docker(self, image_name):
|
def start_docker(self, image_name, command):
|
||||||
"""Creates and runs a Docker container with the specified image"""
|
"""Creates and runs a Docker container with the specified image"""
|
||||||
|
logger.info("Pulling Docker image. This may take a minute.")
|
||||||
for line in self._docker_client.pull(image_name, stream=True):
|
for line in self._docker_client.pull(image_name, stream=True):
|
||||||
logger.debug(line)
|
logger.debug(line)
|
||||||
|
|
||||||
host_config = docker.utils.create_host_config(
|
host_config = docker.utils.create_host_config(
|
||||||
binds={
|
binds={
|
||||||
self._config_dir : {"bind" : self._config_dir, "mode" : "rw"}},
|
self._temp_dir : {"bind" : self._temp_dir, "mode" : "rw"}},
|
||||||
port_bindings={
|
port_bindings={
|
||||||
80 : ("127.0.0.1", self.http_port),
|
80 : ("127.0.0.1", self.http_port),
|
||||||
443 : ("127.0.0.1", self.https_port)},)
|
443 : ("127.0.0.1", self.https_port)},)
|
||||||
container = self._docker_client.create_container(
|
container = self._docker_client.create_container(
|
||||||
image_name, ports=[80, 443], volumes=self._config_dir,
|
image_name, command, ports=[80, 443], volumes=self._temp_dir,
|
||||||
host_config=host_config)
|
host_config=host_config)
|
||||||
if container["Warnings"]:
|
if container["Warnings"]:
|
||||||
logger.warning(container["Warnings"])
|
logger.warning(container["Warnings"])
|
||||||
@@ -123,8 +127,25 @@ class Proxy(object):
|
|||||||
|
|
||||||
def execute_in_docker(self, command):
|
def execute_in_docker(self, command):
|
||||||
"""Executes command inside the running docker image"""
|
"""Executes command inside the running docker image"""
|
||||||
logger.info("Executing '%s'", command)
|
logger.debug("Executing '%s'", command)
|
||||||
exec_id = self._docker_client.exec_create(self._container_id, command)
|
exec_id = self._docker_client.exec_create(self._container_id, command)
|
||||||
output = self._docker_client.exec_start(exec_id)
|
output = self._docker_client.exec_start(exec_id)
|
||||||
returncode = self._docker_client.exec_inspect(exec_id)["ExitCode"]
|
returncode = self._docker_client.exec_inspect(exec_id)["ExitCode"]
|
||||||
return returncode, output
|
return returncode, output
|
||||||
|
|
||||||
|
def copy_certs_and_keys(self, cert_path, key_path, chain_path=None):
|
||||||
|
"""Copies certs and keys into the temporary directory"""
|
||||||
|
cert_and_key_dir = os.path.join(self._temp_dir, "certs_and_keys")
|
||||||
|
os.mkdir(cert_and_key_dir)
|
||||||
|
|
||||||
|
cert = os.path.join(cert_and_key_dir, "cert")
|
||||||
|
shutil.copy(cert_path, cert)
|
||||||
|
key = os.path.join(cert_and_key_dir, "key")
|
||||||
|
shutil.copy(key_path, key)
|
||||||
|
if chain_path:
|
||||||
|
chain = os.path.join(cert_and_key_dir, "chain")
|
||||||
|
shutil.copy(chain_path, chain)
|
||||||
|
else:
|
||||||
|
chain = None
|
||||||
|
|
||||||
|
return cert, key, chain
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
"""Tests Let's Encrypt plugins against different server configurations."""
|
"""Tests Let's Encrypt plugins against different server configurations."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import filecmp
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import OpenSSL
|
||||||
|
|
||||||
|
from acme import challenges
|
||||||
|
from acme import crypto_util
|
||||||
|
from acme import messages
|
||||||
|
from letsencrypt import achallenges
|
||||||
|
from letsencrypt.tests import acme_util
|
||||||
from tests.compatibility import errors
|
from tests.compatibility import errors
|
||||||
|
from tests.compatibility import util
|
||||||
from tests.compatibility.configurators.apache import apache24
|
from tests.compatibility.configurators.apache import apache24
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +32,93 @@ PLUGINS = {"apache" : apache24.Proxy}
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticator(plugin, config, temp_dir):
|
||||||
|
"""Tests plugin as an authenticator"""
|
||||||
|
backup = os.path.join(temp_dir, "backup")
|
||||||
|
shutil.copytree(config, backup, symlinks=True)
|
||||||
|
|
||||||
|
achalls = _create_achalls(plugin)
|
||||||
|
if achalls:
|
||||||
|
try:
|
||||||
|
responses = plugin.perform(achalls)
|
||||||
|
for i in xrange(len(responses)):
|
||||||
|
if not responses[i]:
|
||||||
|
raise errors.Error(
|
||||||
|
"Plugin returned 'None' or 'False' response to "
|
||||||
|
"challenge")
|
||||||
|
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):
|
||||||
|
logger.info(
|
||||||
|
"Verification of DVSNI response for %s succeeded",
|
||||||
|
achalls[i].domain)
|
||||||
|
else:
|
||||||
|
raise errors.Error(
|
||||||
|
"Verification of DVSNI response for {0} "
|
||||||
|
"failed".format(achalls[i].domain))
|
||||||
|
finally:
|
||||||
|
plugin.cleanup(achalls)
|
||||||
|
|
||||||
|
if _dirs_are_unequal(config, backup):
|
||||||
|
raise errors.Error("Challenge cleanup failed")
|
||||||
|
else:
|
||||||
|
logger.info("Challenge cleanup succeeded")
|
||||||
|
|
||||||
|
|
||||||
|
def _create_achalls(plugin):
|
||||||
|
"""Returns a list of annotated challenges to test on plugin"""
|
||||||
|
achalls = list()
|
||||||
|
names = plugin.get_testable_domain_names()
|
||||||
|
for domain in names:
|
||||||
|
prefs = plugin.get_chall_pref(domain)
|
||||||
|
for chall_type in prefs:
|
||||||
|
if chall_type == challenges.DVSNI:
|
||||||
|
chall = challenges.DVSNI(
|
||||||
|
r=os.urandom(challenges.DVSNI.R_SIZE),
|
||||||
|
nonce=os.urandom(challenges.DVSNI.NONCE_SIZE))
|
||||||
|
challb = acme_util.chall_to_challb(
|
||||||
|
chall, messages.STATUS_PENDING)
|
||||||
|
achall = achallenges.DVSNI(
|
||||||
|
challb=challb, domain=domain, key=util.JWK)
|
||||||
|
achalls.append(achall)
|
||||||
|
|
||||||
|
return achalls
|
||||||
|
|
||||||
|
|
||||||
|
def test_installer(plugin, config, temp_dir):
|
||||||
|
"""Tests plugin as an installer"""
|
||||||
|
backup = os.path.join(temp_dir, "backup")
|
||||||
|
shutil.copytree(config, backup, symlinks=True)
|
||||||
|
|
||||||
|
if plugin.get_all_names() != plugin.get_all_names_answer():
|
||||||
|
raise errors.Error("get_all_names test failed")
|
||||||
|
else:
|
||||||
|
logging.info("get_all_names test succeeded")
|
||||||
|
|
||||||
|
domains = list(plugin.get_testable_domain_names())
|
||||||
|
cert = crypto_util.gen_ss_cert(util.KEY, domains)
|
||||||
|
cert_path = os.path.join(temp_dir, "cert.pem")
|
||||||
|
with open(cert_path, "w") as f:
|
||||||
|
f.write(OpenSSL.crypto.dump_certificate(
|
||||||
|
OpenSSL.crypto.FILETYPE_PEM, cert))
|
||||||
|
|
||||||
|
for domain in domains:
|
||||||
|
plugin.deploy_cert(domain, cert_path, util.KEY_PATH)
|
||||||
|
plugin.save()
|
||||||
|
plugin.restart()
|
||||||
|
|
||||||
|
|
||||||
|
def _dirs_are_unequal(dir1, dir2):
|
||||||
|
"""Returns True if dir1 and dir2 are equal"""
|
||||||
|
dircmp = filecmp.dircmp(dir1, dir2)
|
||||||
|
|
||||||
|
return (dircmp.left_only or dircmp.right_only or
|
||||||
|
dircmp.diff_files or dircmp.funny_files)
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
"""Returns parsed command line arguments."""
|
"""Returns parsed command line arguments."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -62,17 +161,10 @@ def setup_logging(args):
|
|||||||
handler = logging.StreamHandler()
|
handler = logging.StreamHandler()
|
||||||
|
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
root_logger.setLevel(logging.WARNING - args.verbose_count * 10)
|
root_logger.setLevel(logging.INFO - args.verbose_count * 10)
|
||||||
root_logger.addHandler(handler)
|
root_logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
def test_installer(plugin):
|
|
||||||
"""Tests plugin as an installer"""
|
|
||||||
if plugin.get_all_names() != plugin.get_all_names_answer():
|
|
||||||
raise errors.Error(
|
|
||||||
"Names found by plugin don't match names found by the wrapper")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main test script execution."""
|
"""Main test script execution."""
|
||||||
args = get_args()
|
args = get_args()
|
||||||
@@ -81,17 +173,20 @@ def main():
|
|||||||
if args.plugin not in PLUGINS:
|
if args.plugin not in PLUGINS:
|
||||||
raise errors.Error("Unknown plugin {0}".format(args.plugin))
|
raise errors.Error("Unknown plugin {0}".format(args.plugin))
|
||||||
|
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
plugin = PLUGINS[args.plugin](args)
|
plugin = PLUGINS[args.plugin](args)
|
||||||
try:
|
try:
|
||||||
|
plugin.execute_in_docker("mkdir -p /var/log/apache2")
|
||||||
while plugin.has_more_configs():
|
while plugin.has_more_configs():
|
||||||
try:
|
try:
|
||||||
print "Loaded configuration: {0}".format(plugin.load_config())
|
config = plugin.load_config()
|
||||||
|
logger.info("Loaded configuration: %s", config)
|
||||||
if args.install:
|
if args.auth:
|
||||||
test_installer(plugin)
|
test_authenticator(plugin, config, temp_dir)
|
||||||
|
#if args.install:
|
||||||
|
#test_installer(plugin, temp_dir)
|
||||||
except errors.Error as error:
|
except errors.Error as error:
|
||||||
print "Test failed"
|
logger.warning("Test failed: %s", error)
|
||||||
print error
|
|
||||||
finally:
|
finally:
|
||||||
plugin.cleanup_from_tests()
|
plugin.cleanup_from_tests()
|
||||||
|
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQCsREbM+UcfsgDy2w56AVGyxsO0HVsbEZHHoEzv7qksIwFgRYMp
|
||||||
|
rowwIxD450RQQqjvw9IoXlMVmr1t5szn5KXn9JRO9T5KNCCy3VPx75WBcp6kzd9Q
|
||||||
|
2HS1OEOtpilNnDkZ+TJfdgFWPUBYj2o4Md1hPmcvagiIJY5U6speka2bjwIDAQAB
|
||||||
|
AoGANCMZ9pF/mDUsmlP4Rq69hkkoFAxKdZ/UqkF256so4mXZ1cRUFTpxzWPfkCWW
|
||||||
|
hGAYdzCiG3uo08IYkPmojIqkN1dk5Hcq5eQAmshaPkQHQCHjmPjjcNvgjIXQoGUf
|
||||||
|
TpDU2hbY4UAlJlj4ZLh+jGP5Zq8/WrNi8RsI3v9Nagfp/FECQQDgi2q8p1gX0TNh
|
||||||
|
d1aEKmSXkR3bxkyFk6oS+pBrAG3+yX27ZayN6Rx6DOs/FcBsOu7fX3PYBziDeEWe
|
||||||
|
Lkf1P743AkEAxGYT/LY3puglSz4iJZZzWmRCrVOg41yhfQ+F1BRX43/2vtoU5GyM
|
||||||
|
2lUn1vQ2e/rfmnAvfJxc90GeZCIHB1ihaQJBALH8UMLxMtbOMJgVbDKfF9U8ZhqK
|
||||||
|
+KT5A1q/2jG2yXmoZU1hroFeQgBMtTvwFfK0VBwjIUQflSBA+Y4EyW0Q9ckCQGvd
|
||||||
|
jHitM1+N/H2YwHRYbz5j9mLvnVuCEod3MQ9LpQGj1Eb5y6OxIqL/RgQ+2HW7UXem
|
||||||
|
yc3sqvp5pZ5lOesE+JECQETPI64gqxlTIs3nErNMpMynUuTWpaElOcIJTT6icLzB
|
||||||
|
Xix67kKXjROO5D58GEYkM0Yi5k7YdUPoQBW7MoIrSIA=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
@@ -8,10 +8,16 @@ import shutil
|
|||||||
import socket
|
import socket
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
|
from acme import jose
|
||||||
|
from acme import test_util
|
||||||
from letsencrypt import constants
|
from letsencrypt import constants
|
||||||
from tests.compatibility import errors
|
from tests.compatibility import errors
|
||||||
|
|
||||||
|
|
||||||
|
_KEY_BASE = "rsa1024_key.pem"
|
||||||
|
KEY_PATH = test_util.vector_path(_KEY_BASE)
|
||||||
|
KEY = test_util.load_pyopenssl_private_key(_KEY_BASE)
|
||||||
|
JWK = jose.JWKRSA(key=test_util.load_rsa_private_key(_KEY_BASE))
|
||||||
IP_REGEX = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
|
IP_REGEX = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user