Merge branch 'master' into separate-test-everything

This commit is contained in:
Brad Warren 2018-08-29 14:15:24 -07:00
commit 13b35db798
28 changed files with 699 additions and 635 deletions

View file

@ -24,19 +24,27 @@ matrix:
- python: "2.7"
env: TOXENV=lint
- python: "2.7"
env: TOXENV=py27-nginx-oldest BOULDER_INTEGRATION=v1
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
sudo: required
services: docker
- python: "2.7"
env: TOXENV=py27-nginx-oldest BOULDER_INTEGRATION=v2
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
sudo: required
services: docker
- python: "2.7"
env: TOXENV=py27_install BOULDER_INTEGRATION=v2
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
sudo: required
services: docker
- python: "2.7"
env: TOXENV=py27_install BOULDER_INTEGRATION=v1
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
sudo: required
services: docker
- python: "2.7"
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=all TOXENV=py27_install
sudo: required
services: docker
- python: "2.7"
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=all TOXENV=py27_install
sudo: required
services: docker
- python: "2.7"

View file

@ -1,5 +1,6 @@
"""Apache Configuration based off of Augeas Configurator."""
# pylint: disable=too-many-lines
import copy
import fnmatch
import logging
import os
@ -97,48 +98,72 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "options-ssl-apache.conf")
)
def constant(self, key):
"""Get constant for OS_DEFAULTS"""
return self.OS_DEFAULTS.get(key)
def option(self, key):
"""Get a value from options"""
return self.options.get(key)
def _prepare_options(self):
"""
Set the values possibly changed by command line parameters to
OS_DEFAULTS constant dictionary
"""
opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root",
"logs_root", "challenge_location", "handle_modules", "handle_sites",
"ctl"]
for o in opts:
# Config options use dashes instead of underscores
if self.conf(o.replace("_", "-")) is not None:
self.options[o] = self.conf(o.replace("_", "-"))
else:
self.options[o] = self.OS_DEFAULTS[o]
# Special cases
self.options["version_cmd"][0] = self.option("ctl")
self.options["restart_cmd"][0] = self.option("ctl")
self.options["conftest_cmd"][0] = self.option("ctl")
@classmethod
def add_parser_arguments(cls, add):
# When adding, modifying or deleting command line arguments, be sure to
# include the changes in the list used in method _prepare_options() to
# ensure consistent behavior.
add("enmod", default=cls.OS_DEFAULTS["enmod"],
help="Path to the Apache 'a2enmod' binary.")
help="Path to the Apache 'a2enmod' binary")
add("dismod", default=cls.OS_DEFAULTS["dismod"],
help="Path to the Apache 'a2dismod' binary.")
help="Path to the Apache 'a2dismod' binary")
add("le-vhost-ext", default=cls.OS_DEFAULTS["le_vhost_ext"],
help="SSL vhost configuration extension.")
help="SSL vhost configuration extension")
add("server-root", default=cls.OS_DEFAULTS["server_root"],
help="Apache server root directory.")
help="Apache server root directory")
add("vhost-root", default=None,
help="Apache server VirtualHost configuration root")
add("logs-root", default=cls.OS_DEFAULTS["logs_root"],
help="Apache server logs directory")
add("challenge-location",
default=cls.OS_DEFAULTS["challenge_location"],
help="Directory path for challenge configuration.")
add("handle-modules", default=cls.OS_DEFAULTS["handle_mods"],
help="Let installer handle enabling required modules for you. " +
help="Directory path for challenge configuration")
add("handle-modules", default=cls.OS_DEFAULTS["handle_modules"],
help="Let installer handle enabling required modules for you " +
"(Only Ubuntu/Debian currently)")
add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"],
help="Let installer handle enabling sites for you. " +
help="Let installer handle enabling sites for you " +
"(Only Ubuntu/Debian currently)")
util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
add("ctl", default=cls.OS_DEFAULTS["ctl"],
help="Full path to Apache control script")
util.add_deprecated_argument(
add, argument_name="init-script", nargs=1)
@ -169,7 +194,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.parser = None
self.version = version
self.vhosts = None
self.vhostroot = None
self.options = copy.deepcopy(self.OS_DEFAULTS)
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header,
"staple-ocsp": self._enable_ocsp_stapling}
@ -201,12 +226,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
except ImportError:
raise errors.NoInstallationError("Problem in Augeas installation")
self._prepare_options()
# Verify Apache is installed
restart_cmd = self.constant("restart_cmd")[0]
if not util.exe_exists(restart_cmd):
if not path_surgery(restart_cmd):
raise errors.NoInstallationError(
'Cannot find Apache control command {0}'.format(restart_cmd))
self._verify_exe_availability(self.option("ctl"))
# Make sure configuration is valid
self.config_test()
@ -226,12 +249,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"version 1.2.0 or higher, please make sure you have you have "
"those installed.")
# Parse vhost-root if defined on cli
if not self.conf("vhost-root"):
self.vhostroot = self.constant("vhost_root")
else:
self.vhostroot = os.path.abspath(self.conf("vhost-root"))
self.parser = self.get_parser()
# Check for errors in parsing files with Augeas
@ -245,13 +262,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Prevent two Apache plugins from modifying a config at once
try:
util.lock_dir_until_exit(self.conf("server-root"))
util.lock_dir_until_exit(self.option("server_root"))
except (OSError, errors.LockError):
logger.debug("Encountered error:", exc_info=True)
raise errors.PluginError(
"Unable to lock %s", self.conf("server-root"))
"Unable to lock %s", self.option("server_root"))
self._prepared = True
def _verify_exe_availability(self, exe):
"""Checks availability of Apache executable"""
if not util.exe_exists(exe):
if not path_surgery(exe):
raise errors.NoInstallationError(
'Cannot find Apache executable {0}'.format(exe))
def _check_aug_version(self):
""" Checks that we have recent enough version of libaugeas.
If augeas version is recent enough, it will support case insensitive
@ -269,8 +293,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
def get_parser(self):
"""Initializes the ApacheParser"""
# If user provided vhost_root value in command line, use it
return parser.ApacheParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.conf("vhost-root"),
self.version, configurator=self)
def _wildcard_domain(self, domain):
@ -1037,7 +1062,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param boolean temp: If the change is temporary
"""
if self.conf("handle-modules"):
if self.option("handle_modules"):
if self.version >= (2, 4) and ("socache_shmcb_module" not in
self.parser.modules):
self.enable_mod("socache_shmcb", temp=temp)
@ -1066,7 +1091,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
Duplicates vhost and adds default ssl options
New vhost will reside as (nonssl_vhost.path) +
``self.constant("le_vhost_ext")``
``self.option("le_vhost_ext")``
.. note:: This function saves the configuration
@ -1165,18 +1190,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")):
# Defined by user on CLI
fp = os.path.join(os.path.realpath(self.vhostroot),
fp = os.path.join(os.path.realpath(self.option("vhost_root")),
os.path.basename(non_ssl_vh_fp))
else:
# Use non-ssl filepath
fp = os.path.realpath(non_ssl_vh_fp)
if fp.endswith(".conf"):
return fp[:-(len(".conf"))] + self.conf("le_vhost_ext")
return fp[:-(len(".conf"))] + self.option("le_vhost_ext")
else:
return fp + self.conf("le_vhost_ext")
return fp + self.option("le_vhost_ext")
def _sift_rewrite_rule(self, line):
"""Decides whether a line should be copied to a SSL vhost.
@ -2025,7 +2048,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
addr in self._get_proposed_addrs(ssl_vhost)),
servername, serveralias,
" ".join(rewrite_rule_args),
self.conf("logs-root")))
self.option("logs_root")))
def _write_out_redirect(self, ssl_vhost, text):
# This is the default name
@ -2037,7 +2060,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)):
redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name
redirect_filepath = os.path.join(self.vhostroot,
redirect_filepath = os.path.join(self.option("vhost_root"),
redirect_filename)
# Register the new file that will be created
@ -2158,18 +2181,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
error = ""
try:
util.run_script(self.constant("restart_cmd"))
util.run_script(self.option("restart_cmd"))
except errors.SubprocessError as err:
logger.info("Unable to restart apache using %s",
self.constant("restart_cmd"))
alt_restart = self.constant("restart_cmd_alt")
self.option("restart_cmd"))
alt_restart = self.option("restart_cmd_alt")
if alt_restart:
logger.debug("Trying alternative restart command: %s",
alt_restart)
# There is an alternative restart command available
# This usually is "restart" verb while original is "graceful"
try:
util.run_script(self.constant(
util.run_script(self.option(
"restart_cmd_alt"))
return
except errors.SubprocessError as secerr:
@ -2185,7 +2208,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
util.run_script(self.constant("conftest_cmd"))
util.run_script(self.option("conftest_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@ -2201,11 +2224,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
stdout, _ = util.run_script(self.constant("version_cmd"))
stdout, _ = util.run_script(self.option("version_cmd"))
except errors.SubprocessError:
raise errors.PluginError(
"Unable to run %s -v" %
self.constant("version_cmd"))
self.option("version_cmd"))
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(stdout)
@ -2295,7 +2318,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# certbot for unprivileged users via setuid), this function will need
# to be modified.
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
def enable_autohsts(self, _unused_lineage, domains):
"""

View file

@ -6,6 +6,7 @@ from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-m
from certbot import errors
from certbot.plugins import common
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
from certbot_apache.parser import get_aug_path
logger = logging.getLogger(__name__)
@ -172,4 +173,9 @@ class ApacheHttp01(common.TLSSNI01):
self.configurator.parser.add_dir(
vhost.path, "Include", self.challenge_conf_post)
if not vhost.enabled:
self.configurator.parser.add_dir(
get_aug_path(self.configurator.parser.loc["default"]),
"Include", vhost.filep)
self.moded_vhosts.add(vhost)

View file

@ -16,14 +16,14 @@ class ArchConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/httpd/conf",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
apache_cmd="apachectl",
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(

View file

@ -18,25 +18,33 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
apache_cmd="apachectl",
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "centos-options-ssl-apache.conf")
)
def _prepare_options(self):
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in CentOS.
"""
super(CentOSConfigurator, self)._prepare_options()
self.options["restart_cmd_alt"][0] = self.option("ctl")
def get_parser(self):
"""Initializes the ApacheParser"""
return CentOSParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.option("vhost_root"),
self.version, configurator=self)

View file

@ -16,14 +16,14 @@ class DarwinConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/other",
vhost_files="*.conf",
logs_root="/var/log/apache2",
version_cmd=['/usr/sbin/httpd', '-v'],
apache_cmd="/usr/sbin/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/other",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(

View file

@ -23,14 +23,14 @@ class DebianConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod="a2enmod",
dismod="a2dismod",
le_vhost_ext="-le-ssl.conf",
handle_mods=True,
handle_modules=True,
handle_sites=True,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
@ -134,11 +134,11 @@ class DebianConfigurator(configurator.ApacheConfigurator):
# Generate reversal command.
# Try to be safe here... check that we can probably reverse before
# applying enmod command
if not util.exe_exists(self.conf("dismod")):
if not util.exe_exists(self.option("dismod")):
raise errors.MisconfigurationError(
"Unable to find a2dismod, please make sure a2enmod and "
"a2dismod are configured correctly for certbot.")
self.reverter.register_undo_command(
temp, [self.conf("dismod"), "-f", mod_name])
util.run_script([self.conf("enmod"), mod_name])
temp, [self.option("dismod"), "-f", mod_name])
util.run_script([self.option("enmod"), mod_name])

View file

@ -18,25 +18,33 @@ class GentooConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
version_cmd=['/usr/sbin/apache2', '-v'],
apache_cmd="apache2ctl",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
restart_cmd_alt=['apache2ctl', 'restart'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "options-ssl-apache.conf")
)
def _prepare_options(self):
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in Gentoo.
"""
super(GentooConfigurator, self)._prepare_options()
self.options["restart_cmd_alt"][0] = self.option("ctl")
def get_parser(self):
"""Initializes the ApacheParser"""
return GentooParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.option("vhost_root"),
self.version, configurator=self)
@ -61,7 +69,7 @@ class GentooParser(parser.ApacheParser):
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
mod_cmd = [self.configurator.constant("apache_cmd"), "modules"]
mod_cmd = [self.configurator.option("ctl"), "modules"]
matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
for mod in matches:
self.add_mod(mod.strip())

View file

@ -16,8 +16,8 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod="a2enmod",

View file

@ -69,7 +69,7 @@ class ApacheParser(object):
# Must also attempt to parse additional virtual host root
if vhostroot:
self.parse_file(os.path.abspath(vhostroot) + "/" +
self.configurator.constant("vhost_files"))
self.configurator.option("vhost_files"))
# check to see if there were unparsed define statements
if version < (2, 4):
@ -152,7 +152,7 @@ class ApacheParser(object):
"""Get Defines from httpd process"""
variables = dict()
define_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
define_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_RUN_CFG"]
matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
try:
@ -179,7 +179,7 @@ class ApacheParser(object):
# configuration files
_ = self.find_dir("Include")
inc_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
inc_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_INCLUDES"]
matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
if matches:
@ -190,7 +190,7 @@ class ApacheParser(object):
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
mod_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
mod_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_MODULES"]
matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
for mod in matches:

View file

@ -119,6 +119,9 @@ class AutoHSTSTest(util.ApacheTest):
cur_val = maxage.format(constants.AUTOHSTS_STEPS[i+1])
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
cur_val)
# Ensure that the value is raised to max
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
maxage.format(constants.AUTOHSTS_STEPS[-1]))
# Make permanent
self.config.deploy_autohsts(mock_lineage)
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),

View file

@ -135,5 +135,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
errors.SubprocessError,
errors.SubprocessError]
self.assertRaises(errors.MisconfigurationError, self.config.restart)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -116,8 +116,9 @@ class MultipleVhostsTest(util.ApacheTest):
ApacheConfigurator.add_parser_arguments(mock.MagicMock())
def test_constant(self):
self.assertEqual(self.config.constant("server_root"), "/etc/apache2")
self.assertEqual(self.config.constant("nonexistent"), None)
self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in
self.config.option("server_root"))
self.assertEqual(self.config.option("nonexistent"), None)
@certbot_util.patch_get_utility()
def test_get_all_names(self, mock_getutility):
@ -651,22 +652,10 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(ssl_vhost_slink.name, "nonsym.link")
def test_make_vhost_ssl_nonexistent_vhost_path(self):
def conf_side_effect(arg):
""" Mock function for ApacheConfigurator.conf """
confvars = {
"vhost-root": "/tmp/nonexistent",
"le_vhost_ext": "-le-ssl.conf",
"handle-sites": True}
return confvars[arg]
with mock.patch(
"certbot_apache.configurator.ApacheConfigurator.conf"
) as mock_conf:
mock_conf.side_effect = conf_side_effect
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
self.assertEqual(os.path.dirname(ssl_vhost.filep),
os.path.dirname(os.path.realpath(
self.vh_truth[1].filep)))
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
self.assertEqual(os.path.dirname(ssl_vhost.filep),
os.path.dirname(os.path.realpath(
self.vh_truth[1].filep)))
def test_make_vhost_ssl(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
@ -1583,7 +1572,7 @@ class AugeasVhostsTest(util.ApacheTest):
broken_vhost)
class MultiVhostsTest(util.ApacheTest):
"""Test vhosts with illegal names dependent on augeas version."""
"""Test configuration with multiple virtualhosts in a single file."""
# pylint: disable=protected-access
def setUp(self): # pylint: disable=arguments-differ
@ -1703,7 +1692,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config.updated_mod_ssl_conf_digest)
def _current_ssl_options_hash(self):
return crypto_util.sha256sum(self.config.constant("MOD_SSL_CONF_SRC"))
return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC"))
def _assert_current_file(self):
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
@ -1739,7 +1728,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.assertFalse(mock_logger.warning.called)
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
self.assertEqual(crypto_util.sha256sum(
self.config.constant("MOD_SSL_CONF_SRC")),
self.config.option("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
self._current_ssl_options_hash())
@ -1755,7 +1744,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
"%s has been manually modified; updated file "
"saved to %s. We recommend updating %s for security purposes.")
self.assertEqual(crypto_util.sha256sum(
self.config.constant("MOD_SSL_CONF_SRC")),
self.config.option("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
# only print warning once
with mock.patch("certbot.plugins.common.logger") as mock_logger:

View file

@ -20,7 +20,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ
super(MultipleVhostsTestDebian, self).setUp()
self.config = util.get_apache_configurator(
self.config_path, None, self.config_dir, self.work_dir,
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="debian")
self.config = self.mock_deploy_cert(self.config)
self.vh_truth = util.get_vh_truth(self.temp_dir,

View file

@ -117,7 +117,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
self.config.parser.modules = set()
with mock.patch("certbot.util.get_os_info") as mock_osi:
# Make sure we have the have the CentOS httpd constants
# Make sure we have the have the Gentoo httpd constants
mock_osi.return_value = ("gentoo", "123")
self.config.parser.update_runtime_variables()

View file

@ -10,6 +10,7 @@ from certbot import achallenges
from certbot import errors
from certbot.tests import acme_util
from certbot_apache.parser import get_aug_path
from certbot_apache.tests import util
@ -134,6 +135,21 @@ class ApacheHttp01Test(util.ApacheTest):
def test_perform_3_achall_apache_2_4(self):
self.combinations_perform_test(num_achalls=3, minor_version=4)
def test_activate_disabled_vhost(self):
vhosts = [v for v in self.config.vhosts if v.name == "certbot.demo"]
achalls = [
achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.chall_to_challb(
challenges.HTTP01(token=((b'a' * 16))),
"pending"),
domain="certbot.demo", account_key=self.account_key)]
vhosts[0].enabled = False
self.common_perform_test(achalls, vhosts)
matches = self.config.parser.find_dir(
"Include", vhosts[0].filep,
get_aug_path(self.config.parser.loc["default"]))
self.assertEqual(len(matches), 1)
def combinations_perform_test(self, num_achalls, minor_version):
"""Test perform with the given achall count and Apache version."""
achalls = self.achalls[:num_achalls]

View file

@ -282,11 +282,11 @@ class BasicParserTest(util.ParserTest):
self.assertRaises(
errors.PluginError, self.parser.update_runtime_variables)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.constant")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.option")
@mock.patch("certbot_apache.parser.subprocess.Popen")
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_const):
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt):
mock_popen.side_effect = OSError
mock_const.return_value = "nonexistent"
mock_opt.return_value = "nonexistent"
self.assertRaises(
errors.MisconfigurationError,
self.parser.update_runtime_variables)

View file

@ -97,9 +97,10 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
backups = os.path.join(work_dir, "backups")
mock_le_config = mock.MagicMock(
apache_server_root=config_path,
apache_vhost_root=conf_vhost_path,
apache_vhost_root=None,
apache_le_vhost_ext="-le-ssl.conf",
apache_challenge_location=config_path,
apache_enmod=None,
backup_dir=backups,
config_dir=config_dir,
http01_port=80,
@ -107,33 +108,25 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir)
orig_os_constant = configurator.ApacheConfigurator(mock_le_config,
name="apache",
version=version).constant
def mock_os_constant(key, vhost_path=vhost_path):
"""Mock default vhost path"""
if key == "vhost_root":
return vhost_path
else:
return orig_os_constant(key)
with mock.patch("certbot_apache.configurator.ApacheConfigurator.constant") as mock_cons:
mock_cons.side_effect = mock_os_constant
with mock.patch("certbot_apache.configurator.util.run_script"):
with mock.patch("certbot_apache.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("certbot_apache.parser.ApacheParser."
"update_runtime_variables"):
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version)
config.prepare()
with mock.patch("certbot_apache.configurator.util.run_script"):
with mock.patch("certbot_apache.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("certbot_apache.parser.ApacheParser."
"update_runtime_variables"):
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version)
if not conf_vhost_path:
config_class.OS_DEFAULTS["vhost_root"] = vhost_path
else:
# Custom virtualhost path was requested
config.config.apache_vhost_root = conf_vhost_path
config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"]
config.prepare()
return config

View file

@ -59,9 +59,6 @@ class Proxy(configurators_common.Proxy):
setattr(self.le_config, "apache_" + k,
entrypoint.ENTRYPOINT.OS_DEFAULTS[k])
# An alias
self.le_config.apache_handle_modules = self.le_config.apache_handle_mods
self._configurator = entrypoint.ENTRYPOINT(
config=configuration.NamespaceConfig(self.le_config),
name="apache")

View file

@ -353,7 +353,7 @@ def _describe_certs(config, parsed_certs, parse_failures):
notify("Found the following {0}certs:".format(match))
notify(_report_human_readable(config, parsed_certs))
if parse_failures:
notify("\nThe following renewal configuration files "
notify("\nThe following renewal configurations "
"were invalid:")
notify(_report_lines(parse_failures))

View file

@ -51,6 +51,6 @@ def path_surgery(cmd):
return True
else:
expanded = " expanded" if any(added) else ""
logger.warning("Failed to find executable %s in%s PATH: %s", cmd,
expanded, path)
logger.debug("Failed to find executable %s in%s PATH: %s", cmd,
expanded, path)
return False

View file

@ -16,9 +16,8 @@ class GetPrefixTest(unittest.TestCase):
class PathSurgeryTest(unittest.TestCase):
"""Tests for certbot.plugins.path_surgery."""
@mock.patch("certbot.plugins.util.logger.warning")
@mock.patch("certbot.plugins.util.logger.debug")
def test_path_surgery(self, mock_debug, mock_warn):
def test_path_surgery(self, mock_debug):
from certbot.plugins.util import path_surgery
all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"}
with mock.patch.dict('os.environ', all_path):
@ -26,14 +25,12 @@ class PathSurgeryTest(unittest.TestCase):
mock_exists.return_value = True
self.assertEqual(path_surgery("eg"), True)
self.assertEqual(mock_debug.call_count, 0)
self.assertEqual(mock_warn.call_count, 0)
self.assertEqual(os.environ["PATH"], all_path["PATH"])
no_path = {"PATH": "/tmp/"}
with mock.patch.dict('os.environ', no_path):
path_surgery("thingy")
self.assertEqual(mock_debug.call_count, 1)
self.assertEqual(mock_warn.call_count, 1)
self.assertTrue("Failed to find" in mock_warn.call_args[0][0])
self.assertEqual(mock_debug.call_count, 2)
self.assertTrue("Failed to find" in mock_debug.call_args[0][0])
self.assertTrue("/usr/local/bin" in os.environ["PATH"])
self.assertTrue("/tmp" in os.environ["PATH"])

View file

@ -359,7 +359,7 @@ def _renew_describe_results(config, renew_successes, renew_failures,
notify_error(report(renew_failures, "failure"))
if parse_failures:
notify("\nAdditionally, the following renewal configuration files "
notify("\nAdditionally, the following renewal configurations "
"were invalid: ")
notify(report(parse_failures, "parsefail"))

View file

@ -40,6 +40,7 @@ needs_sphinx = '1.0'
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.imgconverter',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',

View file

@ -70,8 +70,8 @@ dev3_extras = [
docs_extras = [
'repoze.sphinx.autointerface',
# autodoc_member_order = 'bysource', autodoc_default_flags, and #4686
'Sphinx >=1.0,<=1.5.6',
# sphinx.ext.imgconverter
'Sphinx >=1.6',
'sphinx_rtd_theme',
]

View file

@ -1,496 +1,16 @@
#!/bin/bash
# Simple integration test. Make sure to activate virtualenv beforehand
# (source venv/bin/activate) and that you are running Boulder test
# instance (see ./boulder-fetch.sh).
#
# Environment variables:
# SERVER: Passed as "certbot --server" argument.
#
# Note: this script is called by Boulder integration test suite!
set -eux
set -e
. ./tests/integration/_common.sh
export PATH="$PATH:/usr/sbin" # /usr/sbin/nginx
cleanup_and_exit() {
EXIT_STATUS=$?
if SERVER_STILL_RUNNING=`ps -p $python_server_pid -o pid=`
then
echo Kill server subprocess, left running by abnormal exit
kill $SERVER_STILL_RUNNING
fi
if [ -f "$HOOK_DIRS_TEST" ]; then
rm -f "$HOOK_DIRS_TEST"
fi
exit $EXIT_STATUS
}
trap cleanup_and_exit EXIT
export HOOK_DIRS_TEST="$(mktemp)"
renewal_hooks_root="$config_dir/renewal-hooks"
renewal_hooks_dirs=$(echo "$renewal_hooks_root/"{pre,deploy,post})
renewal_dir_pre_hook="$(echo $renewal_hooks_dirs | cut -f 1 -d " ")/hook.sh"
renewal_dir_deploy_hook="$(echo $renewal_hooks_dirs | cut -f 2 -d " ")/hook.sh"
renewal_dir_post_hook="$(echo $renewal_hooks_dirs | cut -f 3 -d " ")/hook.sh"
# Creates hooks in Certbot's renewal hook directory that write to a file
CreateDirHooks() {
for hook_dir in $renewal_hooks_dirs; do
mkdir -p $hook_dir
hook_path="$hook_dir/hook.sh"
cat << EOF > "$hook_path"
#!/bin/bash -xe
if [ "\$0" = "$renewal_dir_deploy_hook" ]; then
if [ -z "\$RENEWED_DOMAINS" -o -z "\$RENEWED_LINEAGE" ]; then
echo "Environment variables not properly set!" >&2
exit 1
if [ "$INTEGRATION_TEST" = "certbot" ]; then
tests/certbot-boulder-integration.sh
elif [ "$INTEGRATION_TEST" = "nginx" ]; then
certbot-nginx/tests/boulder-integration.sh
else
tests/certbot-boulder-integration.sh
# Most CI systems set this variable to true.
# If the tests are running as part of CI, Nginx should be available.
if ${CI:-false} || type nginx; then
certbot-nginx/tests/boulder-integration.sh
fi
fi
echo \$(basename \$(dirname "\$0")) >> "\$HOOK_DIRS_TEST"
EOF
chmod +x "$hook_path"
done
}
# Asserts that the hooks created by CreateDirHooks have been run once and
# resets the file.
#
# Arguments:
# The number of times the deploy hook should have been run. (It should run
# once for each certificate that was issued in that run of Certbot.)
CheckDirHooks() {
expected="pre\n"
for ((i=0; i<$1; i++)); do
expected=$expected"deploy\n"
done
expected=$expected"post"
if ! diff "$HOOK_DIRS_TEST" <(echo -e "$expected"); then
echo "Unexpected directory hook output!" >&2
echo "Expected:" >&2
echo -e "$expected" >&2
echo "Got:" >&2
cat "$HOOK_DIRS_TEST" >&2
exit 1
fi
rm -f "$HOOK_DIRS_TEST"
export HOOK_DIRS_TEST="$(mktemp)"
}
common_no_force_renew() {
certbot_test_no_force_renew \
--authenticator standalone \
--installer null \
"$@"
}
common() {
common_no_force_renew \
--renew-by-default \
"$@"
}
export HOOK_TEST="/tmp/hook$$"
CheckHooks() {
if [ $(head -n1 "$HOOK_TEST") = "wtf.pre" ]; then
expected="wtf.pre\ndeploy\n"
if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then
expected=$expected"deploy\nwtf2.pre\n"
else
expected=$expected"wtf2.pre\ndeploy\n"
fi
expected=$expected"deploy\ndeploy\nwtf.post\nwtf2.post"
else
expected="wtf2.pre\ndeploy\n"
if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then
expected=$expected"deploy\nwtf.pre\n"
else
expected=$expected"wtf.pre\ndeploy\n"
fi
expected=$expected"deploy\ndeploy\nwtf2.post\nwtf.post"
fi
if ! cmp --quiet <(echo -e "$expected") "$HOOK_TEST" ; then
echo Hooks did not run as expected\; got >&2
cat "$HOOK_TEST" >&2
echo -e "Expected\n$expected" >&2
rm "$HOOK_TEST"
exit 1
fi
rm "$HOOK_TEST"
}
# Checks if deploy is in the hook output and deletes the file
DeployInHookOutput() {
CONTENTS=$(cat "$HOOK_TEST")
rm "$HOOK_TEST"
grep deploy <(echo "$CONTENTS")
}
# Asserts that there is a saved renew_hook for a lineage.
#
# Arguments:
# Name of lineage to check
CheckSavedRenewHook() {
if ! grep renew_hook "$config_dir/renewal/$1.conf"; then
echo "Hook wasn't saved as renew_hook" >&2
exit 1
fi
}
# Asserts the deploy hook was properly run and saved and deletes the hook file
#
# Arguments:
# Lineage name of the issued cert
CheckDeployHook() {
if ! DeployInHookOutput; then
echo "The deploy hook wasn't run" >&2
exit 1
fi
CheckSavedRenewHook $1
}
# Asserts the renew hook wasn't run but was saved and deletes the hook file
#
# Arguments:
# Lineage name of the issued cert
# Asserts the deploy hook wasn't run and deletes the hook file
CheckRenewHook() {
if DeployInHookOutput; then
echo "The renew hook was incorrectly run" >&2
exit 1
fi
CheckSavedRenewHook $1
}
# Return success only if input contains exactly $1 lines of text, of
# which $2 different values occur in the first field.
TotalAndDistinctLines() {
total=$1
distinct=$2
awk '{a[$1] = 1}; END {exit(NR !='$total' || length(a) !='$distinct')}'
}
# Cleanup coverage data
coverage erase
# test for regressions of #4719
get_num_tmp_files() {
ls -1 /tmp | wc -l
}
num_tmp_files=$(get_num_tmp_files)
common --csr / && echo expected error && exit 1 || true
common --help
common --help all
common --version
if [ $(get_num_tmp_files) -ne $num_tmp_files ]; then
echo "New files or directories created in /tmp!"
exit 1
fi
CreateDirHooks
common register
for dir in $renewal_hooks_dirs; do
if [ ! -d "$dir" ]; then
echo "Hook directory not created by Certbot!" >&2
exit 1
fi
done
common unregister
common register --email ex1@domain.org,ex2@domain.org
common register --update-registration --email ex1@domain.org
common register --update-registration --email ex1@domain.org,ex2@domain.org
common plugins --init --prepare | grep webroot
# We start a server listening on the port for the
# unrequested challenge to prevent regressions in #3601.
python ./tests/run_http_server.py $http_01_port &
python_server_pid=$!
certname="le1.wtf"
common --domains le1.wtf --preferred-challenges tls-sni-01 auth \
--cert-name $certname \
--pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf.post >> "$HOOK_TEST"'\
--deploy-hook 'echo deploy >> "$HOOK_TEST"'
kill $python_server_pid
CheckDeployHook $certname
python ./tests/run_http_server.py $tls_sni_01_port &
python_server_pid=$!
certname="le2.wtf"
common --domains le2.wtf --preferred-challenges http-01 run \
--cert-name $certname \
--pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf.post >> "$HOOK_TEST"'\
--deploy-hook 'echo deploy >> "$HOOK_TEST"'
kill $python_server_pid
CheckDeployHook $certname
certname="le.wtf"
common certonly -a manual -d le.wtf --rsa-key-size 4096 --cert-name $certname \
--manual-auth-hook ./tests/manual-http-auth.sh \
--manual-cleanup-hook ./tests/manual-http-cleanup.sh \
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf2.post >> "$HOOK_TEST"' \
--renew-hook 'echo deploy >> "$HOOK_TEST"'
CheckRenewHook $certname
certname="dns.le.wtf"
common -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni run \
--cert-name $certname \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh \
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf2.post >> "$HOOK_TEST"' \
--renew-hook 'echo deploy >> "$HOOK_TEST"'
CheckRenewHook $certname
common certonly --cert-name newname -d newname.le.wtf
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
OPENSSL_CNF=examples/openssl.cnf
./examples/generate-csr.sh le3.wtf
common auth --csr "$CSR_PATH" \
--cert-path "${root}/csr/cert.pem" \
--chain-path "${root}/csr/chain.pem"
openssl x509 -in "${root}/csr/cert.pem" -text
openssl x509 -in "${root}/csr/chain.pem" -text
common --domains le3.wtf install \
--cert-path "${root}/csr/cert.pem" \
--key-path "${root}/key.pem"
CheckCertCount() {
CERTCOUNT=`ls "${root}/conf/archive/$1/cert"* | wc -l`
if [ "$CERTCOUNT" -ne "$2" ] ; then
echo Wrong cert count, not "$2" `ls "${root}/conf/archive/$1/"*`
exit 1
fi
}
CheckCertCount "le.wtf" 1
# This won't renew (because it's not time yet)
common_no_force_renew renew
CheckCertCount "le.wtf" 1
if [ -s "$HOOK_DIRS_TEST" ]; then
echo "Directory hooks were executed for non-renewal!" >&2;
exit 1
fi
rm -rf "$renewal_hooks_root"
# renew using HTTP manual auth hooks
common renew --cert-name le.wtf --authenticator manual
CheckCertCount "le.wtf" 2
# test renewal with no executables in hook directories
for hook_dir in $renewal_hooks_dirs; do
touch "$hook_dir/file"
mkdir "$hook_dir/dir"
done
# renew using DNS manual auth hooks
common renew --cert-name dns.le.wtf --authenticator manual
CheckCertCount "dns.le.wtf" 2
# test with disabled directory hooks
rm -rf "$renewal_hooks_root"
CreateDirHooks
# This will renew because the expiry is less than 10 years from now
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
common_no_force_renew renew --rsa-key-size 2048 --no-directory-hooks
CheckCertCount "le.wtf" 3
if [ -s "$HOOK_DIRS_TEST" ]; then
echo "Directory hooks were executed with --no-directory-hooks!" >&2
exit 1
fi
# The 4096 bit setting should persist to the first renewal, but be overridden in the second
size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1`
size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1`
size3=`wc -c ${root}/conf/archive/le.wtf/privkey3.pem | cut -d" " -f1`
# 4096 bit PEM keys are about ~3270 bytes, 2048 ones are about 1700 bytes
if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; then
echo key sizes violate assumptions:
ls -l "${root}/conf/archive/le.wtf/privkey"*
exit 1
fi
# --renew-by-default is used, so renewal should occur
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
common renew
CheckCertCount "le.wtf" 4
CheckHooks
CheckDirHooks 5
# test with overlapping directory hooks on the command line
common renew --cert-name le2.wtf \
--pre-hook "$renewal_dir_pre_hook" \
--deploy-hook "$renewal_dir_deploy_hook" \
--post-hook "$renewal_dir_post_hook"
CheckDirHooks 1
# test with overlapping directory hooks in the renewal conf files
common renew --cert-name le2.wtf
CheckDirHooks 1
# manual-dns-auth.sh will skip completing the challenge for domains that begin
# with fail.
common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \
--allow-subset-of-names \
--preferred-challenges dns,tls-sni \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh
if common certificates | grep "fail\.dns1\.le\.wtf"; then
echo "certificate should not have been issued for domain!" >&2
exit 1
fi
# reuse-key
common --domains reusekey.le.wtf --reuse-key
common renew --cert-name reusekey.le.wtf
CheckCertCount "reusekey.le.wtf" 2
ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"*
# The final awk command here exits successfully if its input consists of
# exactly two lines with identical first fields, and unsuccessfully otherwise.
sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 2 1
# don't reuse key (just by forcing reissuance without --reuse-key)
common --cert-name reusekey.le.wtf --domains reusekey.le.wtf --force-renewal
CheckCertCount "reusekey.le.wtf" 3
ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"*
# Exactly three lines, of which exactly two identical first fields.
sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 3 2
# Nonetheless, all three certificates are different even though two of them
# share the same subject key.
sha256sum "${root}/conf/archive/reusekey.le.wtf/cert"* | TotalAndDistinctLines 3 3
# ECDSA
openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem"
SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \
-config "${OPENSSL_CNF:-openssl.cnf}" \
-key "${root}/privkey-p384.pem" \
-subj "/" \
-reqexts san \
-outform der \
-out "${root}/csr-p384.der"
common auth --csr "${root}/csr-p384.der" \
--cert-path "${root}/csr/cert-p384.pem" \
--chain-path "${root}/csr/chain-p384.pem"
openssl x509 -in "${root}/csr/cert-p384.pem" -text | grep 'ASN1 OID: secp384r1'
# OCSP Must Staple
common auth --must-staple --domains "must-staple.le.wtf"
openssl x509 -in "${root}/conf/live/must-staple.le.wtf/cert.pem" -text | grep '1.3.6.1.5.5.7.1.24'
# revoke by account key
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem" --delete-after-revoke
# revoke renewed
common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" --no-delete-after-revoke
if [ ! -d "$root/conf/live/le1.wtf" ]; then
echo "cert deleted when --no-delete-after-revoke was used!"
exit 1
fi
common delete --cert-name le1.wtf
# revoke by cert key
common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \
--key-path "$root/conf/live/le2.wtf/privkey.pem"
# Get new certs to test revoke with a reason, by account and by cert key
common --domains le1.wtf
common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" \
--reason cessationOfOperation
common --domains le2.wtf
common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \
--key-path "$root/conf/live/le2.wtf/privkey.pem" \
--reason keyCompromise
common unregister
out=$(common certificates)
subdomains="le dns.le newname.le must-staple.le"
for subdomain in $subdomains; do
domain="$subdomain.wtf"
if ! echo $out | grep "$domain"; then
echo "$domain not in certificates output!"
exit 1;
fi
done
# Testing that revocation also deletes by default
subdomains="le1 le2"
for subdomain in $subdomains; do
domain="$subdomain.wtf"
if echo $out | grep "$domain"; then
echo "Revoked $domain in certificates output! Should not be!"
exit 1;
fi
done
# Test that revocation raises correct error if --cert-name and --cert-path don't match
common --domains le1.wtf
common --domains le2.wtf
out=$(common revoke --cert-path "$root/conf/live/le1.wtf/fullchain.pem" --cert-name "le2.wtf" 2>&1) || true
if ! echo $out | grep "or both must point to the same certificate lineages."; then
echo "Non-interactive revoking with mismatched --cert-name and --cert-path "
echo "did not raise the correct error!"
exit 1
fi
# Revoking by matching --cert-name and --cert-path deletes
common --domains le1.wtf
common revoke --cert-path "$root/conf/live/le1.wtf/fullchain.pem" --cert-name "le1.wtf"
out=$(common certificates)
if echo $out | grep "le1.wtf"; then
echo "Cert le1.wtf should've been deleted! Was revoked via matching --cert-path & --cert-name"
exit 1
fi
# Test that revocation doesn't delete if multiple lineages share an archive dir
common --domains le1.wtf
common --domains le2.wtf
sed -i "s|^archive_dir = .*$|archive_dir = $root/conf/archive/le1.wtf|" "$root/conf/renewal/le2.wtf.conf"
#common update_symlinks # not needed, but a bit more context for what this test is about
out=$(common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem")
if ! echo $out | grep "Not deleting revoked certs due to overlapping archive dirs"; then
echo "Deleted a cert that had an overlapping archive dir with another lineage!"
exit 1
fi
cert_name="must-staple.le.wtf"
common delete --cert-name $cert_name
archive="$root/conf/archive/$cert_name"
conf="$root/conf/renewal/$cert_name.conf"
live="$root/conf/live/$cert_name"
for path in $archive $conf $live; do
if [ -e $path ]; then
echo "Lineage not properly deleted!"
exit 1
fi
done
# Test ACMEv2-only features
if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then
common -a manual -d '*.le4.wtf,le4.wtf' --preferred-challenges dns \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh
fi
coverage report --fail-under 65 --include 'certbot/*' --show-missing
# Most CI systems set this variable to true.
# If the tests are running as part of CI, Nginx should be available.
if ${CI:-false} || type nginx;
then
. ./certbot-nginx/tests/boulder-integration.sh
fi

View file

@ -0,0 +1,492 @@
#!/bin/bash
# Simple integration test. Make sure to activate virtualenv beforehand
# (source venv/bin/activate) and that you are running Boulder test
# instance (see ./boulder-fetch.sh).
#
# Environment variables:
# SERVER: Passed as "certbot --server" argument.
#
# Note: this script is called by Boulder integration test suite!
set -eux
# Check that python executable is available in the PATH. Fail immediatly if not.
command -v python > /dev/null || (echo "Error, python executable is not in the PATH" && exit 1)
. ./tests/integration/_common.sh
export PATH="$PATH:/usr/sbin" # /usr/sbin/nginx
cleanup_and_exit() {
EXIT_STATUS=$?
if SERVER_STILL_RUNNING=`ps -p $python_server_pid -o pid=`
then
echo Kill server subprocess, left running by abnormal exit
kill $SERVER_STILL_RUNNING
fi
if [ -f "$HOOK_DIRS_TEST" ]; then
rm -f "$HOOK_DIRS_TEST"
fi
exit $EXIT_STATUS
}
trap cleanup_and_exit EXIT
export HOOK_DIRS_TEST="$(mktemp)"
renewal_hooks_root="$config_dir/renewal-hooks"
renewal_hooks_dirs=$(echo "$renewal_hooks_root/"{pre,deploy,post})
renewal_dir_pre_hook="$(echo $renewal_hooks_dirs | cut -f 1 -d " ")/hook.sh"
renewal_dir_deploy_hook="$(echo $renewal_hooks_dirs | cut -f 2 -d " ")/hook.sh"
renewal_dir_post_hook="$(echo $renewal_hooks_dirs | cut -f 3 -d " ")/hook.sh"
# Creates hooks in Certbot's renewal hook directory that write to a file
CreateDirHooks() {
for hook_dir in $renewal_hooks_dirs; do
mkdir -p $hook_dir
hook_path="$hook_dir/hook.sh"
cat << EOF > "$hook_path"
#!/bin/bash -xe
if [ "\$0" = "$renewal_dir_deploy_hook" ]; then
if [ -z "\$RENEWED_DOMAINS" -o -z "\$RENEWED_LINEAGE" ]; then
echo "Environment variables not properly set!" >&2
exit 1
fi
fi
echo \$(basename \$(dirname "\$0")) >> "\$HOOK_DIRS_TEST"
EOF
chmod +x "$hook_path"
done
}
# Asserts that the hooks created by CreateDirHooks have been run once and
# resets the file.
#
# Arguments:
# The number of times the deploy hook should have been run. (It should run
# once for each certificate that was issued in that run of Certbot.)
CheckDirHooks() {
expected="pre\n"
for ((i=0; i<$1; i++)); do
expected=$expected"deploy\n"
done
expected=$expected"post"
if ! diff "$HOOK_DIRS_TEST" <(echo -e "$expected"); then
echo "Unexpected directory hook output!" >&2
echo "Expected:" >&2
echo -e "$expected" >&2
echo "Got:" >&2
cat "$HOOK_DIRS_TEST" >&2
exit 1
fi
rm -f "$HOOK_DIRS_TEST"
export HOOK_DIRS_TEST="$(mktemp)"
}
common_no_force_renew() {
certbot_test_no_force_renew \
--authenticator standalone \
--installer null \
"$@"
}
common() {
common_no_force_renew \
--renew-by-default \
"$@"
}
export HOOK_TEST="/tmp/hook$$"
CheckHooks() {
if [ $(head -n1 "$HOOK_TEST") = "wtf.pre" ]; then
expected="wtf.pre\ndeploy\n"
if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then
expected=$expected"deploy\nwtf2.pre\n"
else
expected=$expected"wtf2.pre\ndeploy\n"
fi
expected=$expected"deploy\ndeploy\nwtf.post\nwtf2.post"
else
expected="wtf2.pre\ndeploy\n"
if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then
expected=$expected"deploy\nwtf.pre\n"
else
expected=$expected"wtf.pre\ndeploy\n"
fi
expected=$expected"deploy\ndeploy\nwtf2.post\nwtf.post"
fi
if ! cmp --quiet <(echo -e "$expected") "$HOOK_TEST" ; then
echo Hooks did not run as expected\; got >&2
cat "$HOOK_TEST" >&2
echo -e "Expected\n$expected" >&2
rm "$HOOK_TEST"
exit 1
fi
rm "$HOOK_TEST"
}
# Checks if deploy is in the hook output and deletes the file
DeployInHookOutput() {
CONTENTS=$(cat "$HOOK_TEST")
rm "$HOOK_TEST"
grep deploy <(echo "$CONTENTS")
}
# Asserts that there is a saved renew_hook for a lineage.
#
# Arguments:
# Name of lineage to check
CheckSavedRenewHook() {
if ! grep renew_hook "$config_dir/renewal/$1.conf"; then
echo "Hook wasn't saved as renew_hook" >&2
exit 1
fi
}
# Asserts the deploy hook was properly run and saved and deletes the hook file
#
# Arguments:
# Lineage name of the issued cert
CheckDeployHook() {
if ! DeployInHookOutput; then
echo "The deploy hook wasn't run" >&2
exit 1
fi
CheckSavedRenewHook $1
}
# Asserts the renew hook wasn't run but was saved and deletes the hook file
#
# Arguments:
# Lineage name of the issued cert
# Asserts the deploy hook wasn't run and deletes the hook file
CheckRenewHook() {
if DeployInHookOutput; then
echo "The renew hook was incorrectly run" >&2
exit 1
fi
CheckSavedRenewHook $1
}
# Return success only if input contains exactly $1 lines of text, of
# which $2 different values occur in the first field.
TotalAndDistinctLines() {
total=$1
distinct=$2
awk '{a[$1] = 1}; END {exit(NR !='$total' || length(a) !='$distinct')}'
}
# Cleanup coverage data
coverage erase
# test for regressions of #4719
get_num_tmp_files() {
ls -1 /tmp | wc -l
}
num_tmp_files=$(get_num_tmp_files)
common --csr / && echo expected error && exit 1 || true
common --help
common --help all
common --version
if [ $(get_num_tmp_files) -ne $num_tmp_files ]; then
echo "New files or directories created in /tmp!"
exit 1
fi
CreateDirHooks
common register
for dir in $renewal_hooks_dirs; do
if [ ! -d "$dir" ]; then
echo "Hook directory not created by Certbot!" >&2
exit 1
fi
done
common unregister
common register --email ex1@domain.org,ex2@domain.org
common register --update-registration --email ex1@domain.org
common register --update-registration --email ex1@domain.org,ex2@domain.org
common plugins --init --prepare | grep webroot
# We start a server listening on the port for the
# unrequested challenge to prevent regressions in #3601.
python ./tests/run_http_server.py $http_01_port &
python_server_pid=$!
certname="le1.wtf"
common --domains le1.wtf --preferred-challenges tls-sni-01 auth \
--cert-name $certname \
--pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf.post >> "$HOOK_TEST"'\
--deploy-hook 'echo deploy >> "$HOOK_TEST"'
kill $python_server_pid
CheckDeployHook $certname
python ./tests/run_http_server.py $tls_sni_01_port &
python_server_pid=$!
certname="le2.wtf"
common --domains le2.wtf --preferred-challenges http-01 run \
--cert-name $certname \
--pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf.post >> "$HOOK_TEST"'\
--deploy-hook 'echo deploy >> "$HOOK_TEST"'
kill $python_server_pid
CheckDeployHook $certname
certname="le.wtf"
common certonly -a manual -d le.wtf --rsa-key-size 4096 --cert-name $certname \
--manual-auth-hook ./tests/manual-http-auth.sh \
--manual-cleanup-hook ./tests/manual-http-cleanup.sh \
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf2.post >> "$HOOK_TEST"' \
--renew-hook 'echo deploy >> "$HOOK_TEST"'
CheckRenewHook $certname
certname="dns.le.wtf"
common -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni run \
--cert-name $certname \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh \
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf2.post >> "$HOOK_TEST"' \
--renew-hook 'echo deploy >> "$HOOK_TEST"'
CheckRenewHook $certname
common certonly --cert-name newname -d newname.le.wtf
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
OPENSSL_CNF=examples/openssl.cnf
./examples/generate-csr.sh le3.wtf
common auth --csr "$CSR_PATH" \
--cert-path "${root}/csr/cert.pem" \
--chain-path "${root}/csr/chain.pem"
openssl x509 -in "${root}/csr/cert.pem" -text
openssl x509 -in "${root}/csr/chain.pem" -text
common --domains le3.wtf install \
--cert-path "${root}/csr/cert.pem" \
--key-path "${root}/key.pem"
CheckCertCount() {
CERTCOUNT=`ls "${root}/conf/archive/$1/cert"* | wc -l`
if [ "$CERTCOUNT" -ne "$2" ] ; then
echo Wrong cert count, not "$2" `ls "${root}/conf/archive/$1/"*`
exit 1
fi
}
CheckCertCount "le.wtf" 1
# This won't renew (because it's not time yet)
common_no_force_renew renew
CheckCertCount "le.wtf" 1
if [ -s "$HOOK_DIRS_TEST" ]; then
echo "Directory hooks were executed for non-renewal!" >&2;
exit 1
fi
rm -rf "$renewal_hooks_root"
# renew using HTTP manual auth hooks
common renew --cert-name le.wtf --authenticator manual
CheckCertCount "le.wtf" 2
# test renewal with no executables in hook directories
for hook_dir in $renewal_hooks_dirs; do
touch "$hook_dir/file"
mkdir "$hook_dir/dir"
done
# renew using DNS manual auth hooks
common renew --cert-name dns.le.wtf --authenticator manual
CheckCertCount "dns.le.wtf" 2
# test with disabled directory hooks
rm -rf "$renewal_hooks_root"
CreateDirHooks
# This will renew because the expiry is less than 10 years from now
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
common_no_force_renew renew --rsa-key-size 2048 --no-directory-hooks
CheckCertCount "le.wtf" 3
if [ -s "$HOOK_DIRS_TEST" ]; then
echo "Directory hooks were executed with --no-directory-hooks!" >&2
exit 1
fi
# The 4096 bit setting should persist to the first renewal, but be overridden in the second
size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1`
size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1`
size3=`wc -c ${root}/conf/archive/le.wtf/privkey3.pem | cut -d" " -f1`
# 4096 bit PEM keys are about ~3270 bytes, 2048 ones are about 1700 bytes
if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; then
echo key sizes violate assumptions:
ls -l "${root}/conf/archive/le.wtf/privkey"*
exit 1
fi
# --renew-by-default is used, so renewal should occur
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
common renew
CheckCertCount "le.wtf" 4
CheckHooks
CheckDirHooks 5
# test with overlapping directory hooks on the command line
common renew --cert-name le2.wtf \
--pre-hook "$renewal_dir_pre_hook" \
--deploy-hook "$renewal_dir_deploy_hook" \
--post-hook "$renewal_dir_post_hook"
CheckDirHooks 1
# test with overlapping directory hooks in the renewal conf files
common renew --cert-name le2.wtf
CheckDirHooks 1
# manual-dns-auth.sh will skip completing the challenge for domains that begin
# with fail.
common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \
--allow-subset-of-names \
--preferred-challenges dns,tls-sni \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh
if common certificates | grep "fail\.dns1\.le\.wtf"; then
echo "certificate should not have been issued for domain!" >&2
exit 1
fi
# reuse-key
common --domains reusekey.le.wtf --reuse-key
common renew --cert-name reusekey.le.wtf
CheckCertCount "reusekey.le.wtf" 2
ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"*
# The final awk command here exits successfully if its input consists of
# exactly two lines with identical first fields, and unsuccessfully otherwise.
sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 2 1
# don't reuse key (just by forcing reissuance without --reuse-key)
common --cert-name reusekey.le.wtf --domains reusekey.le.wtf --force-renewal
CheckCertCount "reusekey.le.wtf" 3
ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"*
# Exactly three lines, of which exactly two identical first fields.
sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 3 2
# Nonetheless, all three certificates are different even though two of them
# share the same subject key.
sha256sum "${root}/conf/archive/reusekey.le.wtf/cert"* | TotalAndDistinctLines 3 3
# ECDSA
openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem"
SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \
-config "${OPENSSL_CNF:-openssl.cnf}" \
-key "${root}/privkey-p384.pem" \
-subj "/" \
-reqexts san \
-outform der \
-out "${root}/csr-p384.der"
common auth --csr "${root}/csr-p384.der" \
--cert-path "${root}/csr/cert-p384.pem" \
--chain-path "${root}/csr/chain-p384.pem"
openssl x509 -in "${root}/csr/cert-p384.pem" -text | grep 'ASN1 OID: secp384r1'
# OCSP Must Staple
common auth --must-staple --domains "must-staple.le.wtf"
openssl x509 -in "${root}/conf/live/must-staple.le.wtf/cert.pem" -text | grep '1.3.6.1.5.5.7.1.24'
# revoke by account key
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem" --delete-after-revoke
# revoke renewed
common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" --no-delete-after-revoke
if [ ! -d "$root/conf/live/le1.wtf" ]; then
echo "cert deleted when --no-delete-after-revoke was used!"
exit 1
fi
common delete --cert-name le1.wtf
# revoke by cert key
common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \
--key-path "$root/conf/live/le2.wtf/privkey.pem"
# Get new certs to test revoke with a reason, by account and by cert key
common --domains le1.wtf
common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" \
--reason cessationOfOperation
common --domains le2.wtf
common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \
--key-path "$root/conf/live/le2.wtf/privkey.pem" \
--reason keyCompromise
common unregister
out=$(common certificates)
subdomains="le dns.le newname.le must-staple.le"
for subdomain in $subdomains; do
domain="$subdomain.wtf"
if ! echo $out | grep "$domain"; then
echo "$domain not in certificates output!"
exit 1;
fi
done
# Testing that revocation also deletes by default
subdomains="le1 le2"
for subdomain in $subdomains; do
domain="$subdomain.wtf"
if echo $out | grep "$domain"; then
echo "Revoked $domain in certificates output! Should not be!"
exit 1;
fi
done
# Test that revocation raises correct error if --cert-name and --cert-path don't match
common --domains le1.wtf
common --domains le2.wtf
out=$(common revoke --cert-path "$root/conf/live/le1.wtf/fullchain.pem" --cert-name "le2.wtf" 2>&1) || true
if ! echo $out | grep "or both must point to the same certificate lineages."; then
echo "Non-interactive revoking with mismatched --cert-name and --cert-path "
echo "did not raise the correct error!"
exit 1
fi
# Revoking by matching --cert-name and --cert-path deletes
common --domains le1.wtf
common revoke --cert-path "$root/conf/live/le1.wtf/fullchain.pem" --cert-name "le1.wtf"
out=$(common certificates)
if echo $out | grep "le1.wtf"; then
echo "Cert le1.wtf should've been deleted! Was revoked via matching --cert-path & --cert-name"
exit 1
fi
# Test that revocation doesn't delete if multiple lineages share an archive dir
common --domains le1.wtf
common --domains le2.wtf
sed -i "s|^archive_dir = .*$|archive_dir = $root/conf/archive/le1.wtf|" "$root/conf/renewal/le2.wtf.conf"
#common update_symlinks # not needed, but a bit more context for what this test is about
out=$(common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem")
if ! echo $out | grep "Not deleting revoked certs due to overlapping archive dirs"; then
echo "Deleted a cert that had an overlapping archive dir with another lineage!"
exit 1
fi
cert_name="must-staple.le.wtf"
common delete --cert-name $cert_name
archive="$root/conf/archive/$cert_name"
conf="$root/conf/renewal/$cert_name.conf"
live="$root/conf/live/$cert_name"
for path in $archive $conf $live; do
if [ -e $path ]; then
echo "Lineage not properly deleted!"
exit 1
fi
done
# Test ACMEv2-only features
if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then
common -a manual -d '*.le4.wtf,le4.wtf' --preferred-challenges dns \
--manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh
fi
coverage report --fail-under 64 --include 'certbot/*' --show-missing

View file

@ -60,8 +60,9 @@ s3transfer==0.1.11
scandir==1.6
simplegeneric==0.8.1
snowballstemmer==1.2.1
Sphinx==1.5.6
Sphinx==1.7.5
sphinx-rtd-theme==0.2.4
sphinxcontrib-websupport==1.0.1
tldextract==2.2.0
tox==2.9.1
tqdm==4.19.4