diff --git a/.pylintrc b/.pylintrc
index d510fddfd..36d8c286f 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -41,7 +41,7 @@ load-plugins=linter_plugin
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
-disable=fixme,locally-disabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import
+disable=fixme,locally-disabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import,duplicate-code
# abstract-class-not-used cannot be disabled locally (at least in
# pylint 1.4.1), same for abstract-class-little-used
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1aaef4af1..4acfc0401 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,42 @@
Certbot adheres to [Semantic Versioning](http://semver.org/).
+## 0.20.0 - 2017-12-06
+
+### Added
+
+* Certbot's ACME library now recognizes URL fields in challenge objects in
+ preparation for Let's Encrypt's new ACME endpoint. The value is still
+ accessible in our ACME library through the name "uri".
+
+### Changed
+
+* The Apache plugin now parses some distro specific Apache configuration files
+ on non-Debian systems allowing it to get a clearer picture on the running
+ configuration. Internally, these changes were structured so that external
+ contributors can easily write patches to make the plugin work in new Apache
+ configurations.
+* Certbot better reports network failures by removing information about
+ connection retries from the error output.
+* An unnecessary question when using Certbot's webroot plugin interactively has
+ been removed.
+
+### Fixed
+
+* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was
+ unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a
+ redirect for multiple domains.
+* Problems where the Apache plugin was failing to find directives and
+ duplicating existing directives on openSUSE have been resolved.
+* An issue running the test shipped with Certbot and some our DNS plugins with
+ older versions of mock have been resolved.
+* On some systems, users reported strangely interleaved output depending on
+ when stdout and stderr were flushed. This problem was resolved by having
+ Certbot regularly flush these streams.
+
+More details about these changes can be found on our GitHub repo:
+https://github.com/certbot/certbot/milestone/44?closed=1
+
## 0.19.0 - 2017-10-04
### Added
diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py
index e8a0b16a8..618dda200 100644
--- a/acme/acme/__init__.py
+++ b/acme/acme/__init__.py
@@ -10,3 +10,12 @@ supported version: `draft-ietf-acme-01`_.
https://github.com/ietf-wg-acme/acme/tree/draft-ietf-acme-acme-01
"""
+import sys
+import warnings
+
+if sys.version_info[:2] == (3, 3):
+ warnings.warn(
+ "Python 3.3 support will be dropped in the next release of "
+ "acme. Please upgrade your Python version.",
+ PendingDeprecationWarning,
+ ) #pragma: no cover
diff --git a/acme/acme/messages.py b/acme/acme/messages.py
index 4b4fa5003..2ac29941e 100644
--- a/acme/acme/messages.py
+++ b/acme/acme/messages.py
@@ -325,13 +325,26 @@ class ChallengeBody(ResourceBody):
"""
__slots__ = ('chall',)
- uri = jose.Field('uri')
+ # ACMEv1 has a "uri" field in challenges. ACMEv2 has a "url" field. This
+ # challenge object supports either one, but should be accessed through the
+ # name "uri". In Client.answer_challenge, whichever one is set will be
+ # used.
+ _uri = jose.Field('uri', omitempty=True, default=None)
+ _url = jose.Field('url', omitempty=True, default=None)
status = jose.Field('status', decoder=Status.from_json,
omitempty=True, default=STATUS_PENDING)
validated = fields.RFC3339Field('validated', omitempty=True)
error = jose.Field('error', decoder=Error.from_json,
omitempty=True, default=None)
+ def __init__(self, **kwargs):
+ kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
+ # pylint: disable=star-args
+ super(ChallengeBody, self).__init__(**kwargs)
+
+ def encode(self, name):
+ return super(ChallengeBody, self).encode(self._internal_name(name))
+
def to_partial_json(self):
jobj = super(ChallengeBody, self).to_partial_json()
jobj.update(self.chall.to_partial_json())
@@ -343,9 +356,23 @@ class ChallengeBody(ResourceBody):
jobj_fields['chall'] = challenges.Challenge.from_json(jobj)
return jobj_fields
+ @property
+ def uri(self):
+ """The URL of this challenge."""
+ return self._url or self._uri
+
def __getattr__(self, name):
return getattr(self.chall, name)
+ def __iter__(self):
+ # When iterating over fields, use the external name 'uri' instead of
+ # the internal '_uri'.
+ for name in super(ChallengeBody, self).__iter__():
+ yield name[1:] if name == '_uri' else name
+
+ def _internal_name(self, name):
+ return '_' + name if name == 'uri' else name
+
class ChallengeResource(Resource):
"""Challenge Resource.
@@ -358,10 +385,10 @@ class ChallengeResource(Resource):
authzr_uri = jose.Field('authzr_uri')
@property
- def uri(self): # pylint: disable=missing-docstring,no-self-argument
- # bug? 'method already defined line None'
- # pylint: disable=function-redefined
- return self.body.uri # pylint: disable=no-member
+ def uri(self):
+ """The URL of the challenge body."""
+ # pylint: disable=function-redefined,no-member
+ return self.body.uri
class Authorization(ResourceBody):
diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py
index 631f0ce4d..c9e5c2cf1 100644
--- a/acme/acme/messages_test.py
+++ b/acme/acme/messages_test.py
@@ -283,6 +283,9 @@ class ChallengeBodyTest(unittest.TestCase):
'detail': 'Unable to communicate with DNS server',
}
+ def test_encode(self):
+ self.assertEqual(self.challb.encode('uri'), self.challb.uri)
+
def test_to_partial_json(self):
self.assertEqual(self.jobj_to, self.challb.to_partial_json())
diff --git a/acme/setup.py b/acme/setup.py
index c28e0c152..d04b84739 100644
--- a/acme/setup.py
+++ b/acme/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py
new file mode 100644
index 000000000..b4a24f137
--- /dev/null
+++ b/certbot-apache/certbot_apache/apache_util.py
@@ -0,0 +1,96 @@
+""" Utility functions for certbot-apache plugin """
+import os
+
+from certbot import util
+
+def get_mod_deps(mod_name):
+ """Get known module dependencies.
+
+ .. note:: This does not need to be accurate in order for the client to
+ run. This simply keeps things clean if the user decides to revert
+ changes.
+ .. warning:: If all deps are not included, it may cause incorrect parsing
+ behavior, due to enable_mod's shortcut for updating the parser's
+ currently defined modules (`.ApacheParser.add_mod`)
+ This would only present a major problem in extremely atypical
+ configs that use ifmod for the missing deps.
+
+ """
+ deps = {
+ "ssl": ["setenvif", "mime"]
+ }
+ return deps.get(mod_name, [])
+
+
+def get_file_path(vhost_path):
+ """Get file path from augeas_vhost_path.
+
+ Takes in Augeas path and returns the file name
+
+ :param str vhost_path: Augeas virtual host path
+
+ :returns: filename of vhost
+ :rtype: str
+
+ """
+ if not vhost_path or not vhost_path.startswith("/files/"):
+ return None
+
+ return _split_aug_path(vhost_path)[0]
+
+
+def get_internal_aug_path(vhost_path):
+ """Get the Augeas path for a vhost with the file path removed.
+
+ :param str vhost_path: Augeas virtual host path
+
+ :returns: Augeas path to vhost relative to the containing file
+ :rtype: str
+
+ """
+ return _split_aug_path(vhost_path)[1]
+
+
+def _split_aug_path(vhost_path):
+ """Splits an Augeas path into a file path and an internal path.
+
+ After removing "/files", this function splits vhost_path into the
+ file path and the remaining Augeas path.
+
+ :param str vhost_path: Augeas virtual host path
+
+ :returns: file path and internal Augeas path
+ :rtype: `tuple` of `str`
+
+ """
+ # Strip off /files
+ file_path = vhost_path[6:]
+ internal_path = []
+
+ # Remove components from the end of file_path until it becomes valid
+ while not os.path.exists(file_path):
+ file_path, _, internal_path_part = file_path.rpartition("/")
+ internal_path.append(internal_path_part)
+
+ return file_path, "/".join(reversed(internal_path))
+
+
+def parse_define_file(filepath, varname):
+ """ Parses Defines from a variable in configuration file
+
+ :param str filepath: Path of file to parse
+ :param str varname: Name of the variable
+
+ :returns: Dict of Define:Value pairs
+ :rtype: `dict`
+
+ """
+ return_vars = {}
+ # Get list of words in the variable
+ a_opts = util.get_var_from_file(varname, filepath).split()
+ for i, v in enumerate(a_opts):
+ # Handle Define statements and make sure it has an argument
+ if v == "-D" and len(a_opts) >= i+2:
+ var_parts = a_opts[i+1].partition("=")
+ return_vars[var_parts[0]] = var_parts[2]
+ return return_vars
diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py
index 8b6c578d6..5a33346ea 100644
--- a/certbot-apache/certbot_apache/configurator.py
+++ b/certbot-apache/certbot_apache/configurator.py
@@ -3,6 +3,7 @@
import fnmatch
import logging
import os
+import pkg_resources
import re
import socket
import time
@@ -19,6 +20,7 @@ from certbot import util
from certbot.plugins import common
from certbot.plugins.util import path_surgery
+from certbot_apache import apache_util
from certbot_apache import augeas_configurator
from certbot_apache import constants
from certbot_apache import display_ops
@@ -85,27 +87,50 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
description = "Apache Web Server plugin - Beta"
+ OS_DEFAULTS = dict(
+ server_root="/etc/apache2",
+ vhost_root="/etc/apache2/sites-available",
+ vhost_files="*",
+ logs_root="/var/log/apache2",
+ 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_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)
+
@classmethod
def add_parser_arguments(cls, add):
- add("enmod", default=constants.os_constant("enmod"),
+ add("enmod", default=cls.OS_DEFAULTS["enmod"],
help="Path to the Apache 'a2enmod' binary.")
- add("dismod", default=constants.os_constant("dismod"),
+ add("dismod", default=cls.OS_DEFAULTS["dismod"],
help="Path to the Apache 'a2dismod' binary.")
- add("le-vhost-ext", default=constants.os_constant("le_vhost_ext"),
+ add("le-vhost-ext", default=cls.OS_DEFAULTS["le_vhost_ext"],
help="SSL vhost configuration extension.")
- add("server-root", default=constants.os_constant("server_root"),
+ add("server-root", default=cls.OS_DEFAULTS["server_root"],
help="Apache server root directory.")
add("vhost-root", default=None,
help="Apache server VirtualHost configuration root")
- add("logs-root", default=constants.os_constant("logs_root"),
+ add("logs-root", default=cls.OS_DEFAULTS["logs_root"],
help="Apache server logs directory")
add("challenge-location",
- default=constants.os_constant("challenge_location"),
+ default=cls.OS_DEFAULTS["challenge_location"],
help="Directory path for challenge configuration.")
- add("handle-modules", default=constants.os_constant("handle_mods"),
+ add("handle-modules", default=cls.OS_DEFAULTS["handle_mods"],
help="Let installer handle enabling required modules for you." +
"(Only Ubuntu/Debian currently)")
- add("handle-sites", default=constants.os_constant("handle_sites"),
+ add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"],
help="Let installer handle enabling sites for you." +
"(Only Ubuntu/Debian currently)")
util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
@@ -166,7 +191,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
raise errors.NoInstallationError("Problem in Augeas installation")
# Verify Apache is installed
- restart_cmd = constants.os_constant("restart_cmd")[0]
+ restart_cmd = self.constant("restart_cmd")[0]
if not util.exe_exists(restart_cmd):
if not path_surgery(restart_cmd):
raise errors.NoInstallationError(
@@ -192,20 +217,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Parse vhost-root if defined on cli
if not self.conf("vhost-root"):
- self.vhostroot = constants.os_constant("vhost_root")
+ self.vhostroot = self.constant("vhost_root")
else:
self.vhostroot = os.path.abspath(self.conf("vhost-root"))
- self.parser = parser.ApacheParser(
- self.aug, self.conf("server-root"), self.conf("vhost-root"),
- self.version, configurator=self)
+ self.parser = self.get_parser()
+
# Check for errors in parsing files with Augeas
self.check_parsing_errors("httpd.aug")
# Get all of the available vhosts
self.vhosts = self.get_virtual_hosts()
- install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest)
+ self.install_ssl_options_conf(self.mod_ssl_conf,
+ self.updated_mod_ssl_conf_digest)
# Prevent two Apache plugins from modifying a config at once
try:
@@ -230,6 +255,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.aug.remove("/test/path")
return matches
+ def get_parser(self):
+ """Initializes the ApacheParser"""
+ return parser.ApacheParser(
+ self.aug, self.conf("server-root"), self.conf("vhost-root"),
+ self.version, configurator=self)
+
def deploy_cert(self, domain, cert_path, key_path,
chain_path=None, fullchain_path=None):
"""Deploys certificate to specified virtual host.
@@ -585,7 +616,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if addr.get_port() == "443":
is_ssl = True
- filename = get_file_path(self.aug.get("/augeas/files%s/path" % get_file_path(path)))
+ filename = apache_util.get_file_path(
+ self.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path)))
if filename is None:
return None
@@ -624,7 +656,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
new_vhost = self._create_vhost(path)
if not new_vhost:
continue
- internal_path = get_internal_aug_path(new_vhost.path)
+ internal_path = apache_util.get_internal_aug_path(new_vhost.path)
realpath = os.path.realpath(new_vhost.filep)
if realpath not in file_paths:
file_paths[realpath] = new_vhost.filep
@@ -640,7 +672,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
for v in vhs:
if v.filep == file_paths[realpath]:
internal_paths[realpath].remove(
- get_internal_aug_path(v.path))
+ apache_util.get_internal_aug_path(v.path))
else:
new_vhs.append(v)
vhs = new_vhs
@@ -828,7 +860,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
Duplicates vhost and adds default ssl options
New vhost will reside as (nonssl_vhost.path) +
- ``certbot_apache.constants.os_constant("le_vhost_ext")``
+ ``self.constant("le_vhost_ext")``
.. note:: This function saves the configuration
@@ -1702,17 +1734,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return redirects
-
def enable_site(self, vhost):
"""Enables an available site, Apache reload required.
.. note:: Does not make sure that the site correctly works or that all
modules are enabled appropriately.
-
- .. todo:: This function should number subdomains before the domain
- vhost
-
- .. todo:: Make sure link is not broken...
+ .. note:: The distribution specific override replaces functionality
+ of this method where available.
:param vhost: vhost to enable
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
@@ -1724,39 +1752,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if vhost.enabled:
return
- # Handle non-debian systems
- if not self.conf("handle-sites"):
- if not self.parser.parsed_in_original(vhost.filep):
- # Add direct include to root conf
- self.parser.add_include(self.parser.loc["default"], vhost.filep)
- vhost.enabled = True
- return
+ if not self.parser.parsed_in_original(vhost.filep):
+ # Add direct include to root conf
+ logger.info("Enabling site %s by adding Include to root configuration",
+ vhost.filep)
+ self.save_notes += "Enabled site %s\n" % vhost.filep
+ self.parser.add_include(self.parser.loc["default"], vhost.filep)
+ vhost.enabled = True
+ return
- enabled_path = ("%s/sites-enabled/%s" %
- (self.parser.root, os.path.basename(vhost.filep)))
- self.reverter.register_file_creation(False, enabled_path)
- try:
- os.symlink(vhost.filep, enabled_path)
- except OSError as err:
- if os.path.islink(enabled_path) and os.path.realpath(
- enabled_path) == vhost.filep:
- # Already in shape
- vhost.enabled = True
- return
- else:
- logger.warning(
- "Could not symlink %s to %s, got error: %s", enabled_path,
- vhost.filep, err.strerror)
- errstring = ("Encountered error while trying to enable a " +
- "newly created VirtualHost located at {0} by " +
- "linking to it from {1}")
- raise errors.NotSupportedError(errstring.format(vhost.filep,
- enabled_path))
- vhost.enabled = True
- logger.info("Enabling available site: %s", vhost.filep)
- self.save_notes += "Enabled site %s\n" % vhost.filep
-
- def enable_mod(self, mod_name, temp=False):
+ def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument
"""Enables module in Apache.
Both enables and reloads Apache so module is active.
@@ -1764,64 +1769,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param str mod_name: Name of the module to enable. (e.g. 'ssl')
:param bool temp: Whether or not this is a temporary action.
- :raises .errors.NotSupportedError: If the filesystem layout is not
- supported.
- :raises .errors.MisconfigurationError: If a2enmod or a2dismod cannot be
- run.
+ .. note:: The distribution specific override replaces functionality
+ of this method where available.
+
+ :raises .errors.MisconfigurationError: We cannot enable modules in
+ generic fashion.
"""
- # Support Debian specific setup
- avail_path = os.path.join(self.parser.root, "mods-available")
- enabled_path = os.path.join(self.parser.root, "mods-enabled")
- if not os.path.isdir(avail_path) or not os.path.isdir(enabled_path):
- raise errors.NotSupportedError(
- "Unsupported directory layout. You may try to enable mod %s "
- "and try again." % mod_name)
-
- deps = _get_mod_deps(mod_name)
-
- # Enable all dependencies
- for dep in deps:
- if (dep + "_module") not in self.parser.modules:
- self._enable_mod_debian(dep, temp)
- self._add_parser_mod(dep)
-
- note = "Enabled dependency of %s module - %s" % (mod_name, dep)
- if not temp:
- self.save_notes += note + os.linesep
- logger.debug(note)
-
- # Enable actual module
- self._enable_mod_debian(mod_name, temp)
- self._add_parser_mod(mod_name)
-
- if not temp:
- self.save_notes += "Enabled %s module in Apache\n" % mod_name
- logger.info("Enabled Apache %s module", mod_name)
-
- # Modules can enable additional config files. Variables may be defined
- # within these new configuration sections.
- # Reload is not necessary as DUMP_RUN_CFG uses latest config.
- self.parser.update_runtime_variables()
-
- def _add_parser_mod(self, mod_name):
- """Shortcut for updating parser modules."""
- self.parser.modules.add(mod_name + "_module")
- self.parser.modules.add("mod_" + mod_name + ".c")
-
- def _enable_mod_debian(self, mod_name, temp):
- """Assumes mods-available, mods-enabled layout."""
- # 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")):
- 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"), mod_name])
- util.run_script([self.conf("enmod"), mod_name])
+ mod_message = ("Apache needs to have module \"{0}\" active for the " +
+ "requested installation options. Unfortunately Certbot is unable " +
+ "to install or enable it for you. Please install the module, and " +
+ "run Certbot again.")
+ raise errors.MisconfigurationError(mod_message.format(mod_name))
def restart(self):
"""Runs a config test and reloads the Apache server.
@@ -1840,7 +1799,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
- util.run_script(constants.os_constant("restart_cmd"))
+ util.run_script(self.constant("restart_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@@ -1851,7 +1810,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
- util.run_script(constants.os_constant("conftest_cmd"))
+ util.run_script(self.constant("conftest_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@@ -1867,11 +1826,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
- stdout, _ = util.run_script(constants.os_constant("version_cmd"))
+ stdout, _ = util.run_script(self.constant("version_cmd"))
except errors.SubprocessError:
raise errors.PluginError(
"Unable to run %s -v" %
- constants.os_constant("version_cmd"))
+ self.constant("version_cmd"))
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(stdout)
@@ -1943,86 +1902,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if not self._chall_out:
self.revert_challenge_config()
self.restart()
- self.parser.init_modules()
+ self.parser.reset_modules()
+
+ 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."""
+
+ # XXX if we ever try to enforce a local privilege boundary (eg, running
+ # 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)
-def _get_mod_deps(mod_name):
- """Get known module dependencies.
-
- .. note:: This does not need to be accurate in order for the client to
- run. This simply keeps things clean if the user decides to revert
- changes.
- .. warning:: If all deps are not included, it may cause incorrect parsing
- behavior, due to enable_mod's shortcut for updating the parser's
- currently defined modules (`.ApacheConfigurator._add_parser_mod`)
- This would only present a major problem in extremely atypical
- configs that use ifmod for the missing deps.
-
- """
- deps = {
- "ssl": ["setenvif", "mime"]
- }
- return deps.get(mod_name, [])
-
-
-def get_file_path(vhost_path):
- """Get file path from augeas_vhost_path.
-
- Takes in Augeas path and returns the file name
-
- :param str vhost_path: Augeas virtual host path
-
- :returns: filename of vhost
- :rtype: str
-
- """
- if not vhost_path or not vhost_path.startswith("/files/"):
- return None
-
- return _split_aug_path(vhost_path)[0]
-
-
-def get_internal_aug_path(vhost_path):
- """Get the Augeas path for a vhost with the file path removed.
-
- :param str vhost_path: Augeas virtual host path
-
- :returns: Augeas path to vhost relative to the containing file
- :rtype: str
-
- """
- return _split_aug_path(vhost_path)[1]
-
-
-def _split_aug_path(vhost_path):
- """Splits an Augeas path into a file path and an internal path.
-
- After removing "/files", this function splits vhost_path into the
- file path and the remaining Augeas path.
-
- :param str vhost_path: Augeas virtual host path
-
- :returns: file path and internal Augeas path
- :rtype: `tuple` of `str`
-
- """
- # Strip off /files
- file_path = vhost_path[6:]
- internal_path = []
-
- # Remove components from the end of file_path until it becomes valid
- while not os.path.exists(file_path):
- file_path, _, internal_path_part = file_path.rpartition("/")
- internal_path.append(internal_path_part)
-
- return file_path, "/".join(reversed(internal_path))
-
-
-def install_ssl_options_conf(options_ssl, options_ssl_digest):
- """Copy Certbot's SSL options file into the system's config dir if required."""
-
- # XXX if we ever try to enforce a local privilege boundary (eg, running
- # certbot for unprivileged users via setuid), this function will need
- # to be modified.
- return common.install_version_controlled_file(options_ssl, options_ssl_digest,
- constants.os_constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
diff --git a/certbot-apache/certbot_apache/constants.py b/certbot-apache/certbot_apache/constants.py
index 0c5f7263a..a13ca04a6 100644
--- a/certbot-apache/certbot_apache/constants.py
+++ b/certbot-apache/certbot_apache/constants.py
@@ -1,151 +1,6 @@
"""Apache plugin constants."""
import pkg_resources
-from certbot import util
-CLI_DEFAULTS_DEFAULT = dict(
- server_root="/etc/apache2",
- vhost_root="/etc/apache2/sites-available",
- vhost_files="*",
- logs_root="/var/log/apache2",
- version_cmd=['apache2ctl', '-v'],
- define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apache2ctl', 'graceful'],
- conftest_cmd=['apache2ctl', 'configtest'],
- enmod=None,
- dismod=None,
- le_vhost_ext="-le-ssl.conf",
- handle_mods=False,
- handle_sites=False,
- challenge_location="/etc/apache2",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS_DEBIAN = dict(
- server_root="/etc/apache2",
- vhost_root="/etc/apache2/sites-available",
- vhost_files="*",
- logs_root="/var/log/apache2",
- version_cmd=['apache2ctl', '-v'],
- define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apache2ctl', 'graceful'],
- conftest_cmd=['apache2ctl', 'configtest'],
- enmod="a2enmod",
- dismod="a2dismod",
- le_vhost_ext="-le-ssl.conf",
- handle_mods=True,
- handle_sites=True,
- challenge_location="/etc/apache2",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS_CENTOS = dict(
- server_root="/etc/httpd",
- vhost_root="/etc/httpd/conf.d",
- vhost_files="*.conf",
- logs_root="/var/log/httpd",
- version_cmd=['apachectl', '-v'],
- define_cmd=['apachectl', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apachectl', 'graceful'],
- conftest_cmd=['apachectl', 'configtest'],
- enmod=None,
- dismod=None,
- le_vhost_ext="-le-ssl.conf",
- handle_mods=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")
-)
-CLI_DEFAULTS_GENTOO = dict(
- server_root="/etc/apache2",
- vhost_root="/etc/apache2/vhosts.d",
- vhost_files="*.conf",
- logs_root="/var/log/apache2",
- version_cmd=['/usr/sbin/apache2', '-v'],
- define_cmd=['apache2ctl', 'virtualhosts'],
- restart_cmd=['apache2ctl', 'graceful'],
- conftest_cmd=['apache2ctl', 'configtest'],
- enmod=None,
- dismod=None,
- le_vhost_ext="-le-ssl.conf",
- handle_mods=False,
- handle_sites=False,
- challenge_location="/etc/apache2/vhosts.d",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS_DARWIN = dict(
- server_root="/etc/apache2",
- vhost_root="/etc/apache2/other",
- vhost_files="*.conf",
- logs_root="/var/log/apache2",
- version_cmd=['/usr/sbin/httpd', '-v'],
- define_cmd=['/usr/sbin/httpd', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apachectl', 'graceful'],
- conftest_cmd=['apachectl', 'configtest'],
- enmod=None,
- dismod=None,
- le_vhost_ext="-le-ssl.conf",
- handle_mods=False,
- handle_sites=False,
- challenge_location="/etc/apache2/other",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS_SUSE = dict(
- server_root="/etc/apache2",
- vhost_root="/etc/apache2/vhosts.d",
- vhost_files="*.conf",
- logs_root="/var/log/apache2",
- version_cmd=['apache2ctl', '-v'],
- define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apache2ctl', 'graceful'],
- conftest_cmd=['apache2ctl', 'configtest'],
- enmod="a2enmod",
- dismod="a2dismod",
- le_vhost_ext="-le-ssl.conf",
- handle_mods=False,
- handle_sites=False,
- challenge_location="/etc/apache2/vhosts.d",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS_ARCH = dict(
- server_root="/etc/httpd",
- vhost_root="/etc/httpd/conf",
- vhost_files="*.conf",
- logs_root="/var/log/httpd",
- version_cmd=['apachectl', '-v'],
- define_cmd=['apachectl', '-t', '-D', 'DUMP_RUN_CFG'],
- restart_cmd=['apachectl', 'graceful'],
- conftest_cmd=['apachectl', 'configtest'],
- enmod=None,
- dismod=None,
- le_vhost_ext="-le-ssl.conf",
- handle_mods=False,
- handle_sites=False,
- challenge_location="/etc/httpd/conf",
- MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
- "certbot_apache", "options-ssl-apache.conf")
-)
-CLI_DEFAULTS = {
- "default": CLI_DEFAULTS_DEFAULT,
- "debian": CLI_DEFAULTS_DEBIAN,
- "ubuntu": CLI_DEFAULTS_DEBIAN,
- "centos": CLI_DEFAULTS_CENTOS,
- "centos linux": CLI_DEFAULTS_CENTOS,
- "fedora": CLI_DEFAULTS_CENTOS,
- "red hat enterprise linux server": CLI_DEFAULTS_CENTOS,
- "rhel": CLI_DEFAULTS_CENTOS,
- "amazon": CLI_DEFAULTS_CENTOS,
- "gentoo": CLI_DEFAULTS_GENTOO,
- "gentoo base system": CLI_DEFAULTS_GENTOO,
- "darwin": CLI_DEFAULTS_DARWIN,
- "opensuse": CLI_DEFAULTS_SUSE,
- "suse": CLI_DEFAULTS_SUSE,
- "arch": CLI_DEFAULTS_ARCH,
-}
-"""CLI defaults."""
MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
"""Name of the mod_ssl config file as saved in `IConfig.config_dir`."""
@@ -191,39 +46,3 @@ UIR_ARGS = ["always", "set", "Content-Security-Policy",
HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
"Upgrade-Insecure-Requests": UIR_ARGS}
-
-
-def os_constant(key):
- """
- Get a constant value for operating system
-
- :param key: name of cli constant
- :return: value of constant for active os
- """
-
- os_info = util.get_os_info()
- try:
- constants = CLI_DEFAULTS[os_info[0].lower()]
- except KeyError:
- constants = os_like_constants()
- if not constants:
- constants = CLI_DEFAULTS["default"]
- return constants[key]
-
-
-def os_like_constants():
- """
- Try to get constants for distribution with
- similar layout and configuration, indicated by
- /etc/os-release variable "LIKE"
-
- :returns: Constants dictionary
- :rtype: `dict`
- """
-
- os_like = util.get_systemd_os_like()
- if os_like:
- for os_name in os_like:
- if os_name in CLI_DEFAULTS.keys():
- return CLI_DEFAULTS[os_name]
- return {}
diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py
new file mode 100644
index 000000000..4267398d5
--- /dev/null
+++ b/certbot-apache/certbot_apache/entrypoint.py
@@ -0,0 +1,47 @@
+""" Entry point for Apache Plugin """
+from certbot import util
+
+from certbot_apache import configurator
+from certbot_apache import override_arch
+from certbot_apache import override_darwin
+from certbot_apache import override_debian
+from certbot_apache import override_centos
+from certbot_apache import override_gentoo
+from certbot_apache import override_suse
+
+OVERRIDE_CLASSES = {
+ "arch": override_arch.ArchConfigurator,
+ "darwin": override_darwin.DarwinConfigurator,
+ "debian": override_debian.DebianConfigurator,
+ "ubuntu": override_debian.DebianConfigurator,
+ "centos": override_centos.CentOSConfigurator,
+ "centos linux": override_centos.CentOSConfigurator,
+ "fedora": override_centos.CentOSConfigurator,
+ "red hat enterprise linux server": override_centos.CentOSConfigurator,
+ "rhel": override_centos.CentOSConfigurator,
+ "amazon": override_centos.CentOSConfigurator,
+ "gentoo": override_gentoo.GentooConfigurator,
+ "gentoo base system": override_gentoo.GentooConfigurator,
+ "opensuse": override_suse.OpenSUSEConfigurator,
+ "suse": override_suse.OpenSUSEConfigurator,
+}
+
+def get_configurator():
+ """ Get correct configurator class based on the OS fingerprint """
+ os_info = util.get_os_info()
+ override_class = None
+ try:
+ override_class = OVERRIDE_CLASSES[os_info[0].lower()]
+ except KeyError:
+ # OS not found in the list
+ os_like = util.get_systemd_os_like()
+ if os_like:
+ for os_name in os_like:
+ if os_name in OVERRIDE_CLASSES.keys():
+ override_class = OVERRIDE_CLASSES[os_name]
+ if not override_class:
+ # No override class found, return the generic configurator
+ override_class = configurator.ApacheConfigurator
+ return override_class
+
+ENTRYPOINT = get_configurator()
diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/override_arch.py
new file mode 100644
index 000000000..ea5155a3c
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_arch.py
@@ -0,0 +1,31 @@
+""" Distribution specific override class for Arch Linux """
+import pkg_resources
+
+import zope.interface
+
+from certbot import interfaces
+
+from certbot_apache import configurator
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class ArchConfigurator(configurator.ApacheConfigurator):
+ """Arch Linux specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/httpd",
+ vhost_root="/etc/httpd/conf",
+ vhost_files="*.conf",
+ logs_root="/var/log/httpd",
+ 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_sites=False,
+ challenge_location="/etc/httpd/conf",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "options-ssl-apache.conf")
+ )
diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py
new file mode 100644
index 000000000..db6cd6fba
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_centos.py
@@ -0,0 +1,59 @@
+""" Distribution specific override class for CentOS family (RHEL, Fedora) """
+import pkg_resources
+
+import zope.interface
+
+from certbot import interfaces
+
+from certbot_apache import apache_util
+from certbot_apache import configurator
+from certbot_apache import parser
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class CentOSConfigurator(configurator.ApacheConfigurator):
+ """CentOS specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/httpd",
+ vhost_root="/etc/httpd/conf.d",
+ vhost_files="*.conf",
+ logs_root="/var/log/httpd",
+ 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_sites=False,
+ challenge_location="/etc/httpd/conf.d",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "centos-options-ssl-apache.conf")
+ )
+
+ def get_parser(self):
+ """Initializes the ApacheParser"""
+ return CentOSParser(
+ self.aug, self.conf("server-root"), self.conf("vhost-root"),
+ self.version, configurator=self)
+
+
+class CentOSParser(parser.ApacheParser):
+ """CentOS specific ApacheParser override class"""
+ def __init__(self, *args, **kwargs):
+ # CentOS specific configuration file for Apache
+ self.sysconfig_filep = "/etc/sysconfig/httpd"
+ super(CentOSParser, self).__init__(*args, **kwargs)
+
+ def update_runtime_variables(self, *args, **kwargs):
+ """ Override for update_runtime_variables for custom parsing """
+ # Opportunistic, works if SELinux not enforced
+ super(CentOSParser, self).update_runtime_variables(*args, **kwargs)
+ self.parse_sysconfig_var()
+
+ def parse_sysconfig_var(self):
+ """ Parses Apache CLI options from CentOS configuration file """
+ defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS")
+ for k in defines.keys():
+ self.variables[k] = defines[k]
diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/override_darwin.py
new file mode 100644
index 000000000..53741d504
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_darwin.py
@@ -0,0 +1,31 @@
+""" Distribution specific override class for macOS """
+import pkg_resources
+
+import zope.interface
+
+from certbot import interfaces
+
+from certbot_apache import configurator
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class DarwinConfigurator(configurator.ApacheConfigurator):
+ """macOS specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/apache2",
+ vhost_root="/etc/apache2/other",
+ vhost_files="*.conf",
+ logs_root="/var/log/apache2",
+ version_cmd=['/usr/sbin/httpd', '-v'],
+ apache_cmd="/usr/sbin/httpd",
+ restart_cmd=['apachectl', 'graceful'],
+ conftest_cmd=['apachectl', 'configtest'],
+ enmod=None,
+ dismod=None,
+ le_vhost_ext="-le-ssl.conf",
+ handle_mods=False,
+ handle_sites=False,
+ challenge_location="/etc/apache2/other",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "options-ssl-apache.conf")
+ )
diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py
new file mode 100644
index 000000000..6e2e34ba9
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_debian.py
@@ -0,0 +1,144 @@
+""" Distribution specific override class for Debian family (Ubuntu/Debian) """
+import logging
+import os
+import pkg_resources
+
+import zope.interface
+
+from certbot import errors
+from certbot import interfaces
+from certbot import util
+
+from certbot_apache import apache_util
+from certbot_apache import configurator
+
+logger = logging.getLogger(__name__)
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class DebianConfigurator(configurator.ApacheConfigurator):
+ """Debian specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/apache2",
+ vhost_root="/etc/apache2/sites-available",
+ vhost_files="*",
+ logs_root="/var/log/apache2",
+ 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_sites=True,
+ challenge_location="/etc/apache2",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "options-ssl-apache.conf")
+ )
+
+ def enable_site(self, vhost):
+ """Enables an available site, Apache reload required.
+
+ .. note:: Does not make sure that the site correctly works or that all
+ modules are enabled appropriately.
+
+ :param vhost: vhost to enable
+ :type vhost: :class:`~certbot_apache.obj.VirtualHost`
+
+ :raises .errors.NotSupportedError: If filesystem layout is not
+ supported.
+
+ """
+ if vhost.enabled:
+ return
+
+ enabled_path = ("%s/sites-enabled/%s" %
+ (self.parser.root,
+ os.path.basename(vhost.filep)))
+ if not os.path.isdir(os.path.dirname(enabled_path)):
+ # For some reason, sites-enabled / sites-available do not exist
+ # Call the parent method
+ return super(DebianConfigurator, self).enable_site(vhost)
+ self.reverter.register_file_creation(False, enabled_path)
+ try:
+ os.symlink(vhost.filep, enabled_path)
+ except OSError as err:
+ if os.path.islink(enabled_path) and os.path.realpath(
+ enabled_path) == vhost.filep:
+ # Already in shape
+ vhost.enabled = True
+ return
+ else:
+ logger.warning(
+ "Could not symlink %s to %s, got error: %s", enabled_path,
+ vhost.filep, err.strerror)
+ errstring = ("Encountered error while trying to enable a " +
+ "newly created VirtualHost located at {0} by " +
+ "linking to it from {1}")
+ raise errors.NotSupportedError(errstring.format(vhost.filep,
+ enabled_path))
+ vhost.enabled = True
+ logger.info("Enabling available site: %s", vhost.filep)
+ self.save_notes += "Enabled site %s\n" % vhost.filep
+
+ def enable_mod(self, mod_name, temp=False):
+ # pylint: disable=unused-argument
+ """Enables module in Apache.
+
+ Both enables and reloads Apache so module is active.
+
+ :param str mod_name: Name of the module to enable. (e.g. 'ssl')
+ :param bool temp: Whether or not this is a temporary action.
+
+ :raises .errors.NotSupportedError: If the filesystem layout is not
+ supported.
+ :raises .errors.MisconfigurationError: If a2enmod or a2dismod cannot be
+ run.
+
+ """
+ avail_path = os.path.join(self.parser.root, "mods-available")
+ enabled_path = os.path.join(self.parser.root, "mods-enabled")
+ if not os.path.isdir(avail_path) or not os.path.isdir(enabled_path):
+ raise errors.NotSupportedError(
+ "Unsupported directory layout. You may try to enable mod %s "
+ "and try again." % mod_name)
+
+ deps = apache_util.get_mod_deps(mod_name)
+
+ # Enable all dependencies
+ for dep in deps:
+ if (dep + "_module") not in self.parser.modules:
+ self._enable_mod_debian(dep, temp)
+ self.parser.add_mod(dep)
+ note = "Enabled dependency of %s module - %s" % (mod_name, dep)
+ if not temp:
+ self.save_notes += note + os.linesep
+ logger.debug(note)
+
+ # Enable actual module
+ self._enable_mod_debian(mod_name, temp)
+ self.parser.add_mod(mod_name)
+
+ if not temp:
+ self.save_notes += "Enabled %s module in Apache\n" % mod_name
+ logger.info("Enabled Apache %s module", mod_name)
+
+ # Modules can enable additional config files. Variables may be defined
+ # within these new configuration sections.
+ # Reload is not necessary as DUMP_RUN_CFG uses latest config.
+ self.parser.update_runtime_variables()
+
+ def _enable_mod_debian(self, mod_name, temp):
+ """Assumes mods-available, mods-enabled layout."""
+ # 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")):
+ 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"), mod_name])
+ util.run_script([self.conf("enmod"), mod_name])
diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py
new file mode 100644
index 000000000..d4d4e96b9
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_gentoo.py
@@ -0,0 +1,58 @@
+""" Distribution specific override class for Gentoo Linux """
+import pkg_resources
+
+import zope.interface
+
+from certbot import interfaces
+
+from certbot_apache import apache_util
+from certbot_apache import configurator
+from certbot_apache import parser
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class GentooConfigurator(configurator.ApacheConfigurator):
+ """Gentoo specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/apache2",
+ vhost_root="/etc/apache2/vhosts.d",
+ vhost_files="*.conf",
+ logs_root="/var/log/apache2",
+ version_cmd=['/usr/sbin/apache2', '-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_sites=False,
+ challenge_location="/etc/apache2/vhosts.d",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "options-ssl-apache.conf")
+ )
+
+ def get_parser(self):
+ """Initializes the ApacheParser"""
+ return GentooParser(
+ self.aug, self.conf("server-root"), self.conf("vhost-root"),
+ self.version, configurator=self)
+
+
+class GentooParser(parser.ApacheParser):
+ """Gentoo specific ApacheParser override class"""
+ def __init__(self, *args, **kwargs):
+ # Gentoo specific configuration file for Apache2
+ self.apacheconfig_filep = "/etc/conf.d/apache2"
+ super(GentooParser, self).__init__(*args, **kwargs)
+
+ def update_runtime_variables(self):
+ """ Override for update_runtime_variables for custom parsing """
+ self.parse_sysconfig_var()
+
+ def parse_sysconfig_var(self):
+ """ Parses Apache CLI options from Gentoo configuration file """
+ defines = apache_util.parse_define_file(self.apacheconfig_filep,
+ "APACHE2_OPTS")
+ for k in defines.keys():
+ self.variables[k] = defines[k]
diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/override_suse.py
new file mode 100644
index 000000000..a67054b5b
--- /dev/null
+++ b/certbot-apache/certbot_apache/override_suse.py
@@ -0,0 +1,31 @@
+""" Distribution specific override class for OpenSUSE """
+import pkg_resources
+
+import zope.interface
+
+from certbot import interfaces
+
+from certbot_apache import configurator
+
+@zope.interface.provider(interfaces.IPluginFactory)
+class OpenSUSEConfigurator(configurator.ApacheConfigurator):
+ """OpenSUSE specific ApacheConfigurator override class"""
+
+ OS_DEFAULTS = dict(
+ server_root="/etc/apache2",
+ vhost_root="/etc/apache2/vhosts.d",
+ vhost_files="*.conf",
+ logs_root="/var/log/apache2",
+ 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=False,
+ handle_sites=False,
+ challenge_location="/etc/apache2/vhosts.d",
+ MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
+ "certbot_apache", "options-ssl-apache.conf")
+ )
diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py
index b15608d61..7715d2c35 100644
--- a/certbot-apache/certbot_apache/parser.py
+++ b/certbot-apache/certbot_apache/parser.py
@@ -11,8 +11,6 @@ import six
from certbot import errors
-from certbot_apache import constants
-
logger = logging.getLogger(__name__)
@@ -40,14 +38,9 @@ class ApacheParser(object):
# issues with aug.load() after adding new files / defines to parse tree
self.configurator = configurator
- # This uses the binary, so it can be done first.
- # https://httpd.apache.org/docs/2.4/mod/core.html#define
- # https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine
- # This only handles invocation parameters and Define directives!
+ self.modules = set()
self.parser_paths = {}
self.variables = {}
- if version >= (2, 4):
- self.update_runtime_variables()
self.aug = aug
# Find configuration root and make sure augeas can parse it.
@@ -55,24 +48,26 @@ class ApacheParser(object):
self.loc = {"root": self._find_config_root()}
self.parse_file(self.loc["root"])
+ if version >= (2, 4):
+ # Look up variables from httpd and add to DOM if not already parsed
+ self.update_runtime_variables()
+
# This problem has been fixed in Augeas 1.0
self.standardize_excl()
- # Temporarily set modules to be empty, so that find_dirs can work
- # https://httpd.apache.org/docs/2.4/mod/core.html#ifmodule
- # This needs to come before locations are set.
- self.modules = set()
- self.init_modules()
+ # Parse LoadModule directives from configuration files
+ self.parse_modules()
# Set up rest of locations
self.loc.update(self._set_locations())
+ # list of the active include paths, before modifications
self.existing_paths = copy.deepcopy(self.parser_paths)
# Must also attempt to parse additional virtual host root
if vhostroot:
self.parse_file(os.path.abspath(vhostroot) + "/" +
- constants.os_constant("vhost_files"))
+ self.configurator.constant("vhost_files"))
# check to see if there were unparsed define statements
if version < (2, 4):
@@ -103,50 +98,61 @@ class ApacheParser(object):
# Create a new path
self.existing_paths[new_dir] = [new_file]
- def init_modules(self):
+ def add_mod(self, mod_name):
+ """Shortcut for updating parser modules."""
+ if mod_name + "_module" not in self.modules:
+ self.modules.add(mod_name + "_module")
+ if "mod_" + mod_name + ".c" not in self.modules:
+ self.modules.add("mod_" + mod_name + ".c")
+
+ def reset_modules(self):
+ """Reset the loaded modules list. This is called from cleanup to clear
+ temporarily loaded modules."""
+ self.modules = set()
+ self.update_modules()
+ self.parse_modules()
+
+ def parse_modules(self):
"""Iterates on the configuration until no new modules are loaded.
..todo:: This should be attempted to be done with a binary to avoid
the iteration issue. Else... parse and enable mods at same time.
"""
- # Since modules are being initiated... clear existing set.
- self.modules = set()
+ mods = set()
matches = self.find_dir("LoadModule")
-
iterator = iter(matches)
# Make sure prev_size != cur_size for do: while: iteration
prev_size = -1
- while len(self.modules) != prev_size:
- prev_size = len(self.modules)
+ while len(mods) != prev_size:
+ prev_size = len(mods)
for match_name, match_filename in six.moves.zip(
iterator, iterator):
mod_name = self.get_arg(match_name)
mod_filename = self.get_arg(match_filename)
if mod_name and mod_filename:
- self.modules.add(mod_name)
- self.modules.add(os.path.basename(mod_filename)[:-2] + "c")
+ mods.add(mod_name)
+ mods.add(os.path.basename(mod_filename)[:-2] + "c")
else:
logger.debug("Could not read LoadModule directive from " +
"Augeas path: {0}".format(match_name[6:]))
+ self.modules.update(mods)
def update_runtime_variables(self):
- """"
+ """Update Includes, Defines and Includes from httpd config dump data"""
+ self.update_defines()
+ self.update_includes()
+ self.update_modules()
- .. note:: Compile time variables (apache2ctl -V) are not used within
- the dynamic configuration files. These should not be parsed or
- interpreted.
-
- .. todo:: Create separate compile time variables...
- simply for arg_get()
-
- """
- stdout = self._get_runtime_cfg()
+ def update_defines(self):
+ """Get Defines from httpd process"""
variables = dict()
- matches = re.compile(r"Define: ([^ \n]*)").findall(stdout)
+ define_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
+ "DUMP_RUN_CFG"]
+ matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
try:
matches.remove("DUMP_RUN_CFG")
except ValueError:
@@ -163,15 +169,54 @@ class ApacheParser(object):
self.variables = variables
- def _get_runtime_cfg(self): # pylint: disable=no-self-use
- """Get runtime configuration info.
+ def update_includes(self):
+ """Get includes from httpd process, and add them to DOM if needed"""
- :returns: stdout from DUMP_RUN_CFG
+ # Find_dir iterates over configuration for Include and IncludeOptional
+ # directives to make sure we see the full include tree present in the
+ # configuration files
+ _ = self.find_dir("Include")
+
+ inc_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
+ "DUMP_INCLUDES"]
+ matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
+ if matches:
+ for i in matches:
+ if not self.parsed_in_current(i):
+ self.parse_file(i)
+
+ def update_modules(self):
+ """Get loaded modules from httpd process, and add them to DOM"""
+
+ mod_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
+ "DUMP_MODULES"]
+ matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
+ for mod in matches:
+ self.add_mod(mod.strip())
+
+ def parse_from_subprocess(self, command, regexp):
+ """Get values from stdout of subprocess command
+
+ :param list command: Command to run
+ :param str regexp: Regexp for parsing
+
+ :returns: list parsed from command output
+ :rtype: list
+
+ """
+ stdout = self._get_runtime_cfg(command)
+ return re.compile(regexp).findall(stdout)
+
+ def _get_runtime_cfg(self, command): # pylint: disable=no-self-use
+ """Get runtime configuration info.
+ :param command: Command to run
+
+ :returns: stdout from command
"""
try:
proc = subprocess.Popen(
- constants.os_constant("define_cmd"),
+ command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
@@ -180,10 +225,10 @@ class ApacheParser(object):
except (OSError, ValueError):
logger.error(
"Error running command %s for runtime parameters!%s",
- constants.os_constant("define_cmd"), os.linesep)
+ command, os.linesep)
raise errors.MisconfigurationError(
"Error accessing loaded Apache parameters: %s",
- constants.os_constant("define_cmd"))
+ command)
# Small errors that do not impede
if proc.returncode != 0:
logger.warning("Error in checking parameter list: %s", stderr)
diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py
new file mode 100644
index 000000000..7ca47a4d5
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/centos_test.py
@@ -0,0 +1,123 @@
+"""Test for certbot_apache.configurator for Centos overrides"""
+import os
+import unittest
+
+import mock
+
+from certbot_apache import obj
+from certbot_apache import override_centos
+from certbot_apache.tests import util
+
+def get_vh_truth(temp_dir, config_name):
+ """Return the ground truth for the specified directory."""
+ prefix = os.path.join(
+ temp_dir, config_name, "httpd/conf.d")
+
+ aug_pre = "/files" + prefix
+ vh_truth = [
+ obj.VirtualHost(
+ os.path.join(prefix, "centos.example.com.conf"),
+ os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"),
+ set([obj.Addr.fromstring("*:80")]),
+ False, True, "centos.example.com"),
+ obj.VirtualHost(
+ os.path.join(prefix, "ssl.conf"),
+ os.path.join(aug_pre, "ssl.conf/VirtualHost"),
+ set([obj.Addr.fromstring("_default_:443")]),
+ True, True, None)
+ ]
+ return vh_truth
+
+class MultipleVhostsTestCentOS(util.ApacheTest):
+ """Multiple vhost tests for CentOS / RHEL family of distros"""
+
+ _multiprocess_can_split_ = True
+
+ def setUp(self): # pylint: disable=arguments-differ
+ test_dir = "centos7_apache/apache"
+ config_root = "centos7_apache/apache/httpd"
+ vhost_root = "centos7_apache/apache/httpd/conf.d"
+ super(MultipleVhostsTestCentOS, self).setUp(test_dir=test_dir,
+ config_root=config_root,
+ vhost_root=vhost_root)
+
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir, self.work_dir,
+ os_info="centos")
+ self.vh_truth = get_vh_truth(
+ self.temp_dir, "centos7_apache/apache")
+
+ def test_get_parser(self):
+ self.assertTrue(isinstance(self.config.parser,
+ override_centos.CentOSParser))
+
+ @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
+ def test_opportunistic_httpd_runtime_parsing(self, mock_get):
+ define_val = (
+ 'Define: TEST1\n'
+ 'Define: TEST2\n'
+ 'Define: DUMP_RUN_CFG\n'
+ )
+ mod_val = (
+ 'Loaded Modules:\n'
+ ' mock_module (static)\n'
+ ' another_module (static)\n'
+ )
+ def mock_get_cfg(command):
+ """Mock httpd process stdout"""
+ if command == ['apachectl', '-t', '-D', 'DUMP_RUN_CFG']:
+ return define_val
+ elif command == ['apachectl', '-t', '-D', 'DUMP_MODULES']:
+ return mod_val
+ return ""
+ mock_get.side_effect = mock_get_cfg
+ self.config.parser.modules = set()
+ self.config.parser.variables = {}
+
+ with mock.patch("certbot.util.get_os_info") as mock_osi:
+ # Make sure we have the have the CentOS httpd constants
+ mock_osi.return_value = ("centos", "7")
+ self.config.parser.update_runtime_variables()
+
+ self.assertEquals(mock_get.call_count, 3)
+ self.assertEquals(len(self.config.parser.modules), 4)
+ self.assertEquals(len(self.config.parser.variables), 2)
+ self.assertTrue("TEST2" in self.config.parser.variables.keys())
+ self.assertTrue("mod_another.c" in self.config.parser.modules)
+
+ def test_get_virtual_hosts(self):
+ """Make sure all vhosts are being properly found."""
+ vhs = self.config.get_virtual_hosts()
+ self.assertEqual(len(vhs), 2)
+ found = 0
+
+ for vhost in vhs:
+ for centos_truth in self.vh_truth:
+ if vhost == centos_truth:
+ found += 1
+ break
+ else:
+ raise Exception("Missed: %s" % vhost) # pragma: no cover
+ self.assertEqual(found, 2)
+
+ @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
+ def test_get_sysconfig_vars(self, mock_cfg):
+ """Make sure we read the sysconfig OPTIONS variable correctly"""
+ # Return nothing for the process calls
+ mock_cfg.return_value = ""
+ self.config.parser.sysconfig_filep = os.path.realpath(
+ os.path.join(self.config.parser.root, "../sysconfig/httpd"))
+ self.config.parser.variables = {}
+
+ with mock.patch("certbot.util.get_os_info") as mock_osi:
+ # Make sure we have the have the CentOS httpd constants
+ mock_osi.return_value = ("centos", "7")
+ self.config.parser.update_runtime_variables()
+
+ self.assertTrue("mock_define" in self.config.parser.variables.keys())
+ self.assertTrue("mock_define_too" in self.config.parser.variables.keys())
+ self.assertTrue("mock_value" in self.config.parser.variables.keys())
+ self.assertEqual("TRUE", self.config.parser.variables["mock_value"])
+
+if __name__ == "__main__":
+ unittest.main() # pragma: no cover
diff --git a/certbot-apache/certbot_apache/tests/complex_parsing_test.py b/certbot-apache/certbot_apache/tests/complex_parsing_test.py
index 079d7e95f..a296fb0eb 100644
--- a/certbot-apache/certbot_apache/tests/complex_parsing_test.py
+++ b/certbot-apache/certbot_apache/tests/complex_parsing_test.py
@@ -18,7 +18,7 @@ class ComplexParserTest(util.ParserTest):
self.setup_variables()
# This needs to happen after due to setup_variables not being run
# until after
- self.parser.init_modules() # pylint: disable=protected-access
+ self.parser.parse_modules() # pylint: disable=protected-access
def tearDown(self):
shutil.rmtree(self.temp_dir)
diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py
index 90561d6ad..4f85e1e3f 100644
--- a/certbot-apache/certbot_apache/tests/configurator_test.py
+++ b/certbot-apache/certbot_apache/tests/configurator_test.py
@@ -3,12 +3,12 @@
import os
import shutil
import socket
+import tempfile
import unittest
import mock
# six is used in mock.patch()
import six # pylint: disable=unused-import
-import tempfile
from acme import challenges
@@ -19,7 +19,7 @@ from certbot import errors
from certbot.tests import acme_util
from certbot.tests import util as certbot_util
-from certbot_apache import configurator
+from certbot_apache import apache_util
from certbot_apache import constants
from certbot_apache import parser
from certbot_apache import obj
@@ -34,39 +34,24 @@ class MultipleVhostsTest(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ
super(MultipleVhostsTest, self).setUp()
- from certbot_apache.constants import os_constant
- orig_os_constant = os_constant
- def mock_os_constant(key, vhost_path=self.vhost_path):
- """Mock default vhost path"""
- if key == "vhost_root":
- return vhost_path
- else:
- return orig_os_constant(key)
-
- with mock.patch("certbot_apache.constants.os_constant") as mock_c:
- mock_c.side_effect = mock_os_constant
- self.config = util.get_apache_configurator(
- self.config_path, None, self.config_dir, self.work_dir)
- self.config = self.mock_deploy_cert(self.config)
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir, self.work_dir)
+ self.config = self.mock_deploy_cert(self.config)
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
def mock_deploy_cert(self, config):
"""A test for a mock deploy cert"""
- self.config.real_deploy_cert = self.config.deploy_cert
+ config.real_deploy_cert = self.config.deploy_cert
def mocked_deploy_cert(*args, **kwargs):
"""a helper to mock a deployed cert"""
- with mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod"):
+ g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod"
+ with mock.patch(g_mod):
config.real_deploy_cert(*args, **kwargs)
self.config.deploy_cert = mocked_deploy_cert
return self.config
- def tearDown(self):
- shutil.rmtree(self.temp_dir)
- shutil.rmtree(self.config_dir)
- shutil.rmtree(self.work_dir)
-
@mock.patch("certbot_apache.configurator.ApacheConfigurator.init_augeas")
@mock.patch("certbot_apache.configurator.path_surgery")
def test_prepare_no_install(self, mock_surgery, _init_augeas):
@@ -130,6 +115,10 @@ class MultipleVhostsTest(util.ApacheTest):
# Weak test..
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)
+
@certbot_util.patch_get_utility()
def test_get_all_names(self, mock_getutility):
mock_utility = mock_getutility()
@@ -163,13 +152,12 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue("certbot.demo" in names)
def test_get_bad_path(self):
- from certbot_apache.configurator import get_file_path
- self.assertEqual(get_file_path(None), None)
- self.assertEqual(get_file_path("nonexistent"), None)
+ self.assertEqual(apache_util.get_file_path(None), None)
+ self.assertEqual(apache_util.get_file_path("nonexistent"), None)
self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access
def test_get_aug_internal_path(self):
- from certbot_apache.configurator import get_internal_aug_path
+ from certbot_apache.apache_util import get_internal_aug_path
internal_paths = [
"Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost",
"Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost",
@@ -319,190 +307,23 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.assertEqual(len(self.config._non_default_vhosts()), 8)
- @mock.patch("certbot.util.run_script")
- @mock.patch("certbot.util.exe_exists")
- @mock.patch("certbot_apache.parser.subprocess.Popen")
- def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script):
- mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "")
- mock_popen().returncode = 0
- mock_exe_exists.return_value = True
-
- self.config.enable_mod("ssl")
- self.assertTrue("ssl_module" in self.config.parser.modules)
- self.assertTrue("mod_ssl.c" in self.config.parser.modules)
-
- self.assertTrue(mock_run_script.called)
-
- def test_enable_mod_unsupported_dirs(self):
- shutil.rmtree(os.path.join(self.config.parser.root, "mods-enabled"))
- self.assertRaises(
- errors.NotSupportedError, self.config.enable_mod, "ssl")
-
- @mock.patch("certbot.util.exe_exists")
- def test_enable_mod_no_disable(self, mock_exe_exists):
- mock_exe_exists.return_value = False
- self.assertRaises(
- errors.MisconfigurationError, self.config.enable_mod, "ssl")
-
- def test_enable_site_already_enabled(self):
- self.assertTrue(self.vh_truth[1].enabled)
- self.config.enable_site(self.vh_truth[1])
-
- def test_enable_site_failure(self):
- self.config.parser.root = "/tmp/nonexistent"
- self.assertRaises(
- errors.NotSupportedError,
- self.config.enable_site,
- obj.VirtualHost("asdf", "afsaf", set(), False, False))
-
- def test_enable_site_nondebian(self):
- mock_c = "certbot_apache.configurator.ApacheConfigurator.conf"
- def conf_side_effect(arg):
- """ Mock function for ApacheConfigurator.conf """
- confvars = {"handle-sites": False}
- if arg in confvars:
- return confvars[arg]
- inc_path = "/path/to/wherever"
- vhost = self.vh_truth[0]
- with mock.patch(mock_c) as mock_conf:
- mock_conf.side_effect = conf_side_effect
- vhost.enabled = False
- vhost.filep = inc_path
- self.assertFalse(self.config.parser.find_dir("Include", inc_path))
- self.assertFalse(
- os.path.dirname(inc_path) in self.config.parser.existing_paths)
- self.config.enable_site(vhost)
- self.assertTrue(self.config.parser.find_dir("Include", inc_path))
- self.assertTrue(
- os.path.dirname(inc_path) in self.config.parser.existing_paths)
- self.assertTrue(
- os.path.basename(inc_path) in self.config.parser.existing_paths[
- os.path.dirname(inc_path)])
-
def test_deploy_cert_enable_new_vhost(self):
# Create
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
+ self.config.parser.modules.add("socache_shmcb_module")
+
self.assertFalse(ssl_vhost.enabled)
self.config.deploy_cert(
"encryption-example.demo", "example/cert.pem", "example/key.pem",
"example/cert_chain.pem", "example/fullchain.pem")
self.assertTrue(ssl_vhost.enabled)
- # Make sure that we don't error out if symlink already exists
- ssl_vhost.enabled = False
- self.assertFalse(ssl_vhost.enabled)
- self.config.deploy_cert(
- "encryption-example.demo", "example/cert.pem", "example/key.pem",
- "example/cert_chain.pem", "example/fullchain.pem")
- self.assertTrue(ssl_vhost.enabled)
-
- def test_deploy_cert_newssl(self):
- self.config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir,
- self.work_dir, version=(2, 4, 16))
-
- self.config.parser.modules.add("ssl_module")
- self.config.parser.modules.add("mod_ssl.c")
-
- # Get the default 443 vhost
- self.config.assoc["random.demo"] = self.vh_truth[1]
- self.config = self.mock_deploy_cert(self.config)
- self.config.deploy_cert(
- "random.demo", "example/cert.pem", "example/key.pem",
- "example/cert_chain.pem", "example/fullchain.pem")
- self.config.save()
-
- # Verify ssl_module was enabled.
- self.assertTrue(self.vh_truth[1].enabled)
- self.assertTrue("ssl_module" in self.config.parser.modules)
-
- loc_cert = self.config.parser.find_dir(
- "sslcertificatefile", "example/fullchain.pem",
- self.vh_truth[1].path)
- loc_key = self.config.parser.find_dir(
- "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path)
-
- # Verify one directive was found in the correct file
- self.assertEqual(len(loc_cert), 1)
- self.assertEqual(
- configurator.get_file_path(loc_cert[0]),
- self.vh_truth[1].filep)
-
- self.assertEqual(len(loc_key), 1)
- self.assertEqual(
- configurator.get_file_path(loc_key[0]),
- self.vh_truth[1].filep)
-
- def test_deploy_cert_newssl_no_fullchain(self):
- self.config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir,
- self.work_dir, version=(2, 4, 16))
- self.config = self.mock_deploy_cert(self.config)
-
- self.config.parser.modules.add("ssl_module")
- self.config.parser.modules.add("mod_ssl.c")
-
- # Get the default 443 vhost
- self.config.assoc["random.demo"] = self.vh_truth[1]
- self.assertRaises(errors.PluginError,
- lambda: self.config.deploy_cert(
- "random.demo", "example/cert.pem",
- "example/key.pem"))
-
- def test_deploy_cert_old_apache_no_chain(self):
- self.config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir,
- self.work_dir, version=(2, 4, 7))
- self.config = self.mock_deploy_cert(self.config)
-
- self.config.parser.modules.add("ssl_module")
- self.config.parser.modules.add("mod_ssl.c")
-
- # Get the default 443 vhost
- self.config.assoc["random.demo"] = self.vh_truth[1]
- self.assertRaises(errors.PluginError,
- lambda: self.config.deploy_cert(
- "random.demo", "example/cert.pem",
- "example/key.pem"))
-
- def test_deploy_cert_not_parsed_path(self):
- # Make sure that we add include to root config for vhosts when
- # handle-sites is false
- self.config.parser.modules.add("ssl_module")
- self.config.parser.modules.add("mod_ssl.c")
- tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot"))
- os.chmod(tmp_path, 0o755)
- mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path"
- mock_a = "certbot_apache.parser.ApacheParser.add_include"
- mock_c = "certbot_apache.configurator.ApacheConfigurator.conf"
- orig_conf = self.config.conf
- def conf_side_effect(arg):
- """ Mock function for ApacheConfigurator.conf """
- confvars = {"handle-sites": False}
- if arg in confvars:
- return confvars[arg]
- else:
- return orig_conf("arg")
-
- with mock.patch(mock_c) as mock_conf:
- mock_conf.side_effect = conf_side_effect
- with mock.patch(mock_p) as mock_path:
- mock_path.return_value = os.path.join(tmp_path, "whatever.conf")
- with mock.patch(mock_a) as mock_add:
- self.config.deploy_cert(
- "encryption-example.demo",
- "example/cert.pem", "example/key.pem",
- "example/cert_chain.pem")
- # Test that we actually called add_include
- self.assertTrue(mock_add.called)
- shutil.rmtree(tmp_path)
-
def test_deploy_cert(self):
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
-
+ self.config.parser.modules.add("socache_shmcb_module")
# Patch _add_dummy_ssl_directives to make sure we write them correctly
# pylint: disable=protected-access
orig_add_dummy = self.config._add_dummy_ssl_directives
@@ -531,7 +352,6 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue(
"insert_key_file_path" in find_args(vhostpath,
"SSLCertificateKeyFile"))
-
# pylint: disable=protected-access
self.config._add_dummy_ssl_directives = mock_add_dummy_ssl
@@ -557,17 +377,17 @@ class MultipleVhostsTest(util.ApacheTest):
# Verify one directive was found in the correct file
self.assertEqual(len(loc_cert), 1)
self.assertEqual(
- configurator.get_file_path(loc_cert[0]),
+ apache_util.get_file_path(loc_cert[0]),
self.vh_truth[1].filep)
self.assertEqual(len(loc_key), 1)
self.assertEqual(
- configurator.get_file_path(loc_key[0]),
+ apache_util.get_file_path(loc_key[0]),
self.vh_truth[1].filep)
self.assertEqual(len(loc_chain), 1)
self.assertEqual(
- configurator.get_file_path(loc_chain[0]),
+ apache_util.get_file_path(loc_chain[0]),
self.vh_truth[1].filep)
# One more time for chain directive setting
@@ -877,7 +697,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(mock_restart.call_count, 1)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
- def test_cleanup(self, mock_restart):
+ @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()
self.config._chall_out.add(achall1) # pylint: disable=protected-access
@@ -890,7 +712,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue(mock_restart.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
- def test_cleanup_no_errors(self, mock_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()
self.config._chall_out.add(achall1) # pylint: disable=protected-access
@@ -951,10 +775,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue(isinstance(self.config.get_chall_pref(""), list))
def test_install_ssl_options_conf(self):
- from certbot_apache.configurator import install_ssl_options_conf
path = os.path.join(self.work_dir, "test_it")
other_path = os.path.join(self.work_dir, "other_test_it")
- install_ssl_options_conf(path, other_path)
+ self.config.install_ssl_options_conf(path, other_path)
self.assertTrue(os.path.isfile(path))
self.assertTrue(os.path.isfile(other_path))
@@ -994,20 +817,17 @@ class MultipleVhostsTest(util.ApacheTest):
errors.PluginError,
self.config.enhance, "certbot.demo", "unknown_enhancement")
- @mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
- def test_ocsp_stapling(self, mock_exe, mock_run_script):
+ def test_ocsp_stapling(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
+ self.config.parser.modules.add("socache_shmcb_module")
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.enhance("certbot.demo", "staple-ocsp")
- self.assertTrue("socache_shmcb_module" in self.config.parser.modules)
- self.assertTrue(mock_run_script.called)
-
# Get the ssl vhost for certbot.demo
ssl_vhost = self.config.assoc["certbot.demo"]
@@ -1077,14 +897,13 @@ class MultipleVhostsTest(util.ApacheTest):
def test_http_header_hsts(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
+ self.config.parser.modules.add("headers_module")
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.enhance("certbot.demo", "ensure-http-header",
"Strict-Transport-Security")
- self.assertTrue("headers_module" in self.config.parser.modules)
-
# Get the ssl vhost for certbot.demo
ssl_vhost = self.config.assoc["certbot.demo"]
@@ -1115,6 +934,8 @@ class MultipleVhostsTest(util.ApacheTest):
def test_http_header_uir(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
+ self.config.parser.modules.add("headers_module")
+
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
@@ -1151,6 +972,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_well_formed_http(self, mock_exe, _):
+ self.config.parser.modules.add("rewrite_module")
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2))
@@ -1173,8 +995,6 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3]))
self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3]))
- self.assertTrue("rewrite_module" in self.config.parser.modules)
-
def test_rewrite_rule_exists(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
@@ -1196,6 +1016,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_existing_rewrite(self, mock_exe, _):
+ self.config.parser.modules.add("rewrite_module")
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
@@ -1228,6 +1049,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_old_https_redirection(self, mock_exe, _):
+ self.config.parser.modules.add("rewrite_module")
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
@@ -1365,6 +1187,57 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.aug.match.side_effect = RuntimeError
self.assertFalse(self.config._check_aug_version())
+ def test_enable_site_nondebian(self):
+ inc_path = "/path/to/wherever"
+ vhost = self.vh_truth[0]
+ vhost.enabled = False
+ vhost.filep = inc_path
+ self.assertFalse(self.config.parser.find_dir("Include", inc_path))
+ self.assertFalse(
+ os.path.dirname(inc_path) in self.config.parser.existing_paths)
+ self.config.enable_site(vhost)
+ self.assertTrue(self.config.parser.find_dir("Include", inc_path))
+ self.assertTrue(
+ os.path.dirname(inc_path) in self.config.parser.existing_paths)
+ self.assertTrue(
+ os.path.basename(inc_path) in self.config.parser.existing_paths[
+ os.path.dirname(inc_path)])
+
+ def test_deploy_cert_not_parsed_path(self):
+ # Make sure that we add include to root config for vhosts when
+ # handle-sites is false
+ self.config.parser.modules.add("ssl_module")
+ self.config.parser.modules.add("mod_ssl.c")
+ self.config.parser.modules.add("socache_shmcb_module")
+ tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot"))
+ os.chmod(tmp_path, 0o755)
+ mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path"
+ mock_a = "certbot_apache.parser.ApacheParser.add_include"
+
+ with mock.patch(mock_p) as mock_path:
+ mock_path.return_value = os.path.join(tmp_path, "whatever.conf")
+ with mock.patch(mock_a) as mock_add:
+ self.config.deploy_cert(
+ "encryption-example.demo",
+ "example/cert.pem", "example/key.pem",
+ "example/cert_chain.pem")
+ # Test that we actually called add_include
+ self.assertTrue(mock_add.called)
+ shutil.rmtree(tmp_path)
+
+ @mock.patch("certbot_apache.parser.ApacheParser.parsed_in_original")
+ def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed):
+ ret_vh = self.vh_truth[8]
+ ret_vh.enabled = True
+ self.config.enable_site(ret_vh)
+ # Make sure that we return early
+ self.assertFalse(mock_parsed.called)
+
+ def test_enable_mod_unsupported(self):
+ self.assertRaises(errors.MisconfigurationError,
+ self.config.enable_mod,
+ "whatever")
+
class AugeasVhostsTest(util.ApacheTest):
"""Test vhosts with illegal names dependent on augeas version."""
# pylint: disable=protected-access
@@ -1378,12 +1251,8 @@ class AugeasVhostsTest(util.ApacheTest):
vhost_root=vr)
self.config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir, self.work_dir)
-
- def tearDown(self):
- shutil.rmtree(self.temp_dir)
- shutil.rmtree(self.config_dir)
- shutil.rmtree(self.work_dir)
+ self.config_path, self.vhost_path, self.config_dir,
+ self.work_dir)
def test_choosevhost_with_illegal_name(self):
self.config.aug = mock.MagicMock()
@@ -1461,15 +1330,11 @@ class MultiVhostsTest(util.ApacheTest):
vhost_root=vr)
self.config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir, self.work_dir)
+ self.config_path, self.vhost_path,
+ self.config_dir, self.work_dir, conf_vhost_path=self.vhost_path)
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multi_vhosts")
- def tearDown(self):
- shutil.rmtree(self.temp_dir)
- shutil.rmtree(self.config_dir)
- shutil.rmtree(self.work_dir)
-
def test_make_vhost_ssl(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
@@ -1569,11 +1434,11 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
def _call(self):
- from certbot_apache.configurator import install_ssl_options_conf
- install_ssl_options_conf(self.config.mod_ssl_conf, self.config.updated_mod_ssl_conf_digest)
+ self.config.install_ssl_options_conf(self.config.mod_ssl_conf,
+ self.config.updated_mod_ssl_conf_digest)
def _current_ssl_options_hash(self):
- return crypto_util.sha256sum(constants.os_constant("MOD_SSL_CONF_SRC"))
+ return crypto_util.sha256sum(self.config.constant("MOD_SSL_CONF_SRC"))
def _assert_current_file(self):
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
@@ -1608,7 +1473,8 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self._call()
self.assertFalse(mock_logger.warning.called)
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
- self.assertEqual(crypto_util.sha256sum(constants.os_constant("MOD_SSL_CONF_SRC")),
+ self.assertEqual(crypto_util.sha256sum(
+ self.config.constant("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
self._current_ssl_options_hash())
@@ -1623,7 +1489,8 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.assertEqual(mock_logger.warning.call_args[0][0],
"%s has been manually modified; updated file "
"saved to %s. We recommend updating %s for security purposes.")
- self.assertEqual(crypto_util.sha256sum(constants.os_constant("MOD_SSL_CONF_SRC")),
+ self.assertEqual(crypto_util.sha256sum(
+ self.config.constant("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
# only print warning once
with mock.patch("certbot.plugins.common.logger") as mock_logger:
diff --git a/certbot-apache/certbot_apache/tests/constants_test.py b/certbot-apache/certbot_apache/tests/constants_test.py
deleted file mode 100644
index 5ab324101..000000000
--- a/certbot-apache/certbot_apache/tests/constants_test.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""Test for certbot_apache.configurator."""
-
-import mock
-import unittest
-
-from certbot_apache import constants
-
-
-class ConstantsTest(unittest.TestCase):
-
- @mock.patch("certbot.util.get_os_info")
- def test_get_debian_value(self, os_info):
- os_info.return_value = ('Debian', '', '')
- self.assertEqual(constants.os_constant("vhost_root"),
- "/etc/apache2/sites-available")
-
- @mock.patch("certbot.util.get_os_info")
- def test_get_centos_value(self, os_info):
- os_info.return_value = ('CentOS Linux', '', '')
- self.assertEqual(constants.os_constant("vhost_root"),
- "/etc/httpd/conf.d")
-
- @mock.patch("certbot.util.get_systemd_os_like")
- @mock.patch("certbot.util.get_os_info")
- def test_get_default_values(self, os_info, os_like):
- os_info.return_value = ('Nonexistent Linux', '', '')
- os_like.return_value = {}
- self.assertFalse(constants.os_constant("handle_mods"))
- self.assertEqual(constants.os_constant("server_root"), "/etc/apache2")
- self.assertEqual(constants.os_constant("vhost_root"),
- "/etc/apache2/sites-available")
-
- @mock.patch("certbot.util.get_systemd_os_like")
- @mock.patch("certbot.util.get_os_info")
- def test_get_darwin_like_values(self, os_info, os_like):
- os_info.return_value = ('Nonexistent Linux', '', '')
- os_like.return_value = ["something", "nonexistent", "darwin"]
- self.assertFalse(constants.os_constant("enmod"))
- self.assertEqual(constants.os_constant("vhost_root"),
- "/etc/apache2/other")
-
-
-if __name__ == "__main__":
- unittest.main() # pragma: no cover
diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py
new file mode 100644
index 000000000..a648101e9
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/debian_test.py
@@ -0,0 +1,209 @@
+"""Test for certbot_apache.configurator for Debian overrides"""
+import os
+import shutil
+import unittest
+
+import mock
+
+from certbot import errors
+
+from certbot_apache import apache_util
+from certbot_apache import obj
+from certbot_apache.tests import util
+
+
+class MultipleVhostsTestDebian(util.ApacheTest):
+ """Multiple vhost tests for Debian family of distros"""
+
+ _multiprocess_can_split_ = True
+
+ 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,
+ os_info="debian")
+ self.config = self.mock_deploy_cert(self.config)
+ self.vh_truth = util.get_vh_truth(self.temp_dir,
+ "debian_apache_2_4/multiple_vhosts")
+
+ def mock_deploy_cert(self, config):
+ """A test for a mock deploy cert"""
+ config.real_deploy_cert = self.config.deploy_cert
+
+ def mocked_deploy_cert(*args, **kwargs):
+ """a helper to mock a deployed cert"""
+ g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod"
+ d_mod = "certbot_apache.override_debian.DebianConfigurator.enable_mod"
+ with mock.patch(g_mod):
+ with mock.patch(d_mod):
+ config.real_deploy_cert(*args, **kwargs)
+ self.config.deploy_cert = mocked_deploy_cert
+ return self.config
+
+ def test_enable_mod_unsupported_dirs(self):
+ shutil.rmtree(os.path.join(self.config.parser.root, "mods-enabled"))
+ self.assertRaises(
+ errors.NotSupportedError, self.config.enable_mod, "ssl")
+
+ @mock.patch("certbot.util.run_script")
+ @mock.patch("certbot.util.exe_exists")
+ @mock.patch("certbot_apache.parser.subprocess.Popen")
+ def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script):
+ mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "")
+ mock_popen().returncode = 0
+ mock_exe_exists.return_value = True
+
+ self.config.enable_mod("ssl")
+ self.assertTrue("ssl_module" in self.config.parser.modules)
+ self.assertTrue("mod_ssl.c" in self.config.parser.modules)
+
+ self.assertTrue(mock_run_script.called)
+
+ def test_deploy_cert_enable_new_vhost(self):
+ # Create
+ ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
+ self.config.parser.modules.add("ssl_module")
+ self.config.parser.modules.add("mod_ssl.c")
+ self.assertFalse(ssl_vhost.enabled)
+ self.config.deploy_cert(
+ "encryption-example.demo", "example/cert.pem", "example/key.pem",
+ "example/cert_chain.pem", "example/fullchain.pem")
+ self.assertTrue(ssl_vhost.enabled)
+ # Make sure that we don't error out if symlink already exists
+ ssl_vhost.enabled = False
+ self.assertFalse(ssl_vhost.enabled)
+ self.config.deploy_cert(
+ "encryption-example.demo", "example/cert.pem", "example/key.pem",
+ "example/cert_chain.pem", "example/fullchain.pem")
+ self.assertTrue(ssl_vhost.enabled)
+
+ def test_enable_site_failure(self):
+ self.config.parser.root = "/tmp/nonexistent"
+ with mock.patch("os.path.isdir") as mock_dir:
+ mock_dir.return_value = True
+ with mock.patch("os.path.islink") as mock_link:
+ mock_link.return_value = False
+ self.assertRaises(
+ errors.NotSupportedError,
+ self.config.enable_site,
+ obj.VirtualHost("asdf", "afsaf", set(), False, False))
+
+ def test_deploy_cert_newssl(self):
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir,
+ self.work_dir, version=(2, 4, 16))
+ self.config = self.mock_deploy_cert(self.config)
+ self.config.parser.modules.add("ssl_module")
+ self.config.parser.modules.add("mod_ssl.c")
+
+ # Get the default 443 vhost
+ self.config.assoc["random.demo"] = self.vh_truth[1]
+ self.config.deploy_cert(
+ "random.demo", "example/cert.pem", "example/key.pem",
+ "example/cert_chain.pem", "example/fullchain.pem")
+ self.config.save()
+
+ # Verify ssl_module was enabled.
+ self.assertTrue(self.vh_truth[1].enabled)
+ self.assertTrue("ssl_module" in self.config.parser.modules)
+
+ loc_cert = self.config.parser.find_dir(
+ "sslcertificatefile", "example/fullchain.pem",
+ self.vh_truth[1].path)
+ loc_key = self.config.parser.find_dir(
+ "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path)
+
+ # Verify one directive was found in the correct file
+ self.assertEqual(len(loc_cert), 1)
+ self.assertEqual(
+ apache_util.get_file_path(loc_cert[0]),
+ self.vh_truth[1].filep)
+
+ self.assertEqual(len(loc_key), 1)
+ self.assertEqual(
+ apache_util.get_file_path(loc_key[0]),
+ self.vh_truth[1].filep)
+
+ def test_deploy_cert_newssl_no_fullchain(self):
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir,
+ self.work_dir, version=(2, 4, 16))
+ self.config = self.mock_deploy_cert(self.config)
+ self.config.parser.modules.add("ssl_module")
+ self.config.parser.modules.add("mod_ssl.c")
+
+ # Get the default 443 vhost
+ self.config.assoc["random.demo"] = self.vh_truth[1]
+ self.assertRaises(errors.PluginError,
+ lambda: self.config.deploy_cert(
+ "random.demo", "example/cert.pem",
+ "example/key.pem"))
+
+ def test_deploy_cert_old_apache_no_chain(self):
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir,
+ self.work_dir, version=(2, 4, 7))
+ self.config = self.mock_deploy_cert(self.config)
+ self.config.parser.modules.add("ssl_module")
+ self.config.parser.modules.add("mod_ssl.c")
+
+ # Get the default 443 vhost
+ self.config.assoc["random.demo"] = self.vh_truth[1]
+ self.assertRaises(errors.PluginError,
+ lambda: self.config.deploy_cert(
+ "random.demo", "example/cert.pem",
+ "example/key.pem"))
+
+ @mock.patch("certbot.util.run_script")
+ @mock.patch("certbot.util.exe_exists")
+ def test_ocsp_stapling_enable_mod(self, mock_exe, _):
+ self.config.parser.update_runtime_variables = mock.Mock()
+ self.config.parser.modules.add("mod_ssl.c")
+ self.config.get_version = mock.Mock(return_value=(2, 4, 7))
+ mock_exe.return_value = True
+ self.config.enhance("certbot.demo", "staple-ocsp")
+ self.assertTrue("socache_shmcb_module" in self.config.parser.modules)
+
+ @mock.patch("certbot.util.run_script")
+ @mock.patch("certbot.util.exe_exists")
+ def test_ensure_http_header_enable_mod(self, mock_exe, _):
+ self.config.parser.update_runtime_variables = mock.Mock()
+ self.config.parser.modules.add("mod_ssl.c")
+ mock_exe.return_value = True
+
+ # This will create an ssl vhost for certbot.demo
+ self.config.enhance("certbot.demo", "ensure-http-header",
+ "Strict-Transport-Security")
+ self.assertTrue("headers_module" in self.config.parser.modules)
+
+ @mock.patch("certbot.util.run_script")
+ @mock.patch("certbot.util.exe_exists")
+ def test_redirect_enable_mod(self, mock_exe, _):
+ self.config.parser.update_runtime_variables = mock.Mock()
+ mock_exe.return_value = True
+ self.config.get_version = mock.Mock(return_value=(2, 2))
+ # This will create an ssl vhost for certbot.demo
+ self.config.enhance("certbot.demo", "redirect")
+ self.assertTrue("rewrite_module" in self.config.parser.modules)
+
+ def test_enable_site_already_enabled(self):
+ self.assertTrue(self.vh_truth[1].enabled)
+ self.config.enable_site(self.vh_truth[1])
+
+ def test_enable_site_call_parent(self):
+ with mock.patch(
+ "certbot_apache.configurator.ApacheConfigurator.enable_site") as e_s:
+ self.config.parser.root = "/tmp/nonexistent"
+ vh = self.vh_truth[0]
+ vh.enabled = False
+ self.config.enable_site(vh)
+ self.assertTrue(e_s.called)
+
+ @mock.patch("certbot.util.exe_exists")
+ def test_enable_mod_no_disable(self, mock_exe_exists):
+ mock_exe_exists.return_value = False
+ self.assertRaises(
+ errors.MisconfigurationError, self.config.enable_mod, "ssl")
+
+if __name__ == "__main__":
+ unittest.main() # pragma: no cover
diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/certbot_apache/tests/entrypoint_test.py
new file mode 100644
index 000000000..c04611465
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/entrypoint_test.py
@@ -0,0 +1,41 @@
+"""Test for certbot_apache.entrypoint for override class resolution"""
+import unittest
+
+import mock
+
+from certbot_apache import configurator
+from certbot_apache import entrypoint
+
+class EntryPointTest(unittest.TestCase):
+ """Entrypoint tests"""
+
+ _multiprocess_can_split_ = True
+
+ def test_get_configurator(self):
+
+ with mock.patch("certbot.util.get_os_info") as mock_info:
+ for distro in entrypoint.OVERRIDE_CLASSES.keys():
+ mock_info.return_value = (distro, "whatever")
+ self.assertEqual(entrypoint.get_configurator(),
+ entrypoint.OVERRIDE_CLASSES[distro])
+
+ def test_nonexistent_like(self):
+ with mock.patch("certbot.util.get_os_info") as mock_info:
+ mock_info.return_value = ("nonexistent", "irrelevant")
+ with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
+ for like in entrypoint.OVERRIDE_CLASSES.keys():
+ mock_like.return_value = [like]
+ self.assertEqual(entrypoint.get_configurator(),
+ entrypoint.OVERRIDE_CLASSES[like])
+
+ def test_nonexistent_generic(self):
+ with mock.patch("certbot.util.get_os_info") as mock_info:
+ mock_info.return_value = ("nonexistent", "irrelevant")
+ with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
+ mock_like.return_value = ["unknonwn"]
+ self.assertEqual(entrypoint.get_configurator(),
+ configurator.ApacheConfigurator)
+
+
+if __name__ == "__main__":
+ unittest.main() # pragma: no cover
diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py
new file mode 100644
index 000000000..0f2b96818
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/gentoo_test.py
@@ -0,0 +1,86 @@
+"""Test for certbot_apache.configurator for Gentoo overrides"""
+import os
+import unittest
+
+from certbot_apache import override_gentoo
+from certbot_apache import obj
+from certbot_apache.tests import util
+
+def get_vh_truth(temp_dir, config_name):
+ """Return the ground truth for the specified directory."""
+ prefix = os.path.join(
+ temp_dir, config_name, "apache2/vhosts.d")
+
+ aug_pre = "/files" + prefix
+ vh_truth = [
+ obj.VirtualHost(
+ os.path.join(prefix, "gentoo.example.com.conf"),
+ os.path.join(aug_pre, "gentoo.example.com.conf/VirtualHost"),
+ set([obj.Addr.fromstring("*:80")]),
+ False, True, "gentoo.example.com"),
+ obj.VirtualHost(
+ os.path.join(prefix, "00_default_vhost.conf"),
+ os.path.join(aug_pre, "00_default_vhost.conf/IfDefine/VirtualHost"),
+ set([obj.Addr.fromstring("*:80")]),
+ False, True, "localhost"),
+ obj.VirtualHost(
+ os.path.join(prefix, "00_default_ssl_vhost.conf"),
+ os.path.join(aug_pre,
+ "00_default_ssl_vhost.conf" +
+ "/IfDefine/IfDefine/IfModule/VirtualHost"),
+ set([obj.Addr.fromstring("_default_:443")]),
+ True, True, "localhost")
+ ]
+ return vh_truth
+
+class MultipleVhostsTestGentoo(util.ApacheTest):
+ """Multiple vhost tests for non-debian distro"""
+
+ _multiprocess_can_split_ = True
+
+ def setUp(self): # pylint: disable=arguments-differ
+ test_dir = "gentoo_apache/apache"
+ config_root = "gentoo_apache/apache/apache2"
+ vhost_root = "gentoo_apache/apache/apache2/vhosts.d"
+ super(MultipleVhostsTestGentoo, self).setUp(test_dir=test_dir,
+ config_root=config_root,
+ vhost_root=vhost_root)
+
+ self.config = util.get_apache_configurator(
+ self.config_path, self.vhost_path, self.config_dir, self.work_dir,
+ os_info="gentoo")
+ self.vh_truth = get_vh_truth(
+ self.temp_dir, "gentoo_apache/apache")
+
+ def test_get_parser(self):
+ self.assertTrue(isinstance(self.config.parser,
+ override_gentoo.GentooParser))
+
+ def test_get_virtual_hosts(self):
+ """Make sure all vhosts are being properly found."""
+ vhs = self.config.get_virtual_hosts()
+ self.assertEqual(len(vhs), 3)
+ found = 0
+
+ for vhost in vhs:
+ for gentoo_truth in self.vh_truth:
+ if vhost == gentoo_truth:
+ found += 1
+ break
+ else:
+ raise Exception("Missed: %s" % vhost) # pragma: no cover
+ self.assertEqual(found, 3)
+
+ def test_get_sysconfig_vars(self):
+ """Make sure we read the Gentoo APACHE2_OPTS variable correctly"""
+ defines = ['DEFAULT_VHOST', 'INFO',
+ 'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE']
+ self.config.parser.apacheconfig_filep = os.path.realpath(
+ os.path.join(self.config.parser.root, "../conf.d/apache2"))
+ self.config.parser.variables = {}
+ self.config.parser.update_runtime_variables()
+ for define in defines:
+ self.assertTrue(define in self.config.parser.variables.keys())
+
+if __name__ == "__main__":
+ unittest.main() # pragma: no cover
diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py
index 38c5b29c7..a9eb129c2 100644
--- a/certbot-apache/certbot_apache/tests/parser_test.py
+++ b/certbot-apache/certbot_apache/tests/parser_test.py
@@ -120,17 +120,18 @@ class BasicParserTest(util.ParserTest):
@mock.patch("certbot_apache.parser.ApacheParser.find_dir")
@mock.patch("certbot_apache.parser.ApacheParser.get_arg")
- def test_init_modules_bad_syntax(self, mock_arg, mock_find):
+ def test_parse_modules_bad_syntax(self, mock_arg, mock_find):
mock_find.return_value = ["1", "2", "3", "4", "5", "6", "7", "8"]
mock_arg.return_value = None
with mock.patch("certbot_apache.parser.logger") as mock_logger:
- self.parser.init_modules()
+ self.parser.parse_modules()
# Make sure that we got None return value and logged the file
self.assertTrue(mock_logger.debug.called)
+ @mock.patch("certbot_apache.parser.ApacheParser.find_dir")
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
- def test_update_runtime_variables(self, mock_cfg):
- mock_cfg.return_value = (
+ def test_update_runtime_variables(self, mock_cfg, _):
+ define_val = (
'ServerRoot: "/etc/apache2"\n'
'Main DocumentRoot: "/var/www"\n'
'Main ErrorLog: "/var/log/apache2/error.log"\n'
@@ -147,11 +148,113 @@ class BasicParserTest(util.ParserTest):
'User: name="www-data" id=33 not_used\n'
'Group: name="www-data" id=33 not_used\n'
)
+ inc_val = (
+ 'Included configuration files:\n'
+ ' (*) /etc/apache2/apache2.conf\n'
+ ' (146) /etc/apache2/mods-enabled/access_compat.load\n'
+ ' (146) /etc/apache2/mods-enabled/alias.load\n'
+ ' (146) /etc/apache2/mods-enabled/auth_basic.load\n'
+ ' (146) /etc/apache2/mods-enabled/authn_core.load\n'
+ ' (146) /etc/apache2/mods-enabled/authn_file.load\n'
+ ' (146) /etc/apache2/mods-enabled/authz_core.load\n'
+ ' (146) /etc/apache2/mods-enabled/authz_host.load\n'
+ ' (146) /etc/apache2/mods-enabled/authz_user.load\n'
+ ' (146) /etc/apache2/mods-enabled/autoindex.load\n'
+ ' (146) /etc/apache2/mods-enabled/deflate.load\n'
+ ' (146) /etc/apache2/mods-enabled/dir.load\n'
+ ' (146) /etc/apache2/mods-enabled/env.load\n'
+ ' (146) /etc/apache2/mods-enabled/filter.load\n'
+ ' (146) /etc/apache2/mods-enabled/mime.load\n'
+ ' (146) /etc/apache2/mods-enabled/mpm_event.load\n'
+ ' (146) /etc/apache2/mods-enabled/negotiation.load\n'
+ ' (146) /etc/apache2/mods-enabled/reqtimeout.load\n'
+ ' (146) /etc/apache2/mods-enabled/setenvif.load\n'
+ ' (146) /etc/apache2/mods-enabled/socache_shmcb.load\n'
+ ' (146) /etc/apache2/mods-enabled/ssl.load\n'
+ ' (146) /etc/apache2/mods-enabled/status.load\n'
+ ' (147) /etc/apache2/mods-enabled/alias.conf\n'
+ ' (147) /etc/apache2/mods-enabled/autoindex.conf\n'
+ ' (147) /etc/apache2/mods-enabled/deflate.conf\n'
+ )
+ mod_val = (
+ 'Loaded Modules:\n'
+ ' core_module (static)\n'
+ ' so_module (static)\n'
+ ' watchdog_module (static)\n'
+ ' http_module (static)\n'
+ ' log_config_module (static)\n'
+ ' logio_module (static)\n'
+ ' version_module (static)\n'
+ ' unixd_module (static)\n'
+ ' access_compat_module (shared)\n'
+ ' alias_module (shared)\n'
+ ' auth_basic_module (shared)\n'
+ ' authn_core_module (shared)\n'
+ ' authn_file_module (shared)\n'
+ ' authz_core_module (shared)\n'
+ ' authz_host_module (shared)\n'
+ ' authz_user_module (shared)\n'
+ ' autoindex_module (shared)\n'
+ ' deflate_module (shared)\n'
+ ' dir_module (shared)\n'
+ ' env_module (shared)\n'
+ ' filter_module (shared)\n'
+ ' mime_module (shared)\n'
+ ' mpm_event_module (shared)\n'
+ ' negotiation_module (shared)\n'
+ ' reqtimeout_module (shared)\n'
+ ' setenvif_module (shared)\n'
+ ' socache_shmcb_module (shared)\n'
+ ' ssl_module (shared)\n'
+ ' status_module (shared)\n'
+ )
+
+ def mock_get_vars(cmd):
+ """Mock command output"""
+ if cmd[-1] == "DUMP_RUN_CFG":
+ return define_val
+ elif cmd[-1] == "DUMP_INCLUDES":
+ return inc_val
+ elif cmd[-1] == "DUMP_MODULES":
+ return mod_val
+
+ mock_cfg.side_effect = mock_get_vars
+
expected_vars = {"TEST": "", "U_MICH": "", "TLS": "443",
"example_path": "Documents/path"}
- self.parser.update_runtime_variables()
- self.assertEqual(self.parser.variables, expected_vars)
+ self.parser.modules = set()
+ with mock.patch(
+ "certbot_apache.parser.ApacheParser.parse_file") as mock_parse:
+ self.parser.update_runtime_variables()
+ self.assertEqual(self.parser.variables, expected_vars)
+ self.assertEqual(len(self.parser.modules), 58)
+ # None of the includes in inc_val should be in parsed paths.
+ # Make sure we tried to include them all.
+ self.assertEqual(mock_parse.call_count, 25)
+
+ @mock.patch("certbot_apache.parser.ApacheParser.find_dir")
+ @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
+ def test_update_runtime_variables_alt_values(self, mock_cfg, _):
+ inc_val = (
+ 'Included configuration files:\n'
+ ' (*) {0}\n'
+ ' (146) /etc/apache2/mods-enabled/access_compat.load\n'
+ ' (146) {1}/mods-enabled/alias.load\n'
+ ).format(self.parser.loc["root"],
+ os.path.dirname(self.parser.loc["root"]))
+
+ mock_cfg.return_value = inc_val
+ self.parser.modules = set()
+
+ with mock.patch(
+ "certbot_apache.parser.ApacheParser.parse_file") as mock_parse:
+ self.parser.update_runtime_variables()
+ # No matching modules should have been found
+ self.assertEqual(len(self.parser.modules), 0)
+ # Only one of the three includes do not exist in already parsed
+ # path derived from root configuration Include statements
+ self.assertEqual(mock_parse.call_count, 1)
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
def test_update_runtime_vars_bad_output(self, mock_cfg):
@@ -162,7 +265,7 @@ class BasicParserTest(util.ParserTest):
self.assertRaises(
errors.PluginError, self.parser.update_runtime_variables)
- @mock.patch("certbot_apache.constants.os_constant")
+ @mock.patch("certbot_apache.configurator.ApacheConfigurator.constant")
@mock.patch("certbot_apache.parser.subprocess.Popen")
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_const):
mock_popen.side_effect = OSError
@@ -198,7 +301,7 @@ class ParserInitTest(util.ApacheTest):
self.assertRaises(
errors.PluginError,
ApacheParser, self.aug, os.path.relpath(self.config_path),
- "/dummy/vhostpath", version=(2, 2, 22))
+ "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config)
def test_root_normalized(self):
from certbot_apache.parser import ApacheParser
@@ -210,7 +313,7 @@ class ParserInitTest(util.ApacheTest):
"debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2")
parser = ApacheParser(self.aug, path,
- "/dummy/vhostpath")
+ "/dummy/vhostpath", configurator=self.config)
self.assertEqual(parser.root, self.config_path)
@@ -220,7 +323,7 @@ class ParserInitTest(util.ApacheTest):
"update_runtime_variables"):
parser = ApacheParser(
self.aug, os.path.relpath(self.config_path),
- "/dummy/vhostpath")
+ "/dummy/vhostpath", configurator=self.config)
self.assertEqual(parser.root, self.config_path)
@@ -230,7 +333,7 @@ class ParserInitTest(util.ApacheTest):
"update_runtime_variables"):
parser = ApacheParser(
self.aug, self.config_path + os.path.sep,
- "/dummy/vhostpath")
+ "/dummy/vhostpath", configurator=self.config)
self.assertEqual(parser.root, self.config_path)
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README
new file mode 100644
index 000000000..f5e96615a
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README
@@ -0,0 +1,9 @@
+
+This directory holds configuration files for the Apache HTTP Server;
+any files in this directory which have the ".conf" extension will be
+processed as httpd configuration files. The directory is used in
+addition to the directory /etc/httpd/conf.modules.d/, which contains
+configuration files necessary to load modules.
+
+Files are processed in alphabetical order.
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf
new file mode 100644
index 000000000..a85cf5dca
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf
@@ -0,0 +1,94 @@
+#
+# Directives controlling the display of server-generated directory listings.
+#
+# Required modules: mod_authz_core, mod_authz_host,
+# mod_autoindex, mod_alias
+#
+# To see the listing of a directory, the Options directive for the
+# directory must include "Indexes", and the directory must not contain
+# a file matching those listed in the DirectoryIndex directive.
+#
+
+#
+# IndexOptions: Controls the appearance of server-generated directory
+# listings.
+#
+IndexOptions FancyIndexing HTMLTable VersionSort
+
+# We include the /icons/ alias for FancyIndexed directory listings. If
+# you do not use FancyIndexing, you may comment this out.
+#
+Alias /icons/ "/usr/share/httpd/icons/"
+
+
+ Options Indexes MultiViews FollowSymlinks
+ AllowOverride None
+ Require all granted
+
+
+#
+# AddIcon* directives tell the server which icon to show for different
+# files or filename extensions. These are only displayed for
+# FancyIndexed directories.
+#
+AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
+
+AddIconByType (TXT,/icons/text.gif) text/*
+AddIconByType (IMG,/icons/image2.gif) image/*
+AddIconByType (SND,/icons/sound2.gif) audio/*
+AddIconByType (VID,/icons/movie.gif) video/*
+
+AddIcon /icons/binary.gif .bin .exe
+AddIcon /icons/binhex.gif .hqx
+AddIcon /icons/tar.gif .tar
+AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
+AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
+AddIcon /icons/a.gif .ps .ai .eps
+AddIcon /icons/layout.gif .html .shtml .htm .pdf
+AddIcon /icons/text.gif .txt
+AddIcon /icons/c.gif .c
+AddIcon /icons/p.gif .pl .py
+AddIcon /icons/f.gif .for
+AddIcon /icons/dvi.gif .dvi
+AddIcon /icons/uuencoded.gif .uu
+AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
+AddIcon /icons/tex.gif .tex
+AddIcon /icons/bomb.gif /core
+AddIcon /icons/bomb.gif */core.*
+
+AddIcon /icons/back.gif ..
+AddIcon /icons/hand.right.gif README
+AddIcon /icons/folder.gif ^^DIRECTORY^^
+AddIcon /icons/blank.gif ^^BLANKICON^^
+
+#
+# DefaultIcon is which icon to show for files which do not have an icon
+# explicitly set.
+#
+DefaultIcon /icons/unknown.gif
+
+#
+# AddDescription allows you to place a short description after a file in
+# server-generated indexes. These are only displayed for FancyIndexed
+# directories.
+# Format: AddDescription "description" filename
+#
+#AddDescription "GZIP compressed document" .gz
+#AddDescription "tar archive" .tar
+#AddDescription "GZIP compressed tar archive" .tgz
+
+#
+# ReadmeName is the name of the README file the server will look for by
+# default, and append to directory listings.
+#
+# HeaderName is the name of a file which should be prepended to
+# directory indexes.
+ReadmeName README.html
+HeaderName HEADER.html
+
+#
+# IndexIgnore is a set of filenames which directory indexing should ignore
+# and not include in the listing. Shell-style wildcarding is permitted.
+#
+IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf
new file mode 100644
index 000000000..de7ac2777
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf
@@ -0,0 +1,7 @@
+
+ ServerName centos.example.com
+ ServerAdmin webmaster@localhost
+ DocumentRoot /var/www/html
+ ErrorLog ${APACHE_LOG_DIR}/error.log
+ CustomLog ${APACHE_LOG_DIR}/access.log combined
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf
new file mode 100644
index 000000000..6e2502e9a
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf
@@ -0,0 +1,211 @@
+#
+# When we also provide SSL we have to listen to the
+# the HTTPS port in addition.
+#
+Listen 443 https
+
+##
+## SSL Global Context
+##
+## All SSL configuration in this context applies both to
+## the main server and all SSL-enabled virtual hosts.
+##
+
+# Pass Phrase Dialog:
+# Configure the pass phrase gathering process.
+# The filtering dialog program (`builtin' is a internal
+# terminal dialog) has to provide the pass phrase on stdout.
+SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog
+
+# Inter-Process Session Cache:
+# Configure the SSL Session Cache: First the mechanism
+# to use and second the expiring timeout (in seconds).
+SSLSessionCache shmcb:/run/httpd/sslcache(512000)
+SSLSessionCacheTimeout 300
+
+# Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the
+# SSL library. The seed data should be of good random quality.
+# WARNING! On some platforms /dev/random blocks if not enough entropy
+# is available. This means you then cannot use the /dev/random device
+# because it would lead to very long connection times (as long as
+# it requires to make more entropy available). But usually those
+# platforms additionally provide a /dev/urandom device which doesn't
+# block. So, if available, use this one instead. Read the mod_ssl User
+# Manual for more details.
+SSLRandomSeed startup file:/dev/urandom 256
+SSLRandomSeed connect builtin
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+
+#
+# Use "SSLCryptoDevice" to enable any supported hardware
+# accelerators. Use "openssl engine -v" to list supported
+# engine names. NOTE: If you enable an accelerator and the
+# server does not start, consult the error logs and ensure
+# your accelerator is functioning properly.
+#
+SSLCryptoDevice builtin
+#SSLCryptoDevice ubsec
+
+##
+## SSL Virtual Host Context
+##
+
+
+
+# General setup for the virtual host, inherited from global configuration
+#DocumentRoot "/var/www/html"
+#ServerName www.example.com:443
+
+# Use separate log files for the SSL virtual host; note that LogLevel
+# is not inherited from httpd.conf.
+ErrorLog logs/ssl_error_log
+TransferLog logs/ssl_access_log
+LogLevel warn
+
+# SSL Engine Switch:
+# Enable/Disable SSL for this virtual host.
+SSLEngine on
+
+# SSL Protocol support:
+# List the enable protocol levels with which clients will be able to
+# connect. Disable SSLv2 access by default:
+SSLProtocol all -SSLv2
+
+# SSL Cipher Suite:
+# List the ciphers that the client is permitted to negotiate.
+# See the mod_ssl documentation for a complete list.
+SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA
+
+# Speed-optimized SSL Cipher configuration:
+# If speed is your main concern (on busy HTTPS servers e.g.),
+# you might want to force clients to specific, performance
+# optimized ciphers. In this case, prepend those ciphers
+# to the SSLCipherSuite list, and enable SSLHonorCipherOrder.
+# Caveat: by giving precedence to RC4-SHA and AES128-SHA
+# (as in the example below), most connections will no longer
+# have perfect forward secrecy - if the server's key is
+# compromised, captures of past or future traffic must be
+# considered compromised, too.
+#SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5
+#SSLHonorCipherOrder on
+
+# Server Certificate:
+# Point SSLCertificateFile at a PEM encoded certificate. If
+# the certificate is encrypted, then you will be prompted for a
+# pass phrase. Note that a kill -HUP will prompt again. A new
+# certificate can be generated using the genkey(1) command.
+
+# Server Private Key:
+# If the key is not combined with the certificate, use this
+# directive to point at the key file. Keep in mind that if
+# you've both a RSA and a DSA private key you can configure
+# both in parallel (to also allow the use of DSA ciphers, etc.)
+
+# Server Certificate Chain:
+# Point SSLCertificateChainFile at a file containing the
+# concatenation of PEM encoded CA certificates which form the
+# certificate chain for the server certificate. Alternatively
+# the referenced file can be the same as SSLCertificateFile
+# when the CA certificates are directly appended to the server
+# certificate for convinience.
+#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt
+
+# Certificate Authority (CA):
+# Set the CA certificate verification path where to find CA
+# certificates for client authentication or alternatively one
+# huge file containing all of them (file must be PEM encoded)
+#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt
+
+# Client Authentication (Type):
+# Client certificate verification type and depth. Types are
+# none, optional, require and optional_no_ca. Depth is a
+# number which specifies how deeply to verify the certificate
+# issuer chain before deciding the certificate is not valid.
+#SSLVerifyClient require
+#SSLVerifyDepth 10
+
+# Access Control:
+# With SSLRequire you can do per-directory access control based
+# on arbitrary complex boolean expressions containing server
+# variable checks and other lookup directives. The syntax is a
+# mixture between C and Perl. See the mod_ssl documentation
+# for more details.
+#
+#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \
+# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \
+# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \
+# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
+# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \
+# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
+#
+
+# SSL Engine Options:
+# Set various options for the SSL engine.
+# o FakeBasicAuth:
+# Translate the client X.509 into a Basic Authorisation. This means that
+# the standard Auth/DBMAuth methods can be used for access control. The
+# user name is the `one line' version of the client's X.509 certificate.
+# Note that no password is obtained from the user. Every entry in the user
+# file needs this password: `xxj31ZMTZzkVA'.
+# o ExportCertData:
+# This exports two additional environment variables: SSL_CLIENT_CERT and
+# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the
+# server (always existing) and the client (only existing when client
+# authentication is used). This can be used to import the certificates
+# into CGI scripts.
+# o StdEnvVars:
+# This exports the standard SSL/TLS related `SSL_*' environment variables.
+# Per default this exportation is switched off for performance reasons,
+# because the extraction step is an expensive operation and is usually
+# useless for serving static content. So one usually enables the
+# exportation for CGI and SSI requests only.
+# o StrictRequire:
+# This denies access when "SSLRequireSSL" or "SSLRequire" applied even
+# under a "Satisfy any" situation, i.e. when it applies access is denied
+# and no other module can change it.
+# o OptRenegotiate:
+# This enables optimized SSL connection renegotiation handling when SSL
+# directives are used in per-directory context.
+#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
+
+ SSLOptions +StdEnvVars
+
+
+ SSLOptions +StdEnvVars
+
+
+# SSL Protocol Adjustments:
+# The safe and default but still SSL/TLS standard compliant shutdown
+# approach is that mod_ssl sends the close notify alert but doesn't wait for
+# the close notify alert from client. When you need a different shutdown
+# approach you can use one of the following variables:
+# o ssl-unclean-shutdown:
+# This forces an unclean shutdown when the connection is closed, i.e. no
+# SSL close notify alert is send or allowed to received. This violates
+# the SSL/TLS standard but is needed for some brain-dead browsers. Use
+# this when you receive I/O errors because of the standard approach where
+# mod_ssl sends the close notify alert.
+# o ssl-accurate-shutdown:
+# This forces an accurate shutdown when the connection is closed, i.e. a
+# SSL close notify alert is send and mod_ssl waits for the close notify
+# alert of the client. This is 100% SSL/TLS standard compliant, but in
+# practice often causes hanging connections with brain-dead browsers. Use
+# this only for browsers where you know that their SSL implementation
+# works correctly.
+# Notice: Most problems of broken clients are also related to the HTTP
+# keep-alive facility, so you usually additionally want to disable
+# keep-alive for those clients, too. Use variable "nokeepalive" for this.
+# Similarly, one has to force some clients to use HTTP/1.0 to workaround
+# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
+# "force-response-1.0" for this.
+BrowserMatch "MSIE [2-5]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
+
+# Per-Server Logging:
+# The home of a custom SSL log file. Use this when you want a
+# compact non-error SSL logfile on a virtual host basis.
+CustomLog logs/ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
+
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf
new file mode 100644
index 000000000..b5d7a49ef
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf
@@ -0,0 +1,36 @@
+#
+# UserDir: The name of the directory that is appended onto a user's home
+# directory if a ~user request is received.
+#
+# The path to the end user account 'public_html' directory must be
+# accessible to the webserver userid. This usually means that ~userid
+# must have permissions of 711, ~userid/public_html must have permissions
+# of 755, and documents contained therein must be world-readable.
+# Otherwise, the client will only receive a "403 Forbidden" message.
+#
+
+ #
+ # UserDir is disabled by default since it can confirm the presence
+ # of a username on the system (depending on home directory
+ # permissions).
+ #
+ UserDir disabled
+
+ #
+ # To enable requests to /~user/ to serve the user's public_html
+ # directory, remove the "UserDir disabled" line above, and uncomment
+ # the following line instead:
+ #
+ #UserDir public_html
+
+
+#
+# Control access to UserDir directories. The following is an example
+# for a site where these directories are restricted to read-only.
+#
+
+ AllowOverride FileInfo AuthConfig Limit Indexes
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+ Require method GET POST OPTIONS
+
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf
new file mode 100644
index 000000000..c1b6c11d9
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf
@@ -0,0 +1,22 @@
+#
+# This configuration file enables the default "Welcome" page if there
+# is no default index page present for the root URL. To disable the
+# Welcome page, comment out all the lines below.
+#
+# NOTE: if this file is removed, it will be restored on upgrades.
+#
+
+ Options -Indexes
+ ErrorDocument 403 /.noindex.html
+
+
+
+ AllowOverride None
+ Require all granted
+
+
+Alias /.noindex.html /usr/share/httpd/noindex/index.html
+Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css
+Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css
+Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif
+Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf
new file mode 100644
index 000000000..31d979f20
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf
@@ -0,0 +1,77 @@
+#
+# This file loads most of the modules included with the Apache HTTP
+# Server itself.
+#
+
+LoadModule access_compat_module modules/mod_access_compat.so
+LoadModule actions_module modules/mod_actions.so
+LoadModule alias_module modules/mod_alias.so
+LoadModule allowmethods_module modules/mod_allowmethods.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
+LoadModule auth_digest_module modules/mod_auth_digest.so
+LoadModule authn_anon_module modules/mod_authn_anon.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authn_dbd_module modules/mod_authn_dbd.so
+LoadModule authn_dbm_module modules/mod_authn_dbm.so
+LoadModule authn_file_module modules/mod_authn_file.so
+LoadModule authn_socache_module modules/mod_authn_socache.so
+LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule authz_dbd_module modules/mod_authz_dbd.so
+LoadModule authz_dbm_module modules/mod_authz_dbm.so
+LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
+LoadModule authz_host_module modules/mod_authz_host.so
+LoadModule authz_owner_module modules/mod_authz_owner.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule autoindex_module modules/mod_autoindex.so
+LoadModule cache_module modules/mod_cache.so
+LoadModule cache_disk_module modules/mod_cache_disk.so
+LoadModule data_module modules/mod_data.so
+LoadModule dbd_module modules/mod_dbd.so
+LoadModule deflate_module modules/mod_deflate.so
+LoadModule dir_module modules/mod_dir.so
+LoadModule dumpio_module modules/mod_dumpio.so
+LoadModule echo_module modules/mod_echo.so
+LoadModule env_module modules/mod_env.so
+LoadModule expires_module modules/mod_expires.so
+LoadModule ext_filter_module modules/mod_ext_filter.so
+LoadModule filter_module modules/mod_filter.so
+LoadModule headers_module modules/mod_headers.so
+LoadModule include_module modules/mod_include.so
+LoadModule info_module modules/mod_info.so
+LoadModule log_config_module modules/mod_log_config.so
+LoadModule logio_module modules/mod_logio.so
+LoadModule mime_magic_module modules/mod_mime_magic.so
+LoadModule mime_module modules/mod_mime.so
+LoadModule negotiation_module modules/mod_negotiation.so
+LoadModule remoteip_module modules/mod_remoteip.so
+LoadModule reqtimeout_module modules/mod_reqtimeout.so
+LoadModule rewrite_module modules/mod_rewrite.so
+LoadModule setenvif_module modules/mod_setenvif.so
+LoadModule slotmem_plain_module modules/mod_slotmem_plain.so
+LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
+LoadModule socache_dbm_module modules/mod_socache_dbm.so
+LoadModule socache_memcache_module modules/mod_socache_memcache.so
+LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
+LoadModule status_module modules/mod_status.so
+LoadModule substitute_module modules/mod_substitute.so
+LoadModule suexec_module modules/mod_suexec.so
+LoadModule unique_id_module modules/mod_unique_id.so
+LoadModule unixd_module modules/mod_unixd.so
+LoadModule userdir_module modules/mod_userdir.so
+LoadModule version_module modules/mod_version.so
+LoadModule vhost_alias_module modules/mod_vhost_alias.so
+
+#LoadModule buffer_module modules/mod_buffer.so
+#LoadModule watchdog_module modules/mod_watchdog.so
+#LoadModule heartbeat_module modules/mod_heartbeat.so
+#LoadModule heartmonitor_module modules/mod_heartmonitor.so
+#LoadModule usertrack_module modules/mod_usertrack.so
+#LoadModule dialup_module modules/mod_dialup.so
+#LoadModule charset_lite_module modules/mod_charset_lite.so
+#LoadModule log_debug_module modules/mod_log_debug.so
+#LoadModule ratelimit_module modules/mod_ratelimit.so
+#LoadModule reflector_module modules/mod_reflector.so
+#LoadModule request_module modules/mod_request.so
+#LoadModule sed_module modules/mod_sed.so
+#LoadModule speling_module modules/mod_speling.so
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf
new file mode 100644
index 000000000..e6af8decd
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf
@@ -0,0 +1,3 @@
+LoadModule dav_module modules/mod_dav.so
+LoadModule dav_fs_module modules/mod_dav_fs.so
+LoadModule dav_lock_module modules/mod_dav_lock.so
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf
new file mode 100644
index 000000000..9e0d0db6e
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf
@@ -0,0 +1 @@
+LoadModule lua_module modules/mod_lua.so
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf
new file mode 100644
index 000000000..7bfd1d413
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf
@@ -0,0 +1,19 @@
+# Select the MPM module which should be used by uncommenting exactly
+# one of the following LoadModule lines:
+
+# prefork MPM: Implements a non-threaded, pre-forking web server
+# See: http://httpd.apache.org/docs/2.4/mod/prefork.html
+LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+
+# worker MPM: Multi-Processing Module implementing a hybrid
+# multi-threaded multi-process web server
+# See: http://httpd.apache.org/docs/2.4/mod/worker.html
+#
+#LoadModule mpm_worker_module modules/mod_mpm_worker.so
+
+# event MPM: A variant of the worker MPM with the goal of consuming
+# threads only for connections with active processing
+# See: http://httpd.apache.org/docs/2.4/mod/event.html
+#
+#LoadModule mpm_event_module modules/mod_mpm_event.so
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf
new file mode 100644
index 000000000..cc0bca077
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf
@@ -0,0 +1,16 @@
+# This file configures all the proxy modules:
+LoadModule proxy_module modules/mod_proxy.so
+LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
+LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
+LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
+LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
+LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
+LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
+LoadModule proxy_connect_module modules/mod_proxy_connect.so
+LoadModule proxy_express_module modules/mod_proxy_express.so
+LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
+LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
+LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
+LoadModule proxy_http_module modules/mod_proxy_http.so
+LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
+LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf
new file mode 100644
index 000000000..53235cd76
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf
@@ -0,0 +1 @@
+LoadModule ssl_module modules/mod_ssl.so
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf
new file mode 100644
index 000000000..b208c972d
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf
@@ -0,0 +1,2 @@
+# This file configures systemd module:
+LoadModule systemd_module modules/mod_systemd.so
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf
new file mode 100644
index 000000000..5b8b9362e
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf
@@ -0,0 +1,14 @@
+# This configuration file loads a CGI module appropriate to the MPM
+# which has been configured in 00-mpm.conf. mod_cgid should be used
+# with a threaded MPM; mod_cgi with the prefork MPM.
+
+
+ LoadModule cgid_module modules/mod_cgid.so
+
+
+ LoadModule cgid_module modules/mod_cgid.so
+
+
+ LoadModule cgi_module modules/mod_cgi.so
+
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf
new file mode 100644
index 000000000..a7af0dc1e
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf
@@ -0,0 +1,353 @@
+#
+# This is the main Apache HTTP server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See for detailed information.
+# In particular, see
+#
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so 'log/access_log'
+# with ServerRoot set to '/www' will be interpreted by the
+# server as '/www/log/access_log', where as '/log/access_log' will be
+# interpreted as '/log/access_log'.
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path. If you point
+# ServerRoot at a non-local disk, be sure to specify a local disk on the
+# Mutex directive, if file-based mutexes are used. If you wish to share the
+# same ServerRoot for multiple httpd daemons, you will need to change at
+# least PidFile.
+#
+ServerRoot "/etc/httpd"
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+Include conf.modules.d/*.conf
+
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+#
+User apache
+Group apache
+
+# 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# definition. These values also provide defaults for
+# any containers you may define later in the file.
+#
+# All of these directives may appear inside containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+#
+ServerAdmin root@localhost
+
+#
+# ServerName gives the name and port that the server uses to identify itself.
+# This can often be determined automatically, but we recommend you specify
+# it explicitly to prevent problems during startup.
+#
+# If your host doesn't have a registered DNS name, enter its IP address here.
+#
+#ServerName www.example.com:80
+
+#
+# Deny access to the entirety of your server's filesystem. You must
+# explicitly permit access to web content directories in other
+# blocks below.
+#
+
+ AllowOverride none
+ Require all denied
+
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+DocumentRoot "/var/www/html"
+
+#
+# Relax access to content within /var/www.
+#
+
+ AllowOverride None
+ # Allow open access:
+ Require all granted
+
+
+# Further relax access to the default document root:
+
+ #
+ # Possible values for the Options directive are "None", "All",
+ # or any combination of:
+ # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+ #
+ # Note that "MultiViews" must be named *explicitly* --- "Options All"
+ # doesn't give it to you.
+ #
+ # The Options directive is both complicated and important. Please see
+ # http://httpd.apache.org/docs/2.4/mod/core.html#options
+ # for more information.
+ #
+ Options Indexes FollowSymLinks
+
+ #
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # Options FileInfo AuthConfig Limit
+ #
+ AllowOverride None
+
+ #
+ # Controls who can get stuff from this server.
+ #
+ Require all granted
+
+
+#
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+
+ DirectoryIndex index.html
+
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+
+ Require all denied
+
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog "logs/error_log"
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+
+ #
+ # The following directives define some format nicknames for use with
+ # a CustomLog directive (see below).
+ #
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+
+ # You need to enable mod_logio.c to use %I and %O
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+
+
+ #
+ # The location and format of the access logfile (Common Logfile Format).
+ # If you do not define any access logfiles within a
+ # container, they will be logged here. Contrariwise, if you *do*
+ # define per- access logfiles, transactions will be
+ # logged therein and *not* in this file.
+ #
+ #CustomLog "logs/access_log" common
+
+ #
+ # If you prefer a logfile with access, agent, and referer information
+ # (Combined Logfile Format) you can use the following directive.
+ #
+ CustomLog "logs/access_log" combined
+
+
+
+ #
+ # Redirect: Allows you to tell clients about documents that used to
+ # exist in your server's namespace, but do not anymore. The client
+ # will make a new request for the document at its new location.
+ # Example:
+ # Redirect permanent /foo http://www.example.com/bar
+
+ #
+ # Alias: Maps web paths into filesystem paths and is used to
+ # access content that does not live under the DocumentRoot.
+ # Example:
+ # Alias /webpath /full/filesystem/path
+ #
+ # If you include a trailing / on /webpath then the server will
+ # require it to be present in the URL. You will also likely
+ # need to provide a section to allow access to
+ # the filesystem path.
+
+ #
+ # ScriptAlias: This controls which directories contain server scripts.
+ # ScriptAliases are essentially the same as Aliases, except that
+ # documents in the target directory are treated as applications and
+ # run by the server when requested rather than as documents sent to the
+ # client. The same rules about trailing "/" apply to ScriptAlias
+ # directives as to Alias.
+ #
+ ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+
+
+#
+# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+#
+
+ AllowOverride None
+ Options None
+ Require all granted
+
+
+
+ #
+ # TypesConfig points to the file containing the list of mappings from
+ # filename extension to MIME-type.
+ #
+ TypesConfig /etc/mime.types
+
+ #
+ # AddType allows you to add to or override the MIME configuration
+ # file specified in TypesConfig for specific file types.
+ #
+ #AddType application/x-gzip .tgz
+ #
+ # AddEncoding allows you to have certain browsers uncompress
+ # information on the fly. Note: Not all browsers support this.
+ #
+ #AddEncoding x-compress .Z
+ #AddEncoding x-gzip .gz .tgz
+ #
+ # If the AddEncoding directives above are commented-out, then you
+ # probably should define those extensions to indicate media types:
+ #
+ AddType application/x-compress .Z
+ AddType application/x-gzip .gz .tgz
+
+ #
+ # AddHandler allows you to map certain file extensions to "handlers":
+ # actions unrelated to filetype. These can be either built into the server
+ # or added with the Action directive (see below)
+ #
+ # To use CGI scripts outside of ScriptAliased directories:
+ # (You will also need to add "ExecCGI" to the "Options" directive.)
+ #
+ #AddHandler cgi-script .cgi
+
+ # For type maps (negotiated resources):
+ #AddHandler type-map var
+
+ #
+ # Filters allow you to process content before it is sent to the client.
+ #
+ # To parse .shtml files for server-side includes (SSI):
+ # (You will also need to add "Includes" to the "Options" directive.)
+ #
+ AddType text/html .shtml
+ AddOutputFilter INCLUDES .shtml
+
+
+#
+# Specify a default charset for all content served; this enables
+# interpretation of all content as UTF-8 by default. To use the
+# default browser choice (ISO-8859-1), or to allow the META tags
+# in HTML content to override this choice, comment out this
+# directive:
+#
+AddDefaultCharset UTF-8
+
+
+ #
+ # The mod_mime_magic module allows the server to use various hints from the
+ # contents of the file itself to determine its type. The MIMEMagicFile
+ # directive tells the module where the hint definitions are located.
+ #
+ MIMEMagicFile conf/magic
+
+
+#
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+#
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+#
+
+#
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall may be used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+# Defaults if commented: EnableMMAP On, EnableSendfile Off
+#
+#EnableMMAP off
+EnableSendfile on
+
+# Supplemental configuration
+#
+# Load config files in the "/etc/httpd/conf.d" directory, if any.
+IncludeOptional conf.d/*.conf
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic
new file mode 100644
index 000000000..7c56119e9
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic
@@ -0,0 +1,385 @@
+# Magic data for mod_mime_magic Apache module (originally for file(1) command)
+# The module is described in /manual/mod/mod_mime_magic.html
+#
+# The format is 4-5 columns:
+# Column #1: byte number to begin checking from, ">" indicates continuation
+# Column #2: type of data to match
+# Column #3: contents of data to match
+# Column #4: MIME type of result
+# Column #5: MIME encoding of result (optional)
+
+#------------------------------------------------------------------------------
+# Localstuff: file(1) magic for locally observed files
+# Add any locally observed files here.
+
+#------------------------------------------------------------------------------
+# end local stuff
+#------------------------------------------------------------------------------
+
+#------------------------------------------------------------------------------
+# Java
+
+0 short 0xcafe
+>2 short 0xbabe application/java
+
+#------------------------------------------------------------------------------
+# audio: file(1) magic for sound formats
+#
+# from Jan Nicolai Langfeldt ,
+#
+
+# Sun/NeXT audio data
+0 string .snd
+>12 belong 1 audio/basic
+>12 belong 2 audio/basic
+>12 belong 3 audio/basic
+>12 belong 4 audio/basic
+>12 belong 5 audio/basic
+>12 belong 6 audio/basic
+>12 belong 7 audio/basic
+
+>12 belong 23 audio/x-adpcm
+
+# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format
+# that uses little-endian encoding and has a different magic number
+# (0x0064732E in little-endian encoding).
+0 lelong 0x0064732E
+>12 lelong 1 audio/x-dec-basic
+>12 lelong 2 audio/x-dec-basic
+>12 lelong 3 audio/x-dec-basic
+>12 lelong 4 audio/x-dec-basic
+>12 lelong 5 audio/x-dec-basic
+>12 lelong 6 audio/x-dec-basic
+>12 lelong 7 audio/x-dec-basic
+# compressed (G.721 ADPCM)
+>12 lelong 23 audio/x-dec-adpcm
+
+# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM"
+# AIFF audio data
+8 string AIFF audio/x-aiff
+# AIFF-C audio data
+8 string AIFC audio/x-aiff
+# IFF/8SVX audio data
+8 string 8SVX audio/x-aiff
+
+# Creative Labs AUDIO stuff
+# Standard MIDI data
+0 string MThd audio/unknown
+#>9 byte >0 (format %d)
+#>11 byte >1 using %d channels
+# Creative Music (CMF) data
+0 string CTMF audio/unknown
+# SoundBlaster instrument data
+0 string SBI audio/unknown
+# Creative Labs voice data
+0 string Creative\ Voice\ File audio/unknown
+## is this next line right? it came this way...
+#>19 byte 0x1A
+#>23 byte >0 - version %d
+#>22 byte >0 \b.%d
+
+# [GRR 950115: is this also Creative Labs? Guessing that first line
+# should be string instead of unknown-endian long...]
+#0 long 0x4e54524b MultiTrack sound data
+#0 string NTRK MultiTrack sound data
+#>4 long x - version %ld
+
+# Microsoft WAVE format (*.wav)
+# [GRR 950115: probably all of the shorts and longs should be leshort/lelong]
+# Microsoft RIFF
+0 string RIFF audio/unknown
+# - WAVE format
+>8 string WAVE audio/x-wav
+# MPEG audio.
+0 beshort&0xfff0 0xfff0 audio/mpeg
+# C64 SID Music files, from Linus Walleij
+0 string PSID audio/prs.sid
+
+#------------------------------------------------------------------------------
+# c-lang: file(1) magic for C programs or various scripts
+#
+
+# XPM icons (Greg Roelofs, newt@uchicago.edu)
+# ideally should go into "images", but entries below would tag XPM as C source
+0 string /*\ XPM image/x-xbm 7bit
+
+# this first will upset you if you're a PL/1 shop... (are there any left?)
+# in which case rm it; ascmagic will catch real C programs
+# C or REXX program text
+0 string /* text/plain
+# C++ program text
+0 string // text/plain
+
+#------------------------------------------------------------------------------
+# compress: file(1) magic for pure-compression formats (no archives)
+#
+# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc.
+#
+# Formats for various forms of compressed data
+# Formats for "compress" proper have been moved into "compress.c",
+# because it tries to uncompress it to figure out what's inside.
+
+# standard unix compress
+0 string \037\235 application/octet-stream x-compress
+
+# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver)
+0 string \037\213 application/octet-stream x-gzip
+
+# According to gzip.h, this is the correct byte order for packed data.
+0 string \037\036 application/octet-stream
+#
+# This magic number is byte-order-independent.
+#
+0 short 017437 application/octet-stream
+
+# XXX - why *two* entries for "compacted data", one of which is
+# byte-order independent, and one of which is byte-order dependent?
+#
+# compacted data
+0 short 0x1fff application/octet-stream
+0 string \377\037 application/octet-stream
+# huf output
+0 short 0145405 application/octet-stream
+
+# Squeeze and Crunch...
+# These numbers were gleaned from the Unix versions of the programs to
+# handle these formats. Note that I can only uncrunch, not crunch, and
+# I didn't have a crunched file handy, so the crunch number is untested.
+# Keith Waclena
+#0 leshort 0x76FF squeezed data (CP/M, DOS)
+#0 leshort 0x76FE crunched data (CP/M, DOS)
+
+# Freeze
+#0 string \037\237 Frozen file 2.1
+#0 string \037\236 Frozen file 1.0 (or gzip 0.5)
+
+# lzh?
+#0 string \037\240 LZH compressed data
+
+#------------------------------------------------------------------------------
+# frame: file(1) magic for FrameMaker files
+#
+# This stuff came on a FrameMaker demo tape, most of which is
+# copyright, but this file is "published" as witness the following:
+#
+0 string \
+# and Anna Shergold
+#
+0 string \
+0 string \14 byte 12 (OS/2 1.x format)
+#>14 byte 64 (OS/2 2.x format)
+#>14 byte 40 (Windows 3.x format)
+#0 string IC icon
+#0 string PI pointer
+#0 string CI color icon
+#0 string CP color pointer
+#0 string BA bitmap array
+
+0 string \x89PNG image/png
+0 string FWS application/x-shockwave-flash
+0 string CWS application/x-shockwave-flash
+
+#------------------------------------------------------------------------------
+# lisp: file(1) magic for lisp programs
+#
+# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com)
+0 string ;; text/plain 8bit
+# Emacs 18 - this is always correct, but not very magical.
+0 string \012( application/x-elc
+# Emacs 19
+0 string ;ELC\023\000\000\000 application/x-elc
+
+#------------------------------------------------------------------------------
+# mail.news: file(1) magic for mail and news
+#
+# There are tests to ascmagic.c to cope with mail and news.
+0 string Relay-Version: message/rfc822 7bit
+0 string #!\ rnews message/rfc822 7bit
+0 string N#!\ rnews message/rfc822 7bit
+0 string Forward\ to message/rfc822 7bit
+0 string Pipe\ to message/rfc822 7bit
+0 string Return-Path: message/rfc822 7bit
+0 string Path: message/news 8bit
+0 string Xref: message/news 8bit
+0 string From: message/rfc822 7bit
+0 string Article message/news 8bit
+#------------------------------------------------------------------------------
+# msword: file(1) magic for MS Word files
+#
+# Contributor claims:
+# Reversed-engineered MS Word magic numbers
+#
+
+0 string \376\067\0\043 application/msword
+0 string \333\245-\0\0\0 application/msword
+
+# disable this one because it applies also to other
+# Office/OLE documents for which msword is not correct. See PR#2608.
+#0 string \320\317\021\340\241\261 application/msword
+
+
+
+#------------------------------------------------------------------------------
+# printer: file(1) magic for printer-formatted files
+#
+
+# PostScript
+0 string %! application/postscript
+0 string \004%! application/postscript
+
+# Acrobat
+# (due to clamen@cs.cmu.edu)
+0 string %PDF- application/pdf
+
+#------------------------------------------------------------------------------
+# sc: file(1) magic for "sc" spreadsheet
+#
+38 string Spreadsheet application/x-sc
+
+#------------------------------------------------------------------------------
+# tex: file(1) magic for TeX files
+#
+# XXX - needs byte-endian stuff (big-endian and little-endian DVI?)
+#
+# From
+
+# Although we may know the offset of certain text fields in TeX DVI
+# and font files, we can't use them reliably because they are not
+# zero terminated. [but we do anyway, christos]
+0 string \367\002 application/x-dvi
+#0 string \367\203 TeX generic font data
+#0 string \367\131 TeX packed font data
+#0 string \367\312 TeX virtual font data
+#0 string This\ is\ TeX, TeX transcript text
+#0 string This\ is\ METAFONT, METAFONT transcript text
+
+# There is no way to detect TeX Font Metric (*.tfm) files without
+# breaking them apart and reading the data. The following patterns
+# match most *.tfm files generated by METAFONT or afm2tfm.
+#2 string \000\021 TeX font metric data
+#2 string \000\022 TeX font metric data
+#>34 string >\0 (%s)
+
+# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com)
+#0 string \\input\ texinfo Texinfo source text
+#0 string This\ is\ Info\ file GNU Info text
+
+# correct TeX magic for Linux (and maybe more)
+# from Peter Tobias (tobias@server.et-inf.fho-emden.de)
+#
+0 leshort 0x02f7 application/x-dvi
+
+# RTF - Rich Text Format
+0 string {\\rtf application/rtf
+
+#------------------------------------------------------------------------------
+# animation: file(1) magic for animation/movie formats
+#
+# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8)
+# MPEG file
+0 string \000\000\001\263 video/mpeg
+#
+# The contributor claims:
+# I couldn't find a real magic number for these, however, this
+# -appears- to work. Note that it might catch other files, too,
+# so BE CAREFUL!
+#
+# Note that title and author appear in the two 20-byte chunks
+# at decimal offsets 2 and 22, respectively, but they are XOR'ed with
+# 255 (hex FF)! DL format SUCKS BIG ROCKS.
+#
+# DL file version 1 , medium format (160x100, 4 images/screen)
+0 byte 1 video/unknown
+0 byte 2 video/unknown
+# Quicktime video, from Linus Walleij
+# from Apple quicktime file format documentation.
+4 string moov video/quicktime
+4 string mdat video/quicktime
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites
new file mode 100644
index 000000000..6af1f63fa
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites
@@ -0,0 +1 @@
+conf.d/centos.example.com.conf, centos.example.com
diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd
new file mode 100644
index 000000000..0bf6b176c
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd
@@ -0,0 +1,25 @@
+#
+# This file can be used to set additional environment variables for
+# the httpd process, or pass additional options to the httpd
+# executable.
+#
+# Note: With previous versions of httpd, the MPM could be changed by
+# editing an "HTTPD" variable here. With the current version, that
+# variable is now ignored. The MPM is a loadable module, and the
+# choice of MPM can be changed by editing the configuration file
+# /etc/httpd/conf.modules.d/00-mpm.conf.
+#
+
+#
+# To pass additional options (for instance, -D definitions) to the
+# httpd binary at startup, set OPTIONS here.
+#
+OPTIONS="-D mock_define -D mock_define_too -D mock_value=TRUE"
+
+#
+# This setting ensures the httpd process is started in the "C" locale
+# by default. (Some modules will not behave correctly if
+# case-sensitive string comparisons are performed in a different
+# locale.)
+#
+LANG=C
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf
new file mode 100644
index 000000000..e5693ffff
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf
@@ -0,0 +1,157 @@
+# This is a modification of the default Apache 2.4 configuration file
+# for Gentoo Linux.
+#
+# Support:
+# http://www.gentoo.org/main/en/lists.xml [mailing lists]
+# http://forums.gentoo.org/ [web forums]
+# irc://irc.freenode.net#gentoo-apache [irc chat]
+#
+# Bug Reports:
+# http://bugs.gentoo.org [gentoo related bugs]
+# http://httpd.apache.org/bug_report.html [apache httpd related bugs]
+#
+#
+# This is the main Apache HTTP server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See for detailed information.
+# In particular, see
+#
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "var/log/apache2/foo_log"
+# with ServerRoot set to "/usr" will be interpreted by the
+# server as "/usr/var/log/apache2/foo.log".
+
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path. If you point
+# ServerRoot at a non-local disk, be sure to point the LockFile directive
+# at a local disk. If you wish to share the same ServerRoot for multiple
+# httpd daemons, you will need to change at least LockFile and PidFile.
+# Comment: The LockFile directive has been replaced by the Mutex directive
+ServerRoot "/usr/lib64/apache2"
+
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+# GENTOO: Automatically defined based on APACHE2_MODULES USE_EXPAND variable.
+# Do not change manually, it will be overwritten on upgrade.
+#
+# The following modules are considered as the default configuration.
+# If you wish to disable one of them, you may have to alter other
+# configuration directives.
+#
+# Change these at your own risk!
+
+LoadModule actions_module modules/mod_actions.so
+LoadModule alias_module modules/mod_alias.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
+LoadModule authn_anon_module modules/mod_authn_anon.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authn_dbm_module modules/mod_authn_dbm.so
+LoadModule authn_file_module modules/mod_authn_file.so
+LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule authz_dbm_module modules/mod_authz_dbm.so
+LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
+LoadModule authz_host_module modules/mod_authz_host.so
+LoadModule authz_owner_module modules/mod_authz_owner.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule autoindex_module modules/mod_autoindex.so
+
+LoadModule cache_module modules/mod_cache.so
+
+LoadModule cgi_module modules/mod_cgi.so
+LoadModule cgid_module modules/mod_cgid.so
+
+LoadModule dav_module modules/mod_dav.so
+
+
+LoadModule dav_fs_module modules/mod_dav_fs.so
+
+
+LoadModule dav_lock_module modules/mod_dav_lock.so
+
+LoadModule deflate_module modules/mod_deflate.so
+LoadModule dir_module modules/mod_dir.so
+LoadModule env_module modules/mod_env.so
+LoadModule expires_module modules/mod_expires.so
+LoadModule ext_filter_module modules/mod_ext_filter.so
+
+LoadModule file_cache_module modules/mod_file_cache.so
+
+LoadModule filter_module modules/mod_filter.so
+LoadModule headers_module modules/mod_headers.so
+LoadModule include_module modules/mod_include.so
+
+LoadModule info_module modules/mod_info.so
+
+LoadModule log_config_module modules/mod_log_config.so
+LoadModule logio_module modules/mod_logio.so
+LoadModule mime_module modules/mod_mime.so
+LoadModule mime_magic_module modules/mod_mime_magic.so
+LoadModule negotiation_module modules/mod_negotiation.so
+LoadModule rewrite_module modules/mod_rewrite.so
+LoadModule setenvif_module modules/mod_setenvif.so
+
+LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
+
+LoadModule speling_module modules/mod_speling.so
+
+LoadModule ssl_module modules/mod_ssl.so
+
+
+LoadModule status_module modules/mod_status.so
+
+LoadModule unique_id_module modules/mod_unique_id.so
+LoadModule unixd_module modules/mod_unixd.so
+
+LoadModule userdir_module modules/mod_userdir.so
+
+LoadModule usertrack_module modules/mod_usertrack.so
+LoadModule vhost_alias_module modules/mod_vhost_alias.so
+
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+User apache
+Group apache
+
+# Supplemental configuration
+#
+# Most of the configuration files in the /etc/apache2/modules.d/ directory can
+# be turned on using APACHE2_OPTS in /etc/conf.d/apache2 to add extra features
+# or to modify the default configuration of the server.
+#
+# To know which flag to add to APACHE2_OPTS, look at the first line of the
+# the file, which will usually be an where OPTION is the
+# flag to use.
+
+Include modules.d/*.conf
+
+# Virtual-host support
+#
+# Gentoo has made using virtual-hosts easy. In /etc/apache2/vhosts.d/ we
+# include a default vhost (enabled by adding -D DEFAULT_VHOST to
+# APACHE2_OPTS in /etc/conf.d/apache2).
+Include vhosts.d/*.conf
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic
new file mode 100644
index 000000000..7c56119e9
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic
@@ -0,0 +1,385 @@
+# Magic data for mod_mime_magic Apache module (originally for file(1) command)
+# The module is described in /manual/mod/mod_mime_magic.html
+#
+# The format is 4-5 columns:
+# Column #1: byte number to begin checking from, ">" indicates continuation
+# Column #2: type of data to match
+# Column #3: contents of data to match
+# Column #4: MIME type of result
+# Column #5: MIME encoding of result (optional)
+
+#------------------------------------------------------------------------------
+# Localstuff: file(1) magic for locally observed files
+# Add any locally observed files here.
+
+#------------------------------------------------------------------------------
+# end local stuff
+#------------------------------------------------------------------------------
+
+#------------------------------------------------------------------------------
+# Java
+
+0 short 0xcafe
+>2 short 0xbabe application/java
+
+#------------------------------------------------------------------------------
+# audio: file(1) magic for sound formats
+#
+# from Jan Nicolai Langfeldt ,
+#
+
+# Sun/NeXT audio data
+0 string .snd
+>12 belong 1 audio/basic
+>12 belong 2 audio/basic
+>12 belong 3 audio/basic
+>12 belong 4 audio/basic
+>12 belong 5 audio/basic
+>12 belong 6 audio/basic
+>12 belong 7 audio/basic
+
+>12 belong 23 audio/x-adpcm
+
+# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format
+# that uses little-endian encoding and has a different magic number
+# (0x0064732E in little-endian encoding).
+0 lelong 0x0064732E
+>12 lelong 1 audio/x-dec-basic
+>12 lelong 2 audio/x-dec-basic
+>12 lelong 3 audio/x-dec-basic
+>12 lelong 4 audio/x-dec-basic
+>12 lelong 5 audio/x-dec-basic
+>12 lelong 6 audio/x-dec-basic
+>12 lelong 7 audio/x-dec-basic
+# compressed (G.721 ADPCM)
+>12 lelong 23 audio/x-dec-adpcm
+
+# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM"
+# AIFF audio data
+8 string AIFF audio/x-aiff
+# AIFF-C audio data
+8 string AIFC audio/x-aiff
+# IFF/8SVX audio data
+8 string 8SVX audio/x-aiff
+
+# Creative Labs AUDIO stuff
+# Standard MIDI data
+0 string MThd audio/unknown
+#>9 byte >0 (format %d)
+#>11 byte >1 using %d channels
+# Creative Music (CMF) data
+0 string CTMF audio/unknown
+# SoundBlaster instrument data
+0 string SBI audio/unknown
+# Creative Labs voice data
+0 string Creative\ Voice\ File audio/unknown
+## is this next line right? it came this way...
+#>19 byte 0x1A
+#>23 byte >0 - version %d
+#>22 byte >0 \b.%d
+
+# [GRR 950115: is this also Creative Labs? Guessing that first line
+# should be string instead of unknown-endian long...]
+#0 long 0x4e54524b MultiTrack sound data
+#0 string NTRK MultiTrack sound data
+#>4 long x - version %ld
+
+# Microsoft WAVE format (*.wav)
+# [GRR 950115: probably all of the shorts and longs should be leshort/lelong]
+# Microsoft RIFF
+0 string RIFF audio/unknown
+# - WAVE format
+>8 string WAVE audio/x-wav
+# MPEG audio.
+0 beshort&0xfff0 0xfff0 audio/mpeg
+# C64 SID Music files, from Linus Walleij
+0 string PSID audio/prs.sid
+
+#------------------------------------------------------------------------------
+# c-lang: file(1) magic for C programs or various scripts
+#
+
+# XPM icons (Greg Roelofs, newt@uchicago.edu)
+# ideally should go into "images", but entries below would tag XPM as C source
+0 string /*\ XPM image/x-xbm 7bit
+
+# this first will upset you if you're a PL/1 shop... (are there any left?)
+# in which case rm it; ascmagic will catch real C programs
+# C or REXX program text
+0 string /* text/plain
+# C++ program text
+0 string // text/plain
+
+#------------------------------------------------------------------------------
+# compress: file(1) magic for pure-compression formats (no archives)
+#
+# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc.
+#
+# Formats for various forms of compressed data
+# Formats for "compress" proper have been moved into "compress.c",
+# because it tries to uncompress it to figure out what's inside.
+
+# standard unix compress
+0 string \037\235 application/octet-stream x-compress
+
+# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver)
+0 string \037\213 application/octet-stream x-gzip
+
+# According to gzip.h, this is the correct byte order for packed data.
+0 string \037\036 application/octet-stream
+#
+# This magic number is byte-order-independent.
+#
+0 short 017437 application/octet-stream
+
+# XXX - why *two* entries for "compacted data", one of which is
+# byte-order independent, and one of which is byte-order dependent?
+#
+# compacted data
+0 short 0x1fff application/octet-stream
+0 string \377\037 application/octet-stream
+# huf output
+0 short 0145405 application/octet-stream
+
+# Squeeze and Crunch...
+# These numbers were gleaned from the Unix versions of the programs to
+# handle these formats. Note that I can only uncrunch, not crunch, and
+# I didn't have a crunched file handy, so the crunch number is untested.
+# Keith Waclena
+#0 leshort 0x76FF squeezed data (CP/M, DOS)
+#0 leshort 0x76FE crunched data (CP/M, DOS)
+
+# Freeze
+#0 string \037\237 Frozen file 2.1
+#0 string \037\236 Frozen file 1.0 (or gzip 0.5)
+
+# lzh?
+#0 string \037\240 LZH compressed data
+
+#------------------------------------------------------------------------------
+# frame: file(1) magic for FrameMaker files
+#
+# This stuff came on a FrameMaker demo tape, most of which is
+# copyright, but this file is "published" as witness the following:
+#
+0 string \
+# and Anna Shergold
+#
+0 string \
+0 string \14 byte 12 (OS/2 1.x format)
+#>14 byte 64 (OS/2 2.x format)
+#>14 byte 40 (Windows 3.x format)
+#0 string IC icon
+#0 string PI pointer
+#0 string CI color icon
+#0 string CP color pointer
+#0 string BA bitmap array
+
+0 string \x89PNG image/png
+0 string FWS application/x-shockwave-flash
+0 string CWS application/x-shockwave-flash
+
+#------------------------------------------------------------------------------
+# lisp: file(1) magic for lisp programs
+#
+# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com)
+0 string ;; text/plain 8bit
+# Emacs 18 - this is always correct, but not very magical.
+0 string \012( application/x-elc
+# Emacs 19
+0 string ;ELC\023\000\000\000 application/x-elc
+
+#------------------------------------------------------------------------------
+# mail.news: file(1) magic for mail and news
+#
+# There are tests to ascmagic.c to cope with mail and news.
+0 string Relay-Version: message/rfc822 7bit
+0 string #!\ rnews message/rfc822 7bit
+0 string N#!\ rnews message/rfc822 7bit
+0 string Forward\ to message/rfc822 7bit
+0 string Pipe\ to message/rfc822 7bit
+0 string Return-Path: message/rfc822 7bit
+0 string Path: message/news 8bit
+0 string Xref: message/news 8bit
+0 string From: message/rfc822 7bit
+0 string Article message/news 8bit
+#------------------------------------------------------------------------------
+# msword: file(1) magic for MS Word files
+#
+# Contributor claims:
+# Reversed-engineered MS Word magic numbers
+#
+
+0 string \376\067\0\043 application/msword
+0 string \333\245-\0\0\0 application/msword
+
+# disable this one because it applies also to other
+# Office/OLE documents for which msword is not correct. See PR#2608.
+#0 string \320\317\021\340\241\261 application/msword
+
+
+
+#------------------------------------------------------------------------------
+# printer: file(1) magic for printer-formatted files
+#
+
+# PostScript
+0 string %! application/postscript
+0 string \004%! application/postscript
+
+# Acrobat
+# (due to clamen@cs.cmu.edu)
+0 string %PDF- application/pdf
+
+#------------------------------------------------------------------------------
+# sc: file(1) magic for "sc" spreadsheet
+#
+38 string Spreadsheet application/x-sc
+
+#------------------------------------------------------------------------------
+# tex: file(1) magic for TeX files
+#
+# XXX - needs byte-endian stuff (big-endian and little-endian DVI?)
+#
+# From
+
+# Although we may know the offset of certain text fields in TeX DVI
+# and font files, we can't use them reliably because they are not
+# zero terminated. [but we do anyway, christos]
+0 string \367\002 application/x-dvi
+#0 string \367\203 TeX generic font data
+#0 string \367\131 TeX packed font data
+#0 string \367\312 TeX virtual font data
+#0 string This\ is\ TeX, TeX transcript text
+#0 string This\ is\ METAFONT, METAFONT transcript text
+
+# There is no way to detect TeX Font Metric (*.tfm) files without
+# breaking them apart and reading the data. The following patterns
+# match most *.tfm files generated by METAFONT or afm2tfm.
+#2 string \000\021 TeX font metric data
+#2 string \000\022 TeX font metric data
+#>34 string >\0 (%s)
+
+# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com)
+#0 string \\input\ texinfo Texinfo source text
+#0 string This\ is\ Info\ file GNU Info text
+
+# correct TeX magic for Linux (and maybe more)
+# from Peter Tobias (tobias@server.et-inf.fho-emden.de)
+#
+0 leshort 0x02f7 application/x-dvi
+
+# RTF - Rich Text Format
+0 string {\\rtf application/rtf
+
+#------------------------------------------------------------------------------
+# animation: file(1) magic for animation/movie formats
+#
+# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8)
+# MPEG file
+0 string \000\000\001\263 video/mpeg
+#
+# The contributor claims:
+# I couldn't find a real magic number for these, however, this
+# -appears- to work. Note that it might catch other files, too,
+# so BE CAREFUL!
+#
+# Note that title and author appear in the two 20-byte chunks
+# at decimal offsets 2 and 22, respectively, but they are XOR'ed with
+# 255 (hex FF)! DL format SUCKS BIG ROCKS.
+#
+# DL file version 1 , medium format (160x100, 4 images/screen)
+0 byte 1 video/unknown
+0 byte 2 video/unknown
+# Quicktime video, from Linus Walleij
+# from Apple quicktime file format documentation.
+4 string moov video/quicktime
+4 string mdat video/quicktime
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/.keep_www-servers_apache-2 b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/.keep_www-servers_apache-2
new file mode 100644
index 000000000..e69de29bb
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf
new file mode 100644
index 000000000..38635aa9d
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf
@@ -0,0 +1,131 @@
+# This configuration file reflects default settings for Apache HTTP Server.
+# You may change these, but chances are that you may not need to.
+
+# Timeout: The number of seconds before receives and sends time out.
+Timeout 300
+
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+KeepAlive On
+
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+MaxKeepAliveRequests 100
+
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+KeepAliveTimeout 15
+
+# UseCanonicalName: Determines how Apache constructs self-referencing
+# URLs and the SERVER_NAME and SERVER_PORT variables.
+# When set "Off", Apache will use the Hostname and Port supplied
+# by the client. When set "On", Apache will use the value of the
+# ServerName directive.
+UseCanonicalName Off
+
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+AccessFileName .htaccess
+
+# ServerTokens
+# This directive configures what you return as the Server HTTP response
+# Header. The default is 'Full' which sends information about the OS-Type
+# and compiled in modules.
+# Set to one of: Full | OS | Minor | Minimal | Major | Prod
+# where Full conveys the most information, and Prod the least.
+ServerTokens Prod
+
+# TraceEnable
+# This directive overrides the behavior of TRACE for both the core server and
+# mod_proxy. The default TraceEnable on permits TRACE requests per RFC 2616,
+# which disallows any request body to accompany the request. TraceEnable off
+# causes the core server and mod_proxy to return a 405 (Method not allowed)
+# error to the client.
+# For security reasons this is turned off by default. (bug #240680)
+TraceEnable off
+
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (internal error documents, FTP directory
+# listings, mod_status and mod_info output etc., but not CGI generated
+# documents or custom error documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+ServerSignature On
+
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+HostnameLookups Off
+
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall is used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+EnableMMAP On
+EnableSendfile Off
+
+# FileETag: Configures the file attributes that are used to create
+# the ETag (entity tag) response header field when the document is
+# based on a static file. (The ETag value is used in cache management
+# to save network bandwidth.)
+FileETag MTime Size
+
+# ContentDigest: This directive enables the generation of Content-MD5
+# headers as defined in RFC1864 respectively RFC2616.
+# The Content-MD5 header provides an end-to-end message integrity
+# check (MIC) of the entity-body. A proxy or client may check this
+# header for detecting accidental modification of the entity-body
+# in transit.
+# Note that this can cause performance problems on your server since
+# the message digest is computed on every request (the values are
+# not cached).
+# Content-MD5 is only sent for documents served by the core, and not
+# by any module. For example, SSI documents, output from CGI scripts,
+# and byte range responses do not have this header.
+ContentDigest Off
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+ErrorLog /var/log/apache2/error_log
+
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+LogLevel warn
+
+# We configure the "default" to be a very restrictive set of features.
+
+ Options FollowSymLinks
+ AllowOverride None
+ Require all denied
+
+
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+# The index.html.var file (a type-map) is used to deliver content-
+# negotiated documents. The MultiViews Options can be used for the
+# same purpose, but it is much slower.
+#
+# Do not change this entry unless you know what you are doing.
+
+ DirectoryIndex index.html index.html.var
+
+
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+
+ Require all denied
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf
new file mode 100644
index 000000000..61479fa53
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf
@@ -0,0 +1,57 @@
+# The configuration below implements multi-language error documents through
+# content-negotiation.
+
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+
+# Required modules: mod_alias, mod_include, mod_negotiation
+# We use Alias to redirect any /error/HTTP_.html.var response to
+# our collection of by-error message multi-language collections. We use
+# includes to substitute the appropriate text.
+# You can modify the messages' appearance without changing any of the
+# default HTTP_.html.var files by adding the line:
+# Alias /error/include/ "/your/include/path/"
+# which allows you to create your own set of files by starting with the
+# /var/www/localhost/error/include/ files and copying them to /your/include/path/,
+# even on a per-VirtualHost basis. The default include files will display
+# your Apache version number and your ServerAdmin email address regardless
+# of the setting of ServerSignature.
+
+
+Alias /error/ "/usr/share/apache2/error/"
+
+
+ AllowOverride None
+ Options IncludesNoExec
+ AddOutputFilter Includes html
+ AddHandler type-map var
+ Require all granted
+ LanguagePriority en cs de es fr it ja ko nl pl pt-br ro sv tr
+ ForceLanguagePriority Prefer Fallback
+
+
+ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
+ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
+ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var
+ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var
+ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var
+ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var
+ErrorDocument 410 /error/HTTP_GONE.html.var
+ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var
+ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var
+ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var
+ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var
+ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var
+ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var
+ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var
+ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var
+ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var
+ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf
new file mode 100644
index 000000000..c429bf94c
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf
@@ -0,0 +1,133 @@
+# Settings for hosting different languages.
+
+# DefaultLanguage and AddLanguage allows you to specify the language of
+# a document. You can then use content negotiation to give a browser a
+# file in a language the user can understand.
+#
+# Specify a default language. This means that all data
+# going out without a specific language tag (see below) will
+# be marked with this one. You probably do NOT want to set
+# this unless you are sure it is correct for all cases.
+#
+# It is generally better to not mark a page as
+# being a certain language than marking it with the wrong
+# language!
+#
+# DefaultLanguage nl
+#
+# Note 1: The suffix does not have to be the same as the language
+# keyword --- those with documents in Polish (whose net-standard
+# language code is pl) may wish to use "AddLanguage pl .po" to
+# avoid the ambiguity with the common suffix for perl scripts.
+#
+# Note 2: The example entries below illustrate that in some cases
+# the two character 'Language' abbreviation is not identical to
+# the two character 'Country' code for its country,
+# E.g. 'Danmark/dk' versus 'Danish/da'.
+#
+# Note 3: In the case of 'ltz' we violate the RFC by using a three char
+# specifier. There is 'work in progress' to fix this and get
+# the reference data for rfc1766 cleaned up.
+#
+# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl)
+# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de)
+# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja)
+# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn)
+# Norwegian (no) - Polish (pl) - Portugese (pt)
+# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv)
+# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW)
+AddLanguage ca .ca
+AddLanguage cs .cz .cs
+AddLanguage da .dk
+AddLanguage de .de
+AddLanguage el .el
+AddLanguage en .en
+AddLanguage eo .eo
+AddLanguage es .es
+AddLanguage et .et
+AddLanguage fr .fr
+AddLanguage he .he
+AddLanguage hr .hr
+AddLanguage it .it
+AddLanguage ja .ja
+AddLanguage ko .ko
+AddLanguage ltz .ltz
+AddLanguage nl .nl
+AddLanguage nn .nn
+AddLanguage no .no
+AddLanguage pl .po
+AddLanguage pt .pt
+AddLanguage pt-BR .pt-br
+AddLanguage ru .ru
+AddLanguage sv .sv
+AddLanguage zh-CN .zh-cn
+AddLanguage zh-TW .zh-tw
+
+# LanguagePriority allows you to give precedence to some languages
+# in case of a tie during content negotiation.
+#
+# Just list the languages in decreasing order of preference. We have
+# more or less alphabetized them here. You probably want to change this.
+LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW
+
+# ForceLanguagePriority allows you to serve a result page rather than
+# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback)
+# [in case no accepted languages matched the available variants]
+ForceLanguagePriority Prefer Fallback
+
+# Commonly used filename extensions to character sets. You probably
+# want to avoid clashes with the language extensions, unless you
+# are good at carefully testing your setup after each change.
+# See http://www.iana.org/assignments/character-sets for the
+# official list of charset names and their respective RFCs.
+AddCharset us-ascii.ascii .us-ascii
+AddCharset ISO-8859-1 .iso8859-1 .latin1
+AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen
+AddCharset ISO-8859-3 .iso8859-3 .latin3
+AddCharset ISO-8859-4 .iso8859-4 .latin4
+AddCharset ISO-8859-5 .iso8859-5 .cyr .iso-ru
+AddCharset ISO-8859-6 .iso8859-6 .arb .arabic
+AddCharset ISO-8859-7 .iso8859-7 .grk .greek
+AddCharset ISO-8859-8 .iso8859-8 .heb .hebrew
+AddCharset ISO-8859-9 .iso8859-9 .latin5 .trk
+AddCharset ISO-8859-10 .iso8859-10 .latin6
+AddCharset ISO-8859-13 .iso8859-13
+AddCharset ISO-8859-14 .iso8859-14 .latin8
+AddCharset ISO-8859-15 .iso8859-15 .latin9
+AddCharset ISO-8859-16 .iso8859-16 .latin10
+AddCharset ISO-2022-JP .iso2022-jp .jis
+AddCharset ISO-2022-KR .iso2022-kr .kis
+AddCharset ISO-2022-CN .iso2022-cn .cis
+AddCharset Big5.Big5 .big5 .b5
+AddCharset cn-Big5 .cn-big5
+# For russian, more than one charset is used (depends on client, mostly):
+AddCharset WINDOWS-1251 .cp-1251 .win-1251
+AddCharset CP866 .cp866
+AddCharset KOI8 .koi8
+AddCharset KOI8-E .koi8-e
+AddCharset KOI8-r .koi8-r .koi8-ru
+AddCharset KOI8-U .koi8-u
+AddCharset KOI8-ru .koi8-uk .ua
+AddCharset ISO-10646-UCS-2 .ucs2
+AddCharset ISO-10646-UCS-4 .ucs4
+AddCharset UTF-7 .utf7
+AddCharset UTF-8 .utf8
+AddCharset UTF-16 .utf16
+AddCharset UTF-16BE .utf16be
+AddCharset UTF-16LE .utf16le
+AddCharset UTF-32 .utf32
+AddCharset UTF-32BE .utf32be
+AddCharset UTF-32LE .utf32le
+AddCharset euc-cn .euc-cn
+AddCharset euc-gb .euc-gb
+AddCharset euc-jp .euc-jp
+AddCharset euc-kr .euc-kr
+# Not sure how euc-tw got in - IANA doesn't list it???
+AddCharset EUC-TW .euc-tw
+AddCharset gb2312 .gb2312 .gb
+AddCharset iso-10646-ucs-2 .ucs-2 .iso-10646-ucs-2
+AddCharset iso-10646-ucs-4 .ucs-4 .iso-10646-ucs-4
+AddCharset shift_jis .shift_jis .sjis
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf
new file mode 100644
index 000000000..10bf48317
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf
@@ -0,0 +1,85 @@
+
+
+
+
+# We include the /icons/ alias for FancyIndexed directory listings. If
+# you do not use FancyIndexing, you may comment this out.
+Alias /icons/ "/usr/share/apache2/icons/"
+
+
+ Options Indexes MultiViews
+ AllowOverride None
+ Require all granted
+
+
+
+# Directives controlling the display of server-generated directory listings.
+#
+# To see the listing of a directory, the Options directive for the
+# directory must include "Indexes", and the directory must not contain
+# a file matching those listed in the DirectoryIndex directive.
+
+# IndexOptions: Controls the appearance of server-generated directory
+# listings.
+IndexOptions FancyIndexing VersionSort
+
+# AddIcon* directives tell the server which icon to show for different
+# files or filename extensions. These are only displayed for
+# FancyIndexed directories.
+AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
+
+AddIconByType (TXT,/icons/text.gif) text/*
+AddIconByType (IMG,/icons/image2.gif) image/*
+AddIconByType (SND,/icons/sound2.gif) audio/*
+AddIconByType (VID,/icons/movie.gif) video/*
+
+AddIcon /icons/binary.gif .bin .exe
+AddIcon /icons/binhex.gif .hqx
+AddIcon /icons/tar.gif .tar
+AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
+AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
+AddIcon /icons/a.gif .ps .ai .eps
+AddIcon /icons/layout.gif .html .shtml .htm .pdf
+AddIcon /icons/text.gif .txt
+AddIcon /icons/c.gif .c
+AddIcon /icons/p.gif .pl .py
+AddIcon /icons/f.gif .for
+AddIcon /icons/dvi.gif .dvi
+AddIcon /icons/uuencoded.gif .uu
+AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
+AddIcon /icons/tex.gif .tex
+AddIcon /icons/bomb.gif core
+
+AddIcon /icons/back.gif ..
+AddIcon /icons/hand.right.gif README
+AddIcon /icons/folder.gif ^^DIRECTORY^^
+AddIcon /icons/blank.gif ^^BLANKICON^^
+
+# DefaultIcon is which icon to show for files which do not have an icon
+# explicitly set.
+DefaultIcon /icons/unknown.gif
+
+# AddDescription allows you to place a short description after a file in
+# server-generated indexes. These are only displayed for FancyIndexed
+# directories.
+# Format: AddDescription "description" filename
+
+#AddDescription "GZIP compressed document" .gz
+#AddDescription "tar archive" .tar
+#AddDescription "GZIP compressed tar archive" .tgz
+
+# ReadmeName is the name of the README file the server will look for by
+# default, and append to directory listings.
+
+# HeaderName is the name of a file which should be prepended to
+# directory indexes.
+ReadmeName README.html
+HeaderName HEADER.html
+
+# IndexIgnore is a set of filenames which directory indexing should ignore
+# and not include in the listing. Shell-style wildcarding is permitted.
+IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf
new file mode 100644
index 000000000..2cd32c477
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf
@@ -0,0 +1,10 @@
+
+# Allow remote server configuration reports, with the URL of
+# http://servername/server-info
+
+ SetHandler server-info
+ Require local
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf
new file mode 100644
index 000000000..ce0238eee
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf
@@ -0,0 +1,35 @@
+
+# The following directives define some format nicknames for use with
+# a CustomLog directive (see below).
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-Agent}i" agent
+LogFormat "%v %h %l %u %t \"%r\" %>s %b %T" script
+LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" VLOG=%{VLOG}e" vhost
+
+
+# You need to enable mod_logio.c to use %I and %O
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" vhostio
+
+
+# The location and format of the access logfile (Common Logfile Format).
+# If you do not define any access logfiles within a
+# container, they will be logged here. Contrariwise, if you *do*
+# define per- access logfiles, transactions will be
+# logged therein and *not* in this file.
+CustomLog /var/log/apache2/access_log common
+
+# If you would like to have agent and referer logfiles,
+# uncomment the following directives.
+#CustomLog /var/log/apache2/referer_log referer
+#CustomLog /var/log/apache2/agent_logs agent
+
+# If you prefer a logfile with access, agent, and referer information
+# (Combined Logfile Format) you can use the following directive.
+#CustomLog /var/log/apache2/access_log combined
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf
new file mode 100644
index 000000000..fb8a9a5d5
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf
@@ -0,0 +1,46 @@
+
+# TypesConfig points to the file containing the list of mappings from
+# filename extension to MIME-type.
+TypesConfig /etc/mime.types
+
+# AddType allows you to add to or override the MIME configuration
+# file specified in TypesConfig for specific file types.
+#AddType application/x-gzip .tgz
+
+# AddEncoding allows you to have certain browsers uncompress
+# information on the fly. Note: Not all browsers support this.
+#AddEncoding x-compress .Z
+#AddEncoding x-gzip .gz .tgz
+
+# If the AddEncoding directives above are commented-out, then you
+# probably should define those extensions to indicate media types:
+AddType application/x-compress .Z
+AddType application/x-gzip .gz .tgz
+
+# AddHandler allows you to map certain file extensions to "handlers":
+# actions unrelated to filetype. These can be either built into the server
+# or added with the Action directive (see below)
+
+# To use CGI scripts outside of ScriptAliased directories:
+# (You will also need to add "ExecCGI" to the "Options" directive.)
+#AddHandler cgi-script .cgi
+
+# For type maps (negotiated resources):
+#AddHandler type-map var
+
+# Filters allow you to process content before it is sent to the client.
+#
+# To parse .shtml files for server-side includes (SSI):
+# (You will also need to add "Includes" to the "Options" directive.)
+#AddType text/html .shtml
+#AddOutputFilter INCLUDES .shtml
+
+
+
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type. The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+MIMEMagicFile /etc/apache2/magic
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf
new file mode 100644
index 000000000..ed8b3c7cb
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf
@@ -0,0 +1,15 @@
+
+# Allow server status reports generated by mod_status,
+# with the URL of http://servername/server-status
+
+ SetHandler server-status
+ Require local
+
+
+# ExtendedStatus controls whether Apache will generate "full" status
+# information (ExtendedStatus On) or just basic information (ExtendedStatus
+# Off) when the "server-status" handler is called.
+ExtendedStatus On
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf
new file mode 100644
index 000000000..0087126c4
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf
@@ -0,0 +1,32 @@
+# Settings for user home directories
+
+# UserDir: The name of the directory that is appended onto a user's home
+# directory if a ~user request is received. Note that you must also set
+# the default access control for these directories, as in the example below.
+UserDir public_html
+
+# Control access to UserDir directories. The following is an example
+# for a site where these directories are restricted to read-only.
+
+ AllowOverride FileInfo AuthConfig Limit Indexes
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+
+ Require all granted
+
+
+ Require all denied
+
+
+
+# Suexec isn't really required to run cgi-scripts, but it's a really good
+# idea if you have multiple users serving websites...
+
+
+ Options ExecCGI
+ SetHandler cgi-script
+
+
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf
new file mode 100644
index 000000000..bcb9b6b47
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf
@@ -0,0 +1,99 @@
+# Server-Pool Management (MPM specific)
+
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+#
+# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING
+PidFile /run/apache2.pid
+
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+# Mutex file:/run/apache_mpm_mutex
+
+# Only one of the below sections will be relevant on your
+# installed httpd. Use "/usr/sbin/apache2 -l" to find out the
+# active mpm.
+
+# common MPM configuration
+# These configuration directives apply to all MPMs
+#
+# StartServers: Number of child server processes created at startup
+# MaxRequestWorkers: Maximum number of child processes to serve requests
+# MaxConnectionsPerChild: Limit on the number of connections that an individual
+# child server will handle during its life
+
+
+# prefork MPM
+# This is the default MPM if USE=-threads
+#
+# MinSpareServers: Minimum number of idle child server processes
+# MaxSpareServers: Maximum number of idle child server processes
+
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxRequestWorkers 150
+ MaxConnectionsPerChild 10000
+
+
+# worker MPM
+# This is the default MPM if USE=threads
+#
+# MinSpareThreads: Minimum number of idle threads available to handle request spikes
+# MaxSpareThreads: Maximum number of idle threads
+# ThreadsPerChild: Number of threads created by each child process
+
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadsPerChild 25
+ MaxRequestWorkers 150
+ MaxConnectionsPerChild 10000
+
+
+# event MPM
+#
+# MinSpareThreads: Minimum number of idle threads available to handle request spikes
+# MaxSpareThreads: Maximum number of idle threads
+# ThreadsPerChild: Number of threads created by each child process
+
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadsPerChild 25
+ MaxRequestWorkers 150
+ MaxConnectionsPerChild 10000
+
+
+# peruser MPM
+#
+# MinSpareProcessors: Minimum number of idle child server processes
+# MinProcessors: Minimum number of processors per virtual host
+# MaxProcessors: Maximum number of processors per virtual host
+# ExpireTimeout: Maximum idle time before a child is killed, 0 to disable
+# Multiplexer: Specify a Multiplexer child configuration.
+# Processor: Specify a user and group for a specific child process
+
+ MinSpareProcessors 2
+ MinProcessors 2
+ MaxProcessors 10
+ MaxRequestWorkers 150
+ MaxConnectionsPerChild 1000
+ ExpireTimeout 1800
+
+ Multiplexer nobody nobody
+ Processor apache apache
+
+
+# itk MPM
+#
+# MinSpareServers: Minimum number of idle child server processes
+# MaxSpareServers: Maximum number of idle child server processes
+
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxRequestWorkers 150
+ MaxConnectionsPerChild 10000
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf
new file mode 100644
index 000000000..520d9fd82
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf
@@ -0,0 +1,10 @@
+
+# 128MB cache for objects < 2MB
+CacheEnable mem /
+MCacheSize 131072
+MCacheMaxObjectCount 1000
+MCacheMinObjectSize 1
+MCacheMaxObjectSize 2097152
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf
new file mode 100644
index 000000000..f51de4641
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf
@@ -0,0 +1,67 @@
+# Note: The following must must be present to support
+# starting without SSL on platforms with no /dev/random equivalent
+# but a statically compiled-in mod_ssl.
+
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+
+
+
+# This is the Apache server configuration file providing SSL support.
+# It contains the configuration directives to instruct the server how to
+# serve pages over an https connection. For detailing information about these
+# directives see
+
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+
+## Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the SSL library.
+# The seed data should be of good random quality.
+# WARNING! On some platforms /dev/random blocks if not enough entropy
+# is available. This means you then cannot use the /dev/random device
+# because it would lead to very long connection times (as long as
+# it requires to make more entropy available). But usually those
+# platforms additionally provide a /dev/urandom device which doesn't
+# block. So, if available, use this one instead. Read the mod_ssl User
+# Manual for more details.
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed startup file:/dev/urandom 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+
+## SSL Global Context:
+# All SSL configuration in this context applies both to the main server and
+# all SSL-enabled virtual hosts.
+
+# Some MIME-types for downloading Certificates and CRLs
+
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+
+
+## Pass Phrase Dialog:
+# Configure the pass phrase gathering process. The filtering dialog program
+# (`builtin' is a internal terminal dialog) has to provide the pass phrase on
+# stdout.
+SSLPassPhraseDialog builtin
+
+## Inter-Process Session Cache:
+# Configure the SSL Session Cache: First the mechanism to use and second the
+# expiring timeout (in seconds).
+#SSLSessionCache dbm:/run/ssl_scache
+SSLSessionCache shmcb:/run/ssl_scache(512000)
+SSLSessionCacheTimeout 300
+
+## Semaphore:
+# Configure the path to the mutual exclusion semaphore the SSL engine uses
+# internally for inter-process synchronization.
+Mutex file:/run/apache_ssl_mutex ssl-cache
+
+## SSL Compression:
+# Known to be vulnerable thus disabled by default (bug #507324).
+SSLCompression off
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf
new file mode 100644
index 000000000..e4c9454e0
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf
@@ -0,0 +1,9 @@
+
+
+ # enable debugging for this module
+ #LogLevel http2:info
+
+ #Enable HTTP/2 support
+ Protocols h2 h2c http/1.1
+
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf
new file mode 100644
index 000000000..36f6b9cca
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf
@@ -0,0 +1,19 @@
+
+DavLockDB "/var/lib/dav/lockdb"
+
+# The following directives disable redirects on non-GET requests for
+# a directory that does not include the trailing slash. This fixes a
+# problem with several clients that do not appropriately handle
+# redirects for folders with DAV methods.
+
+BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+BrowserMatch "MS FrontPage" redirect-carefully
+BrowserMatch "^WebDrive" redirect-carefully
+BrowserMatch "^WebDAVFS/1.[012345678]" redirect-carefully
+BrowserMatch "^gnome-vfs/1.0" redirect-carefully
+BrowserMatch "^XML Spy" redirect-carefully
+BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf
new file mode 100644
index 000000000..883061fee
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf
@@ -0,0 +1,18 @@
+# Examples below are taken from the online documentation
+# Refer to:
+# http://localhost/manual/mod/mod_ldap.html
+# http://localhost/manual/mod/mod_auth_ldap.html
+
+LDAPSharedCacheSize 200000
+LDAPCacheEntries 1024
+LDAPCacheTTL 600
+LDAPOpCacheEntries 1024
+LDAPOpCacheTTL 600
+
+
+ SetHandler ldap-status
+ Require local
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/.keep_www-servers_apache-2 b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/.keep_www-servers_apache-2
new file mode 100644
index 000000000..e69de29bb
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf
new file mode 100644
index 000000000..bb395473c
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf
@@ -0,0 +1,191 @@
+
+
+
+# see bug #178966 why this is in here
+
+# When we also provide SSL we have to listen to the HTTPS port
+# Note: Configurations that use IPv6 but not IPv4-mapped addresses need two
+# Listen directives: "Listen [::]:443" and "Listen 0.0.0.0:443"
+Listen 443
+
+
+ ServerName localhost
+ Include /etc/apache2/vhosts.d/default_vhost.include
+ ErrorLog /var/log/apache2/ssl_error_log
+
+
+ TransferLog /var/log/apache2/ssl_access_log
+
+
+ ## SSL Engine Switch:
+ # Enable/Disable SSL for this virtual host.
+ SSLEngine on
+
+ ## SSLProtocol:
+ # Don't use SSLv2 anymore as it's considered to be broken security-wise.
+ # Also disable SSLv3 as most modern browsers are capable of TLS.
+ SSLProtocol ALL -SSLv2 -SSLv3
+
+ ## SSL Cipher Suite:
+ # List the ciphers that the client is permitted to negotiate.
+ # See the mod_ssl documentation for a complete list.
+ # This list of ciphers is recommended by mozilla and was stripped off
+ # its RC4 ciphers. (bug #506924)
+ SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:HIGH:!RC4:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK
+
+ ## SSLHonorCipherOrder:
+ # Prefer the server's cipher preference order as the client may have a
+ # weak default order.
+ SSLHonorCipherOrder On
+
+ ## Server Certificate:
+ # Point SSLCertificateFile at a PEM encoded certificate. If the certificate
+ # is encrypted, then you will be prompted for a pass phrase. Note that a
+ # kill -HUP will prompt again. Keep in mind that if you have both an RSA
+ # and a DSA certificate you can configure both in parallel (to also allow
+ # the use of DSA ciphers, etc.)
+ SSLCertificateFile /etc/ssl/apache2/server.crt
+
+ ## Server Private Key:
+ # If the key is not combined with the certificate, use this directive to
+ # point at the key file. Keep in mind that if you've both a RSA and a DSA
+ # private key you can configure both in parallel (to also allow the use of
+ # DSA ciphers, etc.)
+ SSLCertificateKeyFile /etc/ssl/apache2/server.key
+
+ ## Server Certificate Chain:
+ # Point SSLCertificateChainFile at a file containing the concatenation of
+ # PEM encoded CA certificates which form the certificate chain for the
+ # server certificate. Alternatively the referenced file can be the same as
+ # SSLCertificateFile when the CA certificates are directly appended to the
+ # server certificate for convinience.
+ #SSLCertificateChainFile /etc/ssl/apache2/ca.crt
+
+ ## Certificate Authority (CA):
+ # Set the CA certificate verification path where to find CA certificates
+ # for client authentication or alternatively one huge file containing all
+ # of them (file must be PEM encoded).
+ # Note: Inside SSLCACertificatePath you need hash symlinks to point to the
+ # certificate files. Use the provided Makefile to update the hash symlinks
+ # after changes.
+ #SSLCACertificatePath /etc/ssl/apache2/ssl.crt
+ #SSLCACertificateFile /etc/ssl/apache2/ca-bundle.crt
+
+ ## Certificate Revocation Lists (CRL):
+ # Set the CA revocation path where to find CA CRLs for client authentication
+ # or alternatively one huge file containing all of them (file must be PEM
+ # encoded).
+ # Note: Inside SSLCARevocationPath you need hash symlinks to point to the
+ # certificate files. Use the provided Makefile to update the hash symlinks
+ # after changes.
+ #SSLCARevocationPath /etc/ssl/apache2/ssl.crl
+ #SSLCARevocationFile /etc/ssl/apache2/ca-bundle.crl
+
+ ## Client Authentication (Type):
+ # Client certificate verification type and depth. Types are none, optional,
+ # require and optional_no_ca. Depth is a number which specifies how deeply
+ # to verify the certificate issuer chain before deciding the certificate is
+ # not valid.
+ #SSLVerifyClient require
+ #SSLVerifyDepth 10
+
+ ## Access Control:
+ # With SSLRequire you can do per-directory access control based on arbitrary
+ # complex boolean expressions containing server variable checks and other
+ # lookup directives. The syntax is a mixture between C and Perl. See the
+ # mod_ssl documentation for more details.
+ #
+ # #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \
+ # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \
+ # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \
+ # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
+ # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \
+ # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
+ #
+
+ ## SSL Engine Options:
+ # Set various options for the SSL engine.
+
+ ## FakeBasicAuth:
+ # Translate the client X.509 into a Basic Authorisation. This means that the
+ # standard Auth/DBMAuth methods can be used for access control. The user
+ # name is the `one line' version of the client's X.509 certificate.
+ # Note that no password is obtained from the user. Every entry in the user
+ # file needs this password: `xxj31ZMTZzkVA'.
+
+ ## ExportCertData:
+ # This exports two additional environment variables: SSL_CLIENT_CERT and
+ # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the server
+ # (always existing) and the client (only existing when client
+ # authentication is used). This can be used to import the certificates into
+ # CGI scripts.
+
+ ## StdEnvVars:
+ # This exports the standard SSL/TLS related `SSL_*' environment variables.
+ # Per default this exportation is switched off for performance reasons,
+ # because the extraction step is an expensive operation and is usually
+ # useless for serving static content. So one usually enables the exportation
+ # for CGI and SSI requests only.
+
+ ## StrictRequire:
+ # This denies access when "SSLRequireSSL" or "SSLRequire" applied even under
+ # a "Satisfy any" situation, i.e. when it applies access is denied and no
+ # other module can change it.
+
+ ## OptRenegotiate:
+ # This enables optimized SSL connection renegotiation handling when SSL
+ # directives are used in per-directory context.
+ #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
+
+ SSLOptions +StdEnvVars
+
+
+
+ SSLOptions +StdEnvVars
+
+
+ ## SSL Protocol Adjustments:
+ # The safe and default but still SSL/TLS standard compliant shutdown
+ # approach is that mod_ssl sends the close notify alert but doesn't wait
+ # for the close notify alert from client. When you need a different
+ # shutdown approach you can use one of the following variables:
+
+ ## ssl-unclean-shutdown:
+ # This forces an unclean shutdown when the connection is closed, i.e. no
+ # SSL close notify alert is send or allowed to received. This violates the
+ # SSL/TLS standard but is needed for some brain-dead browsers. Use this when
+ # you receive I/O errors because of the standard approach where mod_ssl
+ # sends the close notify alert.
+
+ ## ssl-accurate-shutdown:
+ # This forces an accurate shutdown when the connection is closed, i.e. a
+ # SSL close notify alert is send and mod_ssl waits for the close notify
+ # alert of the client. This is 100% SSL/TLS standard compliant, but in
+ # practice often causes hanging connections with brain-dead browsers. Use
+ # this only for browsers where you know that their SSL implementation works
+ # correctly.
+ # Notice: Most problems of broken clients are also related to the HTTP
+ # keep-alive facility, so you usually additionally want to disable
+ # keep-alive for those clients, too. Use variable "nokeepalive" for this.
+ # Similarly, one has to force some clients to use HTTP/1.0 to workaround
+ # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
+ # "force-response-1.0" for this.
+
+ BrowserMatch ".*MSIE.*" \
+ nokeepalive ssl-unclean-shutdown \
+ downgrade-1.0 force-response-1.0
+
+
+ ## Per-Server Logging:
+ # The home of a custom SSL log file. Use this when you want a compact
+ # non-error SSL logfile on a virtual host basis.
+
+ CustomLog /var/log/apache2/ssl_request_log \
+ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
+
+
+
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf
new file mode 100644
index 000000000..b9766b5f1
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf
@@ -0,0 +1,45 @@
+# Virtual Hosts
+#
+# If you want to maintain multiple domains/hostnames on your
+# machine you can setup VirtualHost containers for them. Most configurations
+# use only name-based virtual hosts so the server doesn't need to worry about
+# IP addresses. This is indicated by the asterisks in the directives below.
+#
+# Please see the documentation at
+#
+# for further details before you try to setup virtual hosts.
+#
+# You may use the command line option '-S' to verify your virtual host
+# configuration.
+
+
+# see bug #178966 why this is in here
+
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+
+# When virtual hosts are enabled, the main host defined in the default
+# httpd.conf configuration will go away. We redefine it here so that it is
+# still available.
+#
+# If you disable this vhost by removing -D DEFAULT_VHOST from
+# /etc/conf.d/apache2, the first defined virtual host elsewhere will be
+# the default.
+
+ ServerName localhost
+ Include /etc/apache2/vhosts.d/default_vhost.include
+
+
+ ServerEnvironment apache apache
+
+
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include
new file mode 100644
index 000000000..af6ece85b
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include
@@ -0,0 +1,71 @@
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+ServerAdmin root@localhost
+
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+# If you change this to something that isn't under /var/www then suexec
+# will no longer work.
+DocumentRoot "/var/www/localhost/htdocs"
+
+# This should be changed to whatever you set DocumentRoot to.
+
+ # Possible values for the Options directive are "None", "All",
+ # or any combination of:
+ # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+ #
+ # Note that "MultiViews" must be named *explicitly* --- "Options All"
+ # doesn't give it to you.
+ #
+ # The Options directive is both complicated and important. Please see
+ # http://httpd.apache.org/docs/2.4/mod/core.html#options
+ # for more information.
+ Options Indexes FollowSymLinks
+
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # Options FileInfo AuthConfig Limit
+ AllowOverride All
+
+ # Controls who can get stuff from this server.
+ Require all granted
+
+
+
+ # Redirect: Allows you to tell clients about documents that used to
+ # exist in your server's namespace, but do not anymore. The client
+ # will make a new request for the document at its new location.
+ # Example:
+ # Redirect permanent /foo http://www.example.com/bar
+
+ # Alias: Maps web paths into filesystem paths and is used to
+ # access content that does not live under the DocumentRoot.
+ # Example:
+ # Alias /webpath /full/filesystem/path
+ #
+ # If you include a trailing / on /webpath then the server will
+ # require it to be present in the URL. You will also likely
+ # need to provide a section to allow access to
+ # the filesystem path.
+
+ # ScriptAlias: This controls which directories contain server scripts.
+ # ScriptAliases are essentially the same as Aliases, except that
+ # documents in the target directory are treated as applications and
+ # run by the server when requested rather than as documents sent to the
+ # client. The same rules about trailing "/" apply to ScriptAlias
+ # directives as to Alias.
+ ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/"
+
+
+# "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+
+ AllowOverride None
+ Options None
+ Require all granted
+
+
+# vim: ts=4 filetype=apache
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf
new file mode 100644
index 000000000..41de4d236
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf
@@ -0,0 +1,7 @@
+
+ ServerName gentoo.example.com
+ ServerAdmin webmaster@localhost
+ DocumentRoot /var/www/html
+ ErrorLog ${APACHE_LOG_DIR}/error.log
+ CustomLog ${APACHE_LOG_DIR}/access.log combined
+
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2
new file mode 100644
index 000000000..b7ecb4f2a
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2
@@ -0,0 +1,74 @@
+# /etc/conf.d/apache2: config file for /etc/init.d/apache2
+
+# When you install a module it is easy to activate or deactivate the modules
+# and other features of apache using the APACHE2_OPTS line. Every module should
+# install a configuration in /etc/apache2/modules.d. In that file will have an
+# directive where NNN is the option to enable that module.
+#
+# Here are the options available in the default configuration:
+#
+# AUTH_DIGEST Enables mod_auth_digest
+# AUTHNZ_LDAP Enables authentication through mod_ldap (available if USE=ldap)
+# CACHE Enables mod_cache
+# DAV Enables mod_dav
+# ERRORDOCS Enables default error documents for many languages.
+# INFO Enables mod_info, a useful module for debugging
+# LANGUAGE Enables content-negotiation based on language and charset.
+# LDAP Enables mod_ldap (available if USE=ldap)
+# MANUAL Enables /manual/ to be the apache manual (available if USE=docs)
+# MEM_CACHE Enables default configuration mod_mem_cache
+# PROXY Enables mod_proxy
+# SSL Enables SSL (available if USE=ssl)
+# STATUS Enabled mod_status, a useful module for statistics
+# SUEXEC Enables running CGI scripts (in USERDIR) through suexec.
+# USERDIR Enables /~username mapping to /home/username/public_html
+#
+#
+# The following two options provide the default virtual host for the HTTP and
+# HTTPS protocol. YOU NEED TO ENABLE AT LEAST ONE OF THEM, otherwise apache
+# will not listen for incomming connections on the approriate port.
+#
+# DEFAULT_VHOST Enables name-based virtual hosts, with the default
+# virtual host being in /var/www/localhost/htdocs
+# SSL_DEFAULT_VHOST Enables default vhost for SSL (you should enable this
+# when you enable SSL)
+#
+APACHE2_OPTS="-D DEFAULT_VHOST -D INFO -D SSL -D SSL_DEFAULT_VHOST -D LANGUAGE"
+
+# Extended options for advanced uses of Apache ONLY
+# You don't need to edit these unless you are doing crazy Apache stuff
+# As not having them set correctly, or feeding in an incorrect configuration
+# via them will result in Apache failing to start
+# YOU HAVE BEEN WARNED.
+
+# PID file
+#PIDFILE=/var/run/apache2.pid
+
+# timeout for startup/shutdown checks
+#TIMEOUT=10
+
+# ServerRoot setting
+#SERVERROOT=/usr/lib64/apache2
+
+# Configuration file location
+# - If this does NOT start with a '/', then it is treated relative to
+# $SERVERROOT by Apache
+#CONFIGFILE=/etc/apache2/httpd.conf
+
+# Location to log startup errors to
+# They are normally dumped to your terminal.
+#STARTUPERRORLOG="/var/log/apache2/startuperror.log"
+
+# A command that outputs a formatted text version of the HTML at the URL
+# of the command line. Designed for lynx, however other programs may work.
+#LYNX="lynx -dump"
+
+# The URL to your server's mod_status status page.
+# Required for status and fullstatus
+#STATUSURL="http://localhost/server-status"
+
+# Method to use when reloading the server
+# Valid options are 'restart' and 'graceful'
+# See http://httpd.apache.org/docs/2.2/stopping.html for information on
+# what they do and how they differ.
+#RELOAD_TYPE="graceful"
diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites
new file mode 100644
index 000000000..7f0b3a8b3
--- /dev/null
+++ b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites
@@ -0,0 +1,3 @@
+vhosts.d/gentoo.example.com.conf, gentoo.example.com
+vhosts.d/00_default_vhost.conf, localhost
+vhosts.d/00_default_ssl_vhost.conf, localhost
diff --git a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py b/certbot-apache/certbot_apache/tests/tls_sni_01_test.py
index 62464d5d0..6c37c2ecc 100644
--- a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py
+++ b/certbot-apache/certbot_apache/tests/tls_sni_01_test.py
@@ -23,7 +23,8 @@ class TlsSniPerformTest(util.ApacheTest):
super(TlsSniPerformTest, self).setUp()
config = util.get_apache_configurator(
- self.config_path, self.vhost_path, self.config_dir, self.work_dir)
+ self.config_path, self.vhost_path, self.config_dir,
+ self.work_dir)
config.config.tls_sni_01_port = 443
from certbot_apache import tls_sni_01
@@ -41,8 +42,8 @@ class TlsSniPerformTest(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot.util.run_script")
def test_perform1(self, _, mock_exists):
- mock_register = mock.Mock()
- self.sni.configurator.reverter.register_undo_command = mock_register
+ self.sni.configurator.parser.modules.add("socache_shmcb_module")
+ self.sni.configurator.parser.modules.add("ssl_module")
mock_exists.return_value = True
self.sni.configurator.parser.update_runtime_variables = mock.Mock()
@@ -55,10 +56,6 @@ class TlsSniPerformTest(util.ApacheTest):
self.sni._setup_challenge_cert = mock_setup_cert
responses = self.sni.perform()
-
- # Make sure that register_undo_command was called into temp directory.
- self.assertEqual(True, mock_register.call_args[0][0])
-
mock_setup_cert.assert_called_once_with(achall)
# Check to make sure challenge config path is included in apache config
@@ -71,7 +68,7 @@ class TlsSniPerformTest(util.ApacheTest):
def test_perform2(self):
# Avoid load module
self.sni.configurator.parser.modules.add("ssl_module")
-
+ self.sni.configurator.parser.modules.add("socache_shmcb_module")
acme_responses = []
for achall in self.achalls:
self.sni.add_chall(achall)
@@ -81,7 +78,8 @@ class TlsSniPerformTest(util.ApacheTest):
# pylint: disable=protected-access
self.sni._setup_challenge_cert = mock_setup_cert
- with mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod"):
+ with mock.patch(
+ "certbot_apache.override_debian.DebianConfigurator.enable_mod"):
sni_responses = self.sni.perform()
self.assertEqual(mock_setup_cert.call_count, 2)
diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py
index 34d2476f7..2405110c5 100644
--- a/certbot-apache/certbot_apache/tests/util.py
+++ b/certbot-apache/certbot_apache/tests/util.py
@@ -1,5 +1,6 @@
"""Common utilities for certbot_apache."""
import os
+import shutil
import sys
import unittest
@@ -16,7 +17,7 @@ from certbot.plugins import common
from certbot.tests import util as test_util
from certbot_apache import configurator
-from certbot_apache import constants
+from certbot_apache import entrypoint
from certbot_apache import obj
@@ -38,6 +39,9 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods
self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector(
"rsa512_key.pem"))
+ self.config = get_apache_configurator(self.config_path, vhost_root,
+ self.config_dir, self.work_dir)
+
# Make sure all vhosts in sites-enabled are symlinks (Python packaging
# does not preserve symlinks)
sites_enabled = os.path.join(self.config_path, "sites-enabled")
@@ -55,8 +59,13 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods
os.path.pardir, "sites-available", vhost_basename)
os.symlink(target, vhost)
+ def tearDown(self):
+ shutil.rmtree(self.temp_dir)
+ shutil.rmtree(self.config_dir)
+ shutil.rmtree(self.work_dir)
-class ParserTest(ApacheTest): # pytlint: disable=too-few-public-methods
+
+class ParserTest(ApacheTest):
def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts",
config_root="debian_apache_2_4/multiple_vhosts/apache2",
@@ -72,12 +81,16 @@ class ParserTest(ApacheTest): # pytlint: disable=too-few-public-methods
with mock.patch("certbot_apache.parser.ApacheParser."
"update_runtime_variables"):
self.parser = ApacheParser(
- self.aug, self.config_path, self.vhost_path)
+ self.aug, self.config_path, self.vhost_path,
+ configurator=self.config)
-def get_apache_configurator(
+def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals
config_path, vhost_path,
- config_dir, work_dir, version=(2, 4, 7), conf=None):
+ config_dir, work_dir, version=(2, 4, 7),
+ conf=None,
+ os_info="generic",
+ conf_vhost_path=None):
"""Create an Apache Configurator with the specified options.
:param conf: Function that returns binary paths. self.conf in Configurator
@@ -86,8 +99,8 @@ def get_apache_configurator(
backups = os.path.join(work_dir, "backups")
mock_le_config = mock.MagicMock(
apache_server_root=config_path,
- apache_vhost_root=vhost_path,
- apache_le_vhost_ext=constants.os_constant("le_vhost_ext"),
+ apache_vhost_root=conf_vhost_path,
+ apache_le_vhost_ext="-le-ssl.conf",
apache_challenge_location=config_path,
backup_dir=backups,
config_dir=config_dir,
@@ -95,22 +108,37 @@ def get_apache_configurator(
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir)
- 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"):
- config = configurator.ApacheConfigurator(
- config=mock_le_config,
- name="apache",
- version=version)
- # This allows testing scripts to set it a bit more quickly
- if conf is not None:
- config.conf = conf # pragma: no cover
+ orig_os_constant = configurator.ApacheConfigurator(mock_le_config,
+ name="apache",
+ version=version).constant
- config.prepare()
+ 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)
+ # This allows testing scripts to set it a bit more
+ # quickly
+ if conf is not None:
+ config.conf = conf # pragma: no cover
+
+ config.prepare()
return config
diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py
index b276f49f8..3270f2c79 100644
--- a/certbot-apache/setup.py
+++ b/certbot-apache/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@@ -63,7 +63,7 @@ setup(
},
entry_points={
'certbot.plugins': [
- 'apache = certbot_apache.configurator:ApacheConfigurator',
+ 'apache = certbot_apache.entrypoint:ENTRYPOINT',
],
},
test_suite='certbot_apache',
diff --git a/certbot-auto b/certbot-auto
index 25f2ce889..444bee1b9 100755
--- a/certbot-auto
+++ b/certbot-auto
@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
-LE_AUTO_VERSION="0.19.0"
+LE_AUTO_VERSION="0.20.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -1062,9 +1062,10 @@ zope.interface==4.1.3 \
--hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \
--hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \
--hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392
-mock==2.0.0 \
- --hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \
- --hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba
+# Using an older version of mock here prevents regressions of #5276.
+mock==1.3.0 \
+ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \
+ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6
# Contains the requirements for the letsencrypt package.
#
@@ -1077,18 +1078,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
-certbot==0.19.0 \
- --hash=sha256:3207ee5319bfc37e855c25a43148275fcfb37869eefde9087405012049734a20 \
- --hash=sha256:a7230791dff5d085738119fc22d88ad9d8a35d0b6a3d67806fe33990c7c79d53
-acme==0.19.0 \
- --hash=sha256:c612eafe234d722d97bb5d3dbc49e5522f44be29611f7577954eb893e5c2d6de \
- --hash=sha256:1fa23d64d494aaf001e6fe857c461fcfff10f75a1c2c35ec831447f641e1e822
-certbot-apache==0.19.0 \
- --hash=sha256:fadb28b33bfabc85cdb962b5b149bef58b98f0606b78581db7895fe38323f37c \
- --hash=sha256:70306ca2d5be7f542af68d46883c0ae39527cf202f17ef92cd256fb0bc3f1619
-certbot-nginx==0.19.0 \
- --hash=sha256:4909cb3db49919fb35590793cac28e1c0b6dbd29cbedf887b9106e5fcef5362c \
- --hash=sha256:cb5a224a3f277092555c25096d1678fc735306fd3a43447649ebe524c7ca79e1
+certbot==0.20.0 \
+ --hash=sha256:c6b6bd288700898d1eb31a65b605e3a5fc10f1e3213ce468207d76a2decb9d35 \
+ --hash=sha256:cabf505b64fb400c4239dcdbaeb882079477eb6a8442268596a8791b9e34de88
+acme==0.20.0 \
+ --hash=sha256:8b0cee192c0d76d6f4045bdb14b3cfd29d9720e0dad2046794a2a555f1eaccb7 \
+ --hash=sha256:45121aed6c8cc2f31896ac1083068dfdeb613f3edeff9576dc0d10632ea5a3d5
+certbot-apache==0.20.0 \
+ --hash=sha256:f7e4dbc154d2e9d1461118b6dd3dbd16f6892da468f060eeaa162aff673347e2 \
+ --hash=sha256:0ba499706451ffbccb172bcf93d6ef4c6cc8599157077a4fa6dfbe5a83c7921f
+certbot-nginx==0.20.0 \
+ --hash=sha256:b6e372e8740b20dd9bd63837646157ac97b3c9a65affd3954571b8e872ae9ecf \
+ --hash=sha256:6379fdf20d9a7651fe30bb8d4b828cbea178cc263d7af5a380fc4508d793b9ae
UNLIKELY_EOF
# -------------------------------------------------------------------------
diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py
index 2e9e68daf..1d2cfdeca 100644
--- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py
+++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py
@@ -9,8 +9,7 @@ import zope.interface
from certbot import configuration
from certbot import errors as le_errors
from certbot import util as certbot_util
-from certbot_apache import configurator
-from certbot_apache import constants
+from certbot_apache import entrypoint
from certbot_compatibility_test import errors
from certbot_compatibility_test import interfaces
from certbot_compatibility_test import util
@@ -56,13 +55,14 @@ class Proxy(configurators_common.Proxy):
def _prepare_configurator(self):
"""Prepares the Apache plugin for testing"""
- for k in constants.CLI_DEFAULTS_DEBIAN.keys():
- setattr(self.le_config, "apache_" + k, constants.os_constant(k))
+ for k in entrypoint.ENTRYPOINT.OS_DEFAULTS.keys():
+ 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 = configurator.ApacheConfigurator(
+ self._configurator = entrypoint.ENTRYPOINT(
config=configuration.NamespaceConfig(self.le_config),
name="apache")
self._configurator.prepare()
diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py
index 166c383b3..1faf30643 100644
--- a/certbot-compatibility-test/setup.py
+++ b/certbot-compatibility-test/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
install_requires = [
'certbot',
diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py
index 6392e483c..428271045 100644
--- a/certbot-dns-cloudflare/setup.py
+++ b/certbot-dns-cloudflare/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py
index 304acf110..4a103193f 100644
--- a/certbot-dns-cloudxns/setup.py
+++ b/certbot-dns-cloudxns/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py
index 0fdacf4ad..3b8edce64 100644
--- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py
+++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py
@@ -131,7 +131,7 @@ class DigitalOceanClientTest(unittest.TestCase):
self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content)
- correct_record_mock.destroy.assert_called()
+ self.assertTrue(correct_record_mock.destroy.called)
self.assertFalse(first_record_mock.destroy.call_args_list)
self.assertFalse(last_record_mock.destroy.call_args_list)
diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py
index 489321435..23098d4b6 100644
--- a/certbot-dns-digitalocean/setup.py
+++ b/certbot-dns-digitalocean/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py
index 67d68ee16..4ed5a06ca 100644
--- a/certbot-dns-dnsimple/setup.py
+++ b/certbot-dns-dnsimple/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py
index 1ddec208b..8a0b88aab 100644
--- a/certbot-dns-dnsmadeeasy/setup.py
+++ b/certbot-dns-dnsmadeeasy/setup.py
@@ -4,15 +4,13 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
- # new versions of lexicon require that we install dnsmadeeasy extras and
- # 2.1.11 is the first version that defines them.
- 'dns-lexicon[dnsmadeeasy]>=2.1.11',
+ 'dns-lexicon',
'mock',
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
# will tolerate; see #2599:
diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py
index b40899e80..b00bd1ac3 100644
--- a/certbot-dns-google/setup.py
+++ b/certbot-dns-google/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py
index 1b72168e8..b8f50254e 100644
--- a/certbot-dns-luadns/setup.py
+++ b/certbot-dns-luadns/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py
index e9dc2b31d..2a388e487 100644
--- a/certbot-dns-nsone/setup.py
+++ b/certbot-dns-nsone/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py
index 79b523aed..78007afb5 100644
--- a/certbot-dns-rfc2136/setup.py
+++ b/certbot-dns-rfc2136/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py
index ff07b6ccd..d5f1b2816 100644
--- a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py
+++ b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py
@@ -31,7 +31,7 @@ class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest
self.auth._change_txt_record.assert_called_once_with("UPSERT",
'_acme-challenge.' + DOMAIN,
mock.ANY)
- self.auth._wait_for_change.assert_called_once()
+ self.assertEqual(self.auth._wait_for_change.call_count, 1)
def test_perform_no_credentials_error(self):
self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError)
@@ -183,7 +183,8 @@ class ClientTest(unittest.TestCase):
self.client._change_txt_record("FOO", DOMAIN, "foo")
- self.client.r53.change_resource_record_sets.assert_called_once()
+ call_count = self.client.r53.change_resource_record_sets.call_count
+ self.assertEqual(call_count, 1)
def test_wait_for_change(self):
self.client.r53.get_change = mock.MagicMock(
@@ -192,7 +193,7 @@ class ClientTest(unittest.TestCase):
self.client._wait_for_change(1)
- self.client.r53.get_change.assert_called()
+ self.assertTrue(self.client.r53.get_change.called)
if __name__ == "__main__":
diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py
index 2a14e8ab1..7d1eb0bc9 100644
--- a/certbot-dns-route53/setup.py
+++ b/certbot-dns-route53/setup.py
@@ -3,7 +3,7 @@ import sys
from distutils.core import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
install_requires = [
'acme=={0}'.format(version),
diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py
index 98990664f..e9d4e36d4 100644
--- a/certbot-nginx/certbot_nginx/configurator.py
+++ b/certbot-nginx/certbot_nginx/configurator.py
@@ -23,43 +23,24 @@ from certbot import util
from certbot.plugins import common
from certbot_nginx import constants
-from certbot_nginx import tls_sni_01
+from certbot_nginx import nginxparser
from certbot_nginx import parser
+from certbot_nginx import tls_sni_01
logger = logging.getLogger(__name__)
-REDIRECT_BLOCK = [[
- ['\n ', 'if', ' ', '($scheme', ' ', '!=', ' ', '"https")'],
- [['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'],
- '\n ']
-], ['\n']]
-
-TEST_REDIRECT_BLOCK = [
- [
- ['if', '($scheme', '!=', '"https")'],
- [
- ['return', '301', 'https://$host$request_uri']
- ]
- ],
- ['#', ' managed by Certbot']
+REDIRECT_BLOCK = [
+ ['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'],
+ ['\n']
]
REDIRECT_COMMENT_BLOCK = [
['\n ', '#', ' Redirect non-https traffic to https'],
- ['\n ', '#', ' if ($scheme != "https") {'],
- ['\n ', '#', " return 301 https://$host$request_uri;"],
- ['\n ', '#', " } # managed by Certbot"],
+ ['\n ', '#', ' return 301 https://$host$request_uri;'],
['\n']
]
-TEST_REDIRECT_COMMENT_BLOCK = [
- ['#', ' Redirect non-https traffic to https'],
- ['#', ' if ($scheme != "https") {'],
- ['#', " return 301 https://$host$request_uri;"],
- ['#', " } # managed by Certbot"],
-]
-
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class NginxConfigurator(common.Installer):
@@ -194,9 +175,7 @@ class NginxConfigurator(common.Installer):
"The nginx plugin currently requires --fullchain-path to "
"install a cert.")
- vhost = self.choose_vhost(domain, raise_if_no_match=False)
- if vhost is None:
- vhost = self._vhost_from_duplicated_default(domain)
+ vhost = self.choose_vhost(domain, create_if_no_match=True)
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
['\n ', 'ssl_certificate_key', ' ', key_path]]
@@ -214,7 +193,7 @@ class NginxConfigurator(common.Installer):
#######################
# Vhost parsing methods
#######################
- def choose_vhost(self, target_name, raise_if_no_match=True):
+ def choose_vhost(self, target_name, create_if_no_match=False):
"""Chooses a virtual host based on the given domain name.
.. note:: This makes the vhost SSL-enabled if it isn't already. Follows
@@ -228,8 +207,8 @@ class NginxConfigurator(common.Installer):
hostname. Currently we just ignore this.
:param str target_name: domain name
- :param bool raise_if_no_match: True iff not finding a match is an error;
- otherwise, return None
+ :param bool create_if_no_match: If we should create a new vhost from default
+ when there is no match found
:returns: ssl vhost associated with name
:rtype: :class:`~certbot_nginx.obj.VirtualHost`
@@ -240,7 +219,9 @@ class NginxConfigurator(common.Installer):
matches = self._get_ranked_matches(target_name)
vhost = self._select_best_name_match(matches)
if not vhost:
- if raise_if_no_match:
+ if create_if_no_match:
+ vhost = self._vhost_from_duplicated_default(target_name)
+ else:
# No matches. Raise a misconfiguration error.
raise errors.MisconfigurationError(
("Cannot find a VirtualHost matching domain %s. "
@@ -248,16 +229,12 @@ class NginxConfigurator(common.Installer):
"please add a corresponding server_name directive to your "
"nginx configuration: "
"https://nginx.org/en/docs/http/server_names.html") % (target_name))
- else:
- return None
- else:
- # Note: if we are enhancing with ocsp, vhost should already be ssl.
- if not vhost.ssl:
- self._make_server_ssl(vhost)
+ # Note: if we are enhancing with ocsp, vhost should already be ssl.
+ if not vhost.ssl:
+ self._make_server_ssl(vhost)
return vhost
-
def ipv6_info(self, port):
"""Returns tuple of booleans (ipv6_active, ipv6only_present)
ipv6_active is true if any server block listens ipv6 address in any port
@@ -285,18 +262,19 @@ class NginxConfigurator(common.Installer):
def _vhost_from_duplicated_default(self, domain):
if self.new_vhost is None:
default_vhost = self._get_default_vhost()
- self.new_vhost = self.parser.create_new_vhost_from_default(default_vhost)
- if not self.new_vhost.ssl:
- self._make_server_ssl(self.new_vhost)
+ self.new_vhost = self.parser.duplicate_vhost(default_vhost, delete_default=True)
self.new_vhost.names = set()
- self.new_vhost.names.add(domain)
+ self._add_server_name_to_vhost(self.new_vhost, domain)
+ return self.new_vhost
+
+ def _add_server_name_to_vhost(self, vhost, domain):
+ vhost.names.add(domain)
name_block = [['\n ', 'server_name']]
- for name in self.new_vhost.names:
+ for name in vhost.names:
name_block[0].append(' ')
name_block[0].append(name)
- self.parser.add_server_directives(self.new_vhost, name_block, replace=True)
- return self.new_vhost
+ self.parser.add_server_directives(vhost, name_block, replace=True)
def _get_default_vhost(self):
vhost_list = self.parser.get_vhosts()
@@ -505,11 +483,7 @@ class NginxConfigurator(common.Installer):
def _make_server_ssl(self, vhost):
"""Make a server SSL.
- Make a server SSL based on server_name and filename by adding a
- ``listen IConfig.tls_sni_01_port ssl`` directive to the server block.
-
- .. todo:: Maybe this should create a new block instead of modifying
- the existing one?
+ Make a server SSL by adding new listen and SSL directives.
:param vhost: The vhost to add SSL to.
:type vhost: :class:`~certbot_nginx.obj.VirtualHost`
@@ -529,7 +503,9 @@ class NginxConfigurator(common.Installer):
ipv6_block = ['\n ',
'listen',
' ',
- '[::]:{0} ssl'.format(self.config.tls_sni_01_port)]
+ '[::]:{0}'.format(self.config.tls_sni_01_port),
+ ' ',
+ 'ssl']
if not ipv6info[1]:
# ipv6only=on is absent in global config
ipv6_block.append(' ')
@@ -539,8 +515,9 @@ class NginxConfigurator(common.Installer):
ipv4_block = ['\n ',
'listen',
' ',
- '{0} ssl'.format(self.config.tls_sni_01_port)]
-
+ '{0}'.format(self.config.tls_sni_01_port),
+ ' ',
+ 'ssl']
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
@@ -584,10 +561,12 @@ class NginxConfigurator(common.Installer):
raise
def _has_certbot_redirect(self, vhost):
- return vhost.contains_list(TEST_REDIRECT_BLOCK)
+ test_redirect_block = _test_block_from_block(REDIRECT_BLOCK)
+ return vhost.contains_list(test_redirect_block)
def _has_certbot_redirect_comment(self, vhost):
- return vhost.contains_list(TEST_REDIRECT_COMMENT_BLOCK)
+ test_redirect_comment_block = _test_block_from_block(REDIRECT_COMMENT_BLOCK)
+ return vhost.contains_list(test_redirect_comment_block)
def _add_redirect_block(self, vhost, active=True):
"""Add redirect directive to vhost
@@ -603,7 +582,8 @@ class NginxConfigurator(common.Installer):
def _enable_redirect(self, domain, unused_options):
"""Redirect all equivalent HTTP traffic to ssl_vhost.
- Add rewrite directive to non https traffic
+ If the vhost is listening plaintextishly, separate out the
+ relevant directives into a new server block and add a rewrite directive.
.. note:: This function saves the configuration
@@ -616,26 +596,46 @@ class NginxConfigurator(common.Installer):
vhost = None
# If there are blocks listening plaintextishly on self.DEFAULT_LISTEN_PORT,
# choose the most name-matching one.
+
vhost = self.choose_redirect_vhost(domain, port)
if vhost is None:
logger.info("No matching insecure server blocks listening on port %s found.",
self.DEFAULT_LISTEN_PORT)
+ return
+
+ if vhost.ssl:
+ new_vhost = self.parser.duplicate_vhost(vhost,
+ only_directives=['listen', 'server_name'])
+
+ def _ssl_match_func(directive):
+ return 'ssl' in directive
+
+ def _no_ssl_match_func(directive):
+ return 'ssl' not in directive
+
+ # remove all ssl addresses from the new block
+ self.parser.remove_server_directives(new_vhost, 'listen', match_func=_ssl_match_func)
+
+ # remove all non-ssl addresses from the existing block
+ self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
+
+ vhost = new_vhost
+
+ if self._has_certbot_redirect(vhost):
+ logger.info("Traffic on port %s already redirecting to ssl in %s",
+ self.DEFAULT_LISTEN_PORT, vhost.filep)
+ elif vhost.has_redirect():
+ if not self._has_certbot_redirect_comment(vhost):
+ self._add_redirect_block(vhost, active=False)
+ logger.info("The appropriate server block is already redirecting "
+ "traffic. To enable redirect anyway, uncomment the "
+ "redirect lines in %s.", vhost.filep)
else:
- if self._has_certbot_redirect(vhost):
- logger.info("Traffic on port %s already redirecting to ssl in %s",
- self.DEFAULT_LISTEN_PORT, vhost.filep)
- elif vhost.has_redirect():
- if not self._has_certbot_redirect_comment(vhost):
- self._add_redirect_block(vhost, active=False)
- logger.info("The appropriate server block is already redirecting "
- "traffic. To enable redirect anyway, uncomment the "
- "redirect lines in %s.", vhost.filep)
- else:
- # Redirect plaintextish host to https
- self._add_redirect_block(vhost, active=True)
- logger.info("Redirecting all traffic on port %s to ssl in %s",
- self.DEFAULT_LISTEN_PORT, vhost.filep)
+ # Redirect plaintextish host to https
+ self._add_redirect_block(vhost, active=True)
+ logger.info("Redirecting all traffic on port %s to ssl in %s",
+ self.DEFAULT_LISTEN_PORT, vhost.filep)
def _enable_ocsp_stapling(self, domain, chain_path):
"""Include OCSP response in TLS handshake
@@ -809,6 +809,7 @@ class NginxConfigurator(common.Installer):
"""
super(NginxConfigurator, self).recovery_routine()
+ self.new_vhost = None
self.parser.load()
def revert_challenge_config(self):
@@ -818,6 +819,7 @@ class NginxConfigurator(common.Installer):
"""
self.revert_temporary_config()
+ self.new_vhost = None
self.parser.load()
def rollback_checkpoints(self, rollback=1):
@@ -830,6 +832,7 @@ class NginxConfigurator(common.Installer):
"""
super(NginxConfigurator, self).rollback_checkpoints(rollback)
+ self.new_vhost = None
self.parser.load()
###########################################################################
@@ -882,6 +885,11 @@ class NginxConfigurator(common.Installer):
self.restart()
+def _test_block_from_block(block):
+ test_block = nginxparser.UnspacedList(block)
+ parser.comment_directive(test_block, 0)
+ return test_block[:-1]
+
def nginx_restart(nginx_ctl, nginx_conf):
"""Restarts the Nginx Server.
diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/obj.py
index 5816c5571..f5ac5c2e3 100644
--- a/certbot-nginx/certbot_nginx/obj.py
+++ b/certbot-nginx/certbot_nginx/obj.py
@@ -205,7 +205,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
def contains_list(self, test):
"""Determine if raw server block contains test list at top level
"""
- for i in six.moves.range(0, len(self.raw) - len(test)):
+ for i in six.moves.range(0, len(self.raw) - len(test) + 1):
if self.raw[i:i + len(test)] == test:
return True
return False
@@ -220,6 +220,8 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
def ipv4_enabled(self):
"""Return true if one or more of the listen directives in vhost are IPv4
only"""
+ if self.addrs is None or len(self.addrs) == 0:
+ return True
for a in self.addrs:
if not a.ipv6:
return True
diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py
index 3eb6264aa..9f13bc59f 100644
--- a/certbot-nginx/certbot_nginx/parser.py
+++ b/certbot-nginx/certbot_nginx/parser.py
@@ -1,5 +1,6 @@
"""NginxParser is a member object of the NginxConfigurator class."""
import copy
+import functools
import glob
import logging
import os
@@ -294,6 +295,30 @@ class NginxParser(object):
:param bool replace: Whether to only replace existing directives
"""
+ self._modify_server_directives(vhost,
+ functools.partial(_add_directives, directives, replace))
+
+ def remove_server_directives(self, vhost, directive_name, match_func=None):
+ """Remove all directives of type directive_name.
+
+ :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
+ to remove directives from
+ :param string directive_name: The directive type to remove
+ :param callable match_func: Function of the directive that returns true for directives
+ to be deleted.
+ """
+ self._modify_server_directives(vhost,
+ functools.partial(_remove_directives, directive_name, match_func))
+
+ def _update_vhost_based_on_new_directives(self, vhost, directives_list):
+ new_server = self._get_included_directives(directives_list)
+ parsed_server = self.parse_server(new_server)
+ vhost.addrs = parsed_server['addrs']
+ vhost.ssl = parsed_server['ssl']
+ vhost.names = parsed_server['names']
+ vhost.raw = new_server
+
+ def _modify_server_directives(self, vhost, block_func):
filename = vhost.filep
try:
result = self.parsed[filename]
@@ -302,42 +327,52 @@ class NginxParser(object):
if not isinstance(result, list) or len(result) != 2:
raise errors.MisconfigurationError("Not a server block.")
result = result[1]
- _add_directives(result, directives, replace)
+ block_func(result)
- # update vhost based on new directives
- new_server = self._get_included_directives(result)
- parsed_server = self.parse_server(new_server)
- vhost.addrs = parsed_server['addrs']
- vhost.ssl = parsed_server['ssl']
- vhost.names = parsed_server['names']
- vhost.raw = new_server
+ self._update_vhost_based_on_new_directives(vhost, result)
except errors.MisconfigurationError as err:
raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err)))
- def create_new_vhost_from_default(self, vhost_template):
- """Duplicate the default vhost in the configuration files.
+ def duplicate_vhost(self, vhost_template, delete_default=False, only_directives=None):
+ """Duplicate the vhost in the configuration files.
:param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost
whose information we copy
+ :param bool delete_default: If we should remove default_server
+ from listen directives in the block.
+ :param list only_directives: If it exists, only duplicate the named directives. Only
+ looks at first level of depth; does not expand includes.
:returns: A vhost object for the newly created vhost
:rtype: :class:`~certbot_nginx.obj.VirtualHost`
"""
# TODO: https://github.com/certbot/certbot/issues/5185
# put it in the same file as the template, at the same level
+ new_vhost = copy.deepcopy(vhost_template)
+
enclosing_block = self.parsed[vhost_template.filep]
for index in vhost_template.path[:-1]:
enclosing_block = enclosing_block[index]
- new_location = vhost_template.path[-1] + 1
raw_in_parsed = copy.deepcopy(enclosing_block[vhost_template.path[-1]])
- enclosing_block.insert(new_location, raw_in_parsed)
- new_vhost = copy.deepcopy(vhost_template)
- new_vhost.path[-1] = new_location
- for addr in new_vhost.addrs:
- addr.default = False
- for directive in enclosing_block[new_vhost.path[-1]][1]:
- if len(directive) > 0 and directive[0] == 'listen' and 'default_server' in directive:
- del directive[directive.index('default_server')]
+
+ if only_directives is not None:
+ new_directives = nginxparser.UnspacedList([])
+ for directive in raw_in_parsed[1]:
+ if len(directive) > 0 and directive[0] in only_directives:
+ new_directives.append(directive)
+ raw_in_parsed[1] = new_directives
+
+ self._update_vhost_based_on_new_directives(new_vhost, new_directives)
+
+ enclosing_block.append(raw_in_parsed)
+ new_vhost.path[-1] = len(enclosing_block) - 1
+ if delete_default:
+ for addr in new_vhost.addrs:
+ addr.default = False
+ for directive in enclosing_block[new_vhost.path[-1]][1]:
+ if (len(directive) > 0 and directive[0] == 'listen'
+ and 'default_server' in directive):
+ del directive[directive.index('default_server')]
return new_vhost
def _parse_ssl_options(ssl_options):
@@ -486,7 +521,7 @@ def _is_ssl_on_directive(entry):
len(entry) == 2 and entry[0] == 'ssl' and
entry[1] == 'on')
-def _add_directives(block, directives, replace):
+def _add_directives(directives, replace, block):
"""Adds or replaces directives in a config block.
When replace=False, it's an error to try and add a directive that already
@@ -498,8 +533,9 @@ def _add_directives(block, directives, replace):
..todo :: Find directives that are in included files.
- :param list block: The block to replace in
:param list directives: The new directives.
+ :param bool replace: Described above.
+ :param list block: The block to replace in
"""
for directive in directives:
@@ -513,8 +549,12 @@ REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE])
COMMENT = ' managed by Certbot'
COMMENT_BLOCK = [' ', '#', COMMENT]
-def _comment_directive(block, location):
- """Add a comment to the end of the line at location."""
+def comment_directive(block, location):
+ """Add a ``#managed by Certbot`` comment to the end of the line at location.
+
+ :param list block: The block containing the directive to be commented
+ :param int location: The location within ``block`` of the directive to be commented
+ """
next_entry = block[location + 1] if location + 1 < len(block) else None
if isinstance(next_entry, list) and next_entry:
if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]:
@@ -551,6 +591,12 @@ def _comment_out_directive(block, location, include_location):
block[location] = new_dir[0] # set the now-single-line-comment directive back in place
+def _find_location(block, directive_name, match_func=None):
+ """Finds the index of the first instance of directive_name in block.
+ If no line exists, use None."""
+ return next((index for index, line in enumerate(block) \
+ if line and line[0] == directive_name and (match_func is None or match_func(line))), None)
+
def _add_directive(block, directive, replace):
"""Adds or replaces a single directive in a config block.
@@ -566,19 +612,12 @@ def _add_directive(block, directive, replace):
block.append(directive)
return
- def find_location(direc):
- """ Find the index of a config line where the name of the directive matches
- the name of the directive we want to add. If no line exists, use None.
- """
- return next((index for index, line in enumerate(block) \
- if line and line[0] == direc[0]), None)
-
- location = find_location(directive)
+ location = _find_location(block, directive[0])
if replace:
if location is not None:
block[location] = directive
- _comment_directive(block, location)
+ comment_directive(block, location)
return
# Append directive. Fail if the name is not a repeatable directive name,
# and there is already a copy of that directive with a different value
@@ -602,7 +641,7 @@ def _add_directive(block, directive, replace):
included_directives = _parse_ssl_options(directive[1])
for included_directive in included_directives:
- included_dir_loc = find_location(included_directive)
+ included_dir_loc = _find_location(block, included_directive[0])
included_dir_name = included_directive[0]
if not is_whitespace_or_comment(included_directive) \
and not can_append(included_dir_loc, included_dir_name):
@@ -614,10 +653,19 @@ def _add_directive(block, directive, replace):
if can_append(location, directive_name):
block.append(directive)
- _comment_directive(block, len(block) - 1)
+ comment_directive(block, len(block) - 1)
elif block[location] != directive:
raise errors.MisconfigurationError(err_fmt.format(directive, block[location]))
+def _remove_directives(directive_name, match_func, block):
+ """Removes directives of name directive_name from a config block if match_func matches.
+ """
+ while True:
+ location = _find_location(block, directive_name, match_func=match_func)
+ if location is None:
+ return
+ del block[location]
+
def _apply_global_addr_ssl(addr_to_ssl, parsed_server):
"""Apply global sslishness information to the parsed server block
"""
diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py
index 996bd238b..e708b159a 100644
--- a/certbot-nginx/certbot_nginx/tests/configurator_test.py
+++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py
@@ -443,10 +443,7 @@ class NginxConfiguratorTest(util.NginxTest):
def test_redirect_enhance(self):
# Test that we successfully add a redirect when there is
# a listen directive
- expected = [
- ['if', '($scheme', '!=', '"https")'],
- [['return', '301', 'https://$host$request_uri']]
- ]
+ expected = ['return', '301', 'https://$host$request_uri']
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
self.config.enhance("www.example.com", "redirect")
@@ -462,6 +459,35 @@ class NginxConfiguratorTest(util.NginxTest):
generated_conf = self.config.parser.parsed[migration_conf]
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
+ def test_split_for_redirect(self):
+ example_conf = self.config.parser.abs_path('sites-enabled/example.com')
+ self.config.deploy_cert(
+ "example.org",
+ "example/cert.pem",
+ "example/key.pem",
+ "example/chain.pem",
+ "example/fullchain.pem")
+ self.config.enhance("www.example.com", "redirect")
+ generated_conf = self.config.parser.parsed[example_conf]
+ self.assertEqual(
+ [[['server'], [
+ ['server_name', '.example.com'],
+ ['server_name', 'example.*'], [],
+ ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'],
+ ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'],
+ ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'],
+ ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'],
+ ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'],
+ [], []]],
+ [['server'], [
+ ['listen', '69.50.225.155:9000'],
+ ['listen', '127.0.0.1'],
+ ['server_name', '.example.com'],
+ ['server_name', 'example.*'],
+ ['return', '301', 'https://$host$request_uri'], ['#', ' managed by Certbot'],
+ [], []]]],
+ generated_conf)
+
@mock.patch('certbot_nginx.obj.VirtualHost.contains_list')
@mock.patch('certbot_nginx.obj.VirtualHost.has_redirect')
def test_certbot_redirect_exists(self, mock_has_redirect, mock_contains_list):
@@ -494,9 +520,38 @@ class NginxConfiguratorTest(util.NginxTest):
generated_conf = self.config.parser.parsed[example_conf]
expected = [
['#', ' Redirect non-https traffic to https'],
- ['#', ' if ($scheme != "https") {'],
- ['#', ' return 301 https://$host$request_uri;'],
- ['#', ' } # managed by Certbot']
+ ['#', ' return 301 https://$host$request_uri;'],
+ ]
+ for line in expected:
+ self.assertTrue(util.contains_at_depth(generated_conf, line, 2))
+
+ @mock.patch('certbot_nginx.obj.VirtualHost.contains_list')
+ @mock.patch('certbot_nginx.obj.VirtualHost.has_redirect')
+ def test_non_certbot_redirect_exists_has_ssl_copy(self, mock_has_redirect, mock_contains_list):
+ # Test that we add a redirect as a comment if there is already a
+ # redirect-class statement in the block that isn't managed by certbot
+ example_conf = self.config.parser.abs_path('sites-enabled/example.com')
+
+ self.config.deploy_cert(
+ "example.org",
+ "example/cert.pem",
+ "example/key.pem",
+ "example/chain.pem",
+ "example/fullchain.pem")
+
+ # Has a non-Certbot redirect, and has no existing comment
+ mock_contains_list.return_value = False
+ mock_has_redirect.return_value = True
+ with mock.patch("certbot_nginx.configurator.logger") as mock_logger:
+ self.config.enhance("www.example.com", "redirect")
+ self.assertEqual(mock_logger.info.call_args[0][0],
+ "The appropriate server block is already redirecting "
+ "traffic. To enable redirect anyway, uncomment the "
+ "redirect lines in %s.")
+ generated_conf = self.config.parser.parsed[example_conf]
+ expected = [
+ ['#', ' Redirect non-https traffic to https'],
+ ['#', ' return 301 https://$host$request_uri;'],
]
for line in expected:
self.assertTrue(util.contains_at_depth(generated_conf, line, 2))
@@ -704,14 +759,18 @@ class NginxConfiguratorTest(util.NginxTest):
self.config.parser.load()
- expected = [
- ['if', '($scheme', '!=', '"https")'],
- [['return', '301', 'https://$host$request_uri']]
- ]
+ expected = ['return', '301', 'https://$host$request_uri']
generated_conf = self.config.parser.parsed[default_conf]
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
+ @mock.patch('certbot.reverter.logger')
+ @mock.patch('certbot_nginx.parser.NginxParser.load')
+ def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger):
+ self.config.recovery_routine()
+ self.config.revert_challenge_config()
+ self.config.rollback_checkpoints()
+ self.assertTrue(mock_parser_load.call_count == 3)
class InstallSslOptionsConfTest(util.NginxTest):
"""Test that the options-ssl-nginx.conf file is installed and updated properly."""
diff --git a/certbot-nginx/certbot_nginx/tests/obj_test.py b/certbot-nginx/certbot_nginx/tests/obj_test.py
index ba136bb78..92cb0e086 100644
--- a/certbot-nginx/certbot_nginx/tests/obj_test.py
+++ b/certbot-nginx/certbot_nginx/tests/obj_test.py
@@ -171,8 +171,8 @@ class VirtualHostTest(unittest.TestCase):
def test_contains_list(self):
from certbot_nginx.obj import VirtualHost
from certbot_nginx.obj import Addr
- from certbot_nginx.configurator import TEST_REDIRECT_BLOCK
- test_needle = TEST_REDIRECT_BLOCK
+ from certbot_nginx.configurator import REDIRECT_BLOCK, _test_block_from_block
+ test_needle = _test_block_from_block(REDIRECT_BLOCK)
test_haystack = [['listen', '80'], ['root', '/var/www/html'],
['index', 'index.html index.htm index.nginx-debian.html'],
['server_name', 'two.functorkitten.xyz'], ['listen', '443 ssl'],
@@ -181,9 +181,7 @@ class VirtualHostTest(unittest.TestCase):
['#', ' managed by Certbot'],
['ssl_certificate_key', '/etc/letsencrypt/live/two.functorkitten.xyz/privkey.pem'],
['#', ' managed by Certbot'],
- [['if', '($scheme', '!=', '"https")'],
- [['return', '301', 'https://$host$request_uri']]
- ],
+ ['return', '301', 'https://$host$request_uri'],
['#', ' managed by Certbot'], []]
vhost_haystack = VirtualHost(
"filp",
diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py
index ca5de7ff6..e21acb8ea 100644
--- a/certbot-nginx/certbot_nginx/tests/parser_test.py
+++ b/certbot-nginx/certbot_nginx/tests/parser_test.py
@@ -334,9 +334,9 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
["\n", "a", " ", "b", "\n"],
["c", " ", "d"],
["\n", "e", " ", "f"]])
- from certbot_nginx.parser import _comment_directive, COMMENT_BLOCK
- _comment_directive(block, 1)
- _comment_directive(block, 0)
+ from certbot_nginx.parser import comment_directive, COMMENT_BLOCK
+ comment_directive(block, 1)
+ comment_directive(block, 0)
self.assertEqual(block.spaced, [
["\n", "a", " ", "b", "\n"],
COMMENT_BLOCK,
@@ -406,12 +406,12 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
])
self.assertTrue(server['ssl'])
- def test_create_new_vhost_from_default(self):
+ def test_duplicate_vhost(self):
nparser = parser.NginxParser(self.config_path)
vhosts = nparser.get_vhosts()
default = [x for x in vhosts if 'default' in x.filep][0]
- new_vhost = nparser.create_new_vhost_from_default(default)
+ new_vhost = nparser.duplicate_vhost(default, delete_default=True)
nparser.filedump(ext='')
# check properties of new vhost
diff --git a/certbot-nginx/certbot_nginx/tls_sni_01.py b/certbot-nginx/certbot_nginx/tls_sni_01.py
index 7f597ac4a..eca198bfe 100644
--- a/certbot-nginx/certbot_nginx/tls_sni_01.py
+++ b/certbot-nginx/certbot_nginx/tls_sni_01.py
@@ -55,7 +55,7 @@ class NginxTlsSni01(common.TLSSNI01):
self.configurator.config.tls_sni_01_port)
for achall in self.achalls:
- vhost = self.configurator.choose_vhost(achall.domain, raise_if_no_match=False)
+ vhost = self.configurator.choose_vhost(achall.domain, create_if_no_match=True)
if vhost is not None and vhost.addrs:
addresses.append(list(vhost.addrs))
diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py
index f3919413d..2ad7aaf08 100644
--- a/certbot-nginx/setup.py
+++ b/certbot-nginx/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
-version = '0.20.0.dev0'
+version = '0.21.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
diff --git a/certbot/__init__.py b/certbot/__init__.py
index 231a0f5f5..cbea701ee 100644
--- a/certbot/__init__.py
+++ b/certbot/__init__.py
@@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
-__version__ = '0.20.0.dev0'
+__version__ = '0.21.0.dev0'
diff --git a/certbot/main.py b/certbot/main.py
index 9e2850891..72af7fbba 100644
--- a/certbot/main.py
+++ b/certbot/main.py
@@ -43,7 +43,15 @@ logger = logging.getLogger(__name__)
def _suggest_donation_if_appropriate(config):
- """Potentially suggest a donation to support Certbot."""
+ """Potentially suggest a donation to support Certbot.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :returns: `None`
+ :rtype: None
+
+ """
assert config.verb != "renew"
if config.staging:
# --dry-run implies --staging
@@ -55,6 +63,15 @@ def _suggest_donation_if_appropriate(config):
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
def _report_successful_dry_run(config):
+ """Reports on successful dry run
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :returns: `None`
+ :rtype: None
+
+ """
reporter_util = zope.component.getUtility(interfaces.IReporter)
assert config.verb != "renew"
reporter_util.add_message("The dry run was successful.",
@@ -68,8 +85,23 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
then performs that action. Includes calls to hooks, various reports,
checks, and requests for user input.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param domains: List of domain names to get a certificate. Defaults to `None`
+ :type domains: `list` of `str`
+
+ :param certname: Name of new certificate. Defaults to `None`
+ :type certname: str
+
+ :param lineage: Certificate lineage object. Defaults to `None`
+ :type lineage: storage.RenewableCert
+
:returns: the issued certificate or `None` if doing a dry run
- :rtype: `storage.RenewableCert` or `None`
+ :rtype: storage.RenewableCert or None
+
+ :raises errors.Error: if certificate could not be obtained
+
"""
hooks.pre_hook(config)
try:
@@ -96,11 +128,18 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
def _handle_subset_cert_request(config, domains, cert):
"""Figure out what to do if a previous cert had a subset of the names now requested
- :param storage.RenewableCert cert:
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param domains: List of domain names
+ :type domains: `list` of `str`
+
+ :param cert: Certificate object
+ :type cert: storage.RenewableCert
:returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
action can be: "newcert" | "renew" | "reinstall"
- :rtype: tuple
+ :rtype: `tuple` of `str`
"""
existing = ", ".join(cert.names())
@@ -137,11 +176,15 @@ def _handle_subset_cert_request(config, domains, cert):
def _handle_identical_cert_request(config, lineage):
"""Figure out what to do if a lineage has the same names as a previously obtained one
- :param storage.RenewableCert lineage:
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param lineage: Certificate lineage object
+ :type lineage: storage.RenewableCert
:returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
action can be: "newcert" | "renew" | "reinstall"
- :rtype: tuple
+ :rtype: `tuple` of `str`
"""
if not lineage.ensure_deployed():
@@ -186,11 +229,18 @@ def _find_lineage_for_domains(config, domains):
the client run if the user chooses to cancel the operation when
prompted).
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param domains: List of domain names
+ :type domains: `list` of `str`
+
:returns: Two-element tuple containing desired new-certificate behavior as
a string token ("reinstall", "renew", or "newcert"), plus either
- a RenewableCert instance or None if renewal shouldn't occur.
+ a RenewableCert instance or `None` if renewal shouldn't occur.
+ :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None`
- :raises .Error: If the user would like to rerun the client again.
+ :raises errors.Error: If the user would like to rerun the client again.
"""
# Considering the possibility that the requested certificate is
@@ -214,9 +264,20 @@ def _find_lineage_for_domains(config, domains):
def _find_cert(config, domains, certname):
"""Finds an existing certificate object given domains and/or a certificate name.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param domains: List of domain names
+ :type domains: `list` of `str`
+
+ :param certname: Name of certificate
+ :type certname: str
+
:returns: Two-element tuple of a boolean that indicates if this function should be
followed by a call to fetch a certificate from the server, and either a
RenewableCert instance or None.
+ :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None`
+
"""
action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname)
if action == "reinstall":
@@ -226,11 +287,22 @@ def _find_cert(config, domains, certname):
def _find_lineage_for_domains_and_certname(config, domains, certname):
"""Find appropriate lineage based on given domains and/or certname.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param domains: List of domain names
+ :type domains: `list` of `str`
+
+ :param certname: Name of certificate
+ :type certname: str
+
:returns: Two-element tuple containing desired new-certificate behavior as
a string token ("reinstall", "renew", or "newcert"), plus either
- a RenewableCert instance or None if renewal shouldn't occur.
+ a RenewableCert instance or None if renewal should not occur.
- :raises .Error: If the user would like to rerun the client again.
+ :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None`
+
+ :raises errors.Error: If the user would like to rerun the client again.
"""
if not certname:
@@ -253,18 +325,57 @@ def _find_lineage_for_domains_and_certname(config, domains, certname):
"Use -d to specify domains, or run certbot --certificates to see "
"possible certificate names.".format(certname))
+def _get_added_removed(after, before):
+ """Get lists of items removed from `before`
+ and a lists of items added to `after`
+ """
+ added = list(set(after) - set(before))
+ removed = list(set(before) - set(after))
+ added.sort()
+ removed.sort()
+ return added, removed
+
+def _format_list(character, strings):
+ """Format list with given character
+ """
+ formatted = "{br}{ch} " + "{br}{ch} ".join(strings)
+ return formatted.format(
+ ch=character,
+ br=os.linesep
+ )
+
def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):
"""Ask user to confirm update cert certname to contain new_domains.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param new_domains: List of new domain names
+ :type new_domains: `list` of `str`
+
+ :param certname: Name of certificate
+ :type certname: str
+
+ :param old_domains: List of old domain names
+ :type old_domains: `list` of `str`
+
+ :returns: None
+ :rtype: None
+
+ :raises errors.ConfigurationError: if cert name and domains mismatch
+
"""
if config.renew_with_new_domains:
return
- msg = ("You are updating certificate {0} to include domains: {1}{br}{br}"
- "It previously included domains: {2}{br}{br}"
+ added, removed = _get_added_removed(new_domains, old_domains)
+
+ msg = ("You are updating certificate {0} to include new domain(s): {1}{br}{br}"
+ "You are also removing previously included domain(s): {2}{br}{br}"
"Did you intend to make this change?".format(
certname,
- ", ".join(new_domains),
- ", ".join(old_domains),
+ _format_list("+", added),
+ _format_list("-", removed),
br=os.linesep))
obj = zope.component.getUtility(interfaces.IDisplay)
if not obj.yesno(msg, "Update cert", "Cancel", default=True):
@@ -272,6 +383,19 @@ def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):
def _find_domains_or_certname(config, installer):
"""Retrieve domains and certname from config or user input.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param installer: Installer object
+ :type installer: interfaces.IInstaller
+
+
+ :returns: Two-part tuple of domains and certname
+ :rtype: `tuple` of list of `str` and `str`
+
+ :raises errors.Error: Usage message, if parameters are not used correctly
+
"""
domains = None
certname = config.certname
@@ -299,9 +423,17 @@ def _find_domains_or_certname(config, installer):
def _report_new_cert(config, cert_path, fullchain_path, key_path=None):
"""Reports the creation of a new certificate to the user.
- :param str cert_path: path to cert
- :param str fullchain_path: path to full chain
- :param str key_path: path to private key, if available
+ :param cert_path: path to certificate
+ :type cert_path: str
+
+ :param fullchain_path: path to full chain
+ :type fullchain_path: str
+
+ :param key_path: path to private key, if available
+ :type key_path: str
+
+ :returns: `None`
+ :rtype: None
"""
if config.dry_run:
@@ -337,14 +469,14 @@ def _determine_account(config):
if ``config.account`` is ``None``, it will be updated based on the
user input. Same for ``config.email``.
- :param argparse.Namespace config: CLI arguments
- :param certbot.interface.IConfig config: Configuration object
- :param .AccountStorage account_storage: Account storage.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
:returns: Account and optionally ACME client API (biproduct of new
registration).
- :rtype: `tuple` of `certbot.account.Account` and
- `acme.client.Client`
+ :rtype: tuple of :class:`certbot.account.Account` and :class:`acme.client.Client`
+
+ :raises errors.Error: If unable to register an account with ACME server
"""
account_storage = account.AccountFileStorage(config)
@@ -392,9 +524,13 @@ def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-b
deleting happens automatically, unless if both `--cert-name` and `--cert-path` were
specified with conflicting values.
- :param `configuration.NamespaceConfig` config: parsed command line arguments
+ :param config: parsed command line arguments
+ :type config: interfaces.IConfig
- :raises `error.Errors`: If anything goes wrong, including bad user input, if an overlapping
+ :returns: `None`
+ :rtype: None
+
+ :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping
archive dir is found for the specified lineage, etc ...
"""
display = zope.component.getUtility(interfaces.IDisplay)
@@ -474,6 +610,20 @@ def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-b
def _init_le_client(config, authenticator, installer):
+ """Initialize Let's Encrypt Client
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param authenticator: Acme authentication handler
+ :type authenticator: interfaces.IAuthenticator
+ :param installer: Installer object
+ :type installer: interfaces.IInstaller
+
+ :returns: client: Client object
+ :rtype: client.Client
+
+ """
if authenticator is not None:
# if authenticator was given, then we will need account...
acc, acme = _determine_account(config)
@@ -487,7 +637,18 @@ def _init_le_client(config, authenticator, installer):
def unregister(config, unused_plugins):
- """Deactivate account on server"""
+ """Deactivate account on server
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
account_storage = account.AccountFileStorage(config)
accounts = account_storage.find_all()
reporter_util = zope.component.getUtility(interfaces.IReporter)
@@ -516,8 +677,18 @@ def unregister(config, unused_plugins):
def register(config, unused_plugins):
- """Create or modify accounts on the server."""
+ """Create or modify accounts on the server.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None` or a string indicating and error
+ :rtype: None or str
+
+ """
# Portion of _determine_account logic to see whether accounts already
# exist or not.
account_storage = account.AccountFileStorage(config)
@@ -558,6 +729,24 @@ def register(config, unused_plugins):
add_msg("Your e-mail address was updated to {0}.".format(config.email))
def _install_cert(config, le_client, domains, lineage=None):
+ """Install a cert
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param le_client: Client object
+ :type le_client: client.Client
+
+ :param plugins: List of domains
+ :type plugins: `list` of `str`
+
+ :param lineage: Certificate lineage object. Defaults to `None`
+ :type lineage: storage.RenewableCert
+
+ :returns: `None`
+ :rtype: None
+
+ """
path_provider = lineage if lineage else config
assert path_provider.cert_path is not None
@@ -566,7 +755,18 @@ def _install_cert(config, le_client, domains, lineage=None):
le_client.enhance_config(domains, path_provider.chain_path)
def install(config, plugins):
- """Install a previously obtained cert in a server."""
+ """Install a previously obtained cert in a server.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
# XXX: Update for renewer/RenewableCert
# FIXME: be consistent about whether errors are raised or returned from
# this function ...
@@ -582,7 +782,18 @@ def install(config, plugins):
def plugins_cmd(config, plugins):
- """List server software plugins."""
+ """List server software plugins.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
logger.debug("Expected interfaces: %s", config.ifaces)
ifaces = [] if config.ifaces is None else config.ifaces
@@ -610,7 +821,18 @@ def plugins_cmd(config, plugins):
def rollback(config, plugins):
- """Rollback server configuration changes made during install."""
+ """Rollback server configuration changes made during install.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
client.rollback(config.installer, config.checkpoints, config, plugins)
@@ -619,6 +841,15 @@ def config_changes(config, unused_plugins):
View checkpoints and associated configuration changes.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
"""
client.view_config_changes(config, num=config.num)
@@ -627,6 +858,16 @@ def update_symlinks(config, unused_plugins):
Use the information in the config file to make symlinks point to
the correct archive directory.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
"""
cert_manager.update_live_symlinks(config)
@@ -635,6 +876,16 @@ def rename(config, unused_plugins):
Use the information in the config file to rename an existing
lineage.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
"""
cert_manager.rename_lineage(config)
@@ -643,16 +894,47 @@ def delete(config, unused_plugins):
Use the information in the config file to delete an existing
lineage.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
"""
cert_manager.delete(config)
def certificates(config, unused_plugins):
"""Display information about certs configured with Certbot
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
"""
cert_manager.certificates(config)
def revoke(config, unused_plugins): # TODO: coop with renewal config
- """Revoke a previously obtained certificate."""
+ """Revoke a previously obtained certificate.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None` or string indicating error in case of error
+ :rtype: None or str
+
+ """
# For user-agent construction
config.installer = config.authenticator = "None"
if config.key_path is not None: # revocation by cert key
@@ -678,7 +960,18 @@ def revoke(config, unused_plugins): # TODO: coop with renewal config
def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals
- """Obtain a certificate and install."""
+ """Obtain a certificate and install.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
# TODO: Make run as close to auth + install as possible
# Possible difficulties: config.csr was hacked into auth
try:
@@ -718,6 +1011,16 @@ def _csr_get_and_save_cert(config, le_client):
This works differently in the CSR case (for now) because we don't
have the privkey, and therefore can't construct the files for a lineage.
So we just save the cert & chain to disk :/
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param client: Client object
+ :type client: client.Client
+
+ :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files
+ :rtype: `tuple` of `str`
+
"""
csr, _ = config.actual_csr
certr, chain = le_client.obtain_certificate_from_csr(config.domains, csr)
@@ -730,7 +1033,23 @@ def _csr_get_and_save_cert(config, le_client):
return cert_path, fullchain_path
def renew_cert(config, plugins, lineage):
- """Renew & save an existing cert. Do not install it."""
+ """Renew & save an existing cert. Do not install it.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :param lineage: Certificate lineage object
+ :type lineage: storage.RenewableCert
+
+ :returns: `None`
+ :rtype: None
+
+ :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass
+
+ """
try:
# installers are used in auth mode to determine domain names
installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
@@ -757,8 +1076,20 @@ def renew_cert(config, plugins, lineage):
def certonly(config, plugins):
"""Authenticate & obtain cert, but do not install it.
- This implements the 'certonly' subcommand."""
+ This implements the 'certonly' subcommand.
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param plugins: List of plugins
+ :type plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ :raises errors.Error: If specified plugin could not be used
+
+ """
# SETUP: Select plugins and construct a client instance
try:
# installers are used in auth mode to determine domain names
@@ -792,7 +1123,18 @@ def certonly(config, plugins):
_suggest_donation_if_appropriate(config)
def renew(config, unused_plugins):
- """Renew previously-obtained certificates."""
+ """Renew previously-obtained certificates.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :param unused_plugins: List of plugins (deprecated)
+ :type unused_plugins: `list` of `str`
+
+ :returns: `None`
+ :rtype: None
+
+ """
try:
renewal.handle_renewal_request(config)
finally:
@@ -800,7 +1142,15 @@ def renew(config, unused_plugins):
def make_or_verify_needed_dirs(config):
- """Create or verify existence of config, work, and hook directories."""
+ """Create or verify existence of config, work, and hook directories.
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :returns: `None`
+ :rtype: None
+
+ """
util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), config.strict_permissions)
util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,
@@ -816,7 +1166,15 @@ def make_or_verify_needed_dirs(config):
def set_displayer(config):
- """Set the displayer"""
+ """Set the displayer
+
+ :param config: Configuration object
+ :type config: interfaces.IConfig
+
+ :returns: `None`
+ :rtype: None
+
+ """
if config.quiet:
config.noninteractive_mode = True
displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w"))
@@ -829,7 +1187,14 @@ def set_displayer(config):
def main(cli_args=sys.argv[1:]):
- """Command line argument parsing and main script execution."""
+ """Command line argument parsing and main script execution.
+
+ :returns: result of requested command
+
+ :raises errors.Error: OS errors triggered by wrong permissions
+ :raises errors.Error: error if plugin command is not supported
+
+ """
log.pre_arg_parse_setup()
plugins = plugins_disco.PluginsRegistry.find_all()
@@ -850,6 +1215,9 @@ def main(cli_args=sys.argv[1:]):
# Let plugins_cmd be run as un-privileged user.
if config.func != plugins_cmd:
raise
+ if sys.version_info[:2] == (3, 3):
+ logger.warning("Python 3.3 support will be dropped in the next release "
+ "of Certbot - please upgrade your Python version.")
set_displayer(config)
diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py
index 45e5db1df..1f690df26 100644
--- a/certbot/tests/main_test.py
+++ b/certbot/tests/main_test.py
@@ -356,7 +356,7 @@ class DeleteIfAppropriateTest(unittest.TestCase):
mock_cert_path_for_cert_name.return_value = "/some/reasonable/path"
mock_overlapping_archive_dirs.return_value = False
self._call(config)
- mock_delete.assert_called_once()
+ self.assertEqual(mock_delete.call_count, 1)
# pylint: disable=too-many-arguments
@mock.patch('certbot.storage.renewal_file_for_certname')
@@ -375,7 +375,7 @@ class DeleteIfAppropriateTest(unittest.TestCase):
mock_cert_path_to_lineage.return_value = "example.com"
mock_overlapping_archive_dirs.return_value = False
self._call(config)
- mock_delete.assert_called_once()
+ self.assertEqual(mock_delete.call_count, 1)
# pylint: disable=too-many-arguments
@mock.patch('certbot.storage.renewal_file_for_certname')
@@ -396,7 +396,7 @@ class DeleteIfAppropriateTest(unittest.TestCase):
mock_full_archive_dir.return_value = ""
mock_match_and_check_overlaps.return_value = ""
self._call(config)
- mock_delete.assert_called_once()
+ self.assertEqual(mock_delete.call_count, 1)
# pylint: disable=too-many-arguments
@mock.patch('certbot.storage.renewal_file_for_certname')
@@ -415,7 +415,7 @@ class DeleteIfAppropriateTest(unittest.TestCase):
mock_cert_path_to_lineage.return_value = config.certname
mock_overlapping_archive_dirs.return_value = False
self._call(config)
- mock_delete.assert_called_once()
+ self.assertEqual(mock_delete.call_count, 1)
# pylint: disable=too-many-arguments
@mock.patch('certbot.cert_manager.match_and_check_overlaps')
@@ -442,7 +442,7 @@ class DeleteIfAppropriateTest(unittest.TestCase):
util_mock = mock_get_utility()
util_mock.menu.return_value = (display_util.OK, 0)
self._call(config)
- mock_delete.assert_called_once()
+ self.assertEqual(mock_delete.call_count, 1)
# pylint: disable=too-many-arguments
@mock.patch('certbot.cert_manager.match_and_check_overlaps')
diff --git a/certbot/util.py b/certbot/util.py
index 30de0c157..b7e60a225 100644
--- a/certbot/util.py
+++ b/certbot/util.py
@@ -342,9 +342,9 @@ def get_os_info_ua(filepath="/etc/os-release"):
"""
if os.path.isfile(filepath):
- os_ua = _get_systemd_os_release_var("PRETTY_NAME", filepath=filepath)
+ os_ua = get_var_from_file("PRETTY_NAME", filepath=filepath)
if not os_ua:
- os_ua = _get_systemd_os_release_var("NAME", filepath=filepath)
+ os_ua = get_var_from_file("NAME", filepath=filepath)
if os_ua:
return os_ua
@@ -361,8 +361,8 @@ def get_systemd_os_info(filepath="/etc/os-release"):
:rtype: `tuple` of `str`
"""
- os_name = _get_systemd_os_release_var("ID", filepath=filepath)
- os_version = _get_systemd_os_release_var("VERSION_ID", filepath=filepath)
+ os_name = get_var_from_file("ID", filepath=filepath)
+ os_version = get_var_from_file("VERSION_ID", filepath=filepath)
return (os_name, os_version)
@@ -377,10 +377,10 @@ def get_systemd_os_like(filepath="/etc/os-release"):
:rtype: `list` of `str`
"""
- return _get_systemd_os_release_var("ID_LIKE", filepath).split(" ")
+ return get_var_from_file("ID_LIKE", filepath).split(" ")
-def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
+def get_var_from_file(varname, filepath="/etc/os-release"):
"""
Get single value from systemd /etc/os-release
@@ -405,7 +405,7 @@ def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
def _normalize_string(orig):
"""
- Helper function for _get_systemd_os_release_var() to remove quotes
+ Helper function for get_var_from_file() to remove quotes
and whitespaces
"""
return orig.replace('"', '').replace("'", "").strip()
diff --git a/docs/cli-help.txt b/docs/cli-help.txt
index 6b43fd0a2..abaa95b9b 100644
--- a/docs/cli-help.txt
+++ b/docs/cli-help.txt
@@ -107,7 +107,7 @@ optional arguments:
case, and to know when to deprecate support for past
Python versions and flags. If you wish to hide this
information from the Let's Encrypt server, set this to
- "". (default: CertbotACMEClient/0.19.0 (certbot;
+ "". (default: CertbotACMEClient/0.20.0 (certbot;
Ubuntu 16.04.3 LTS) Authenticator/XXX Installer/YYY
(SUBCOMMAND; flags: FLAGS) Py/2.7.12). The flags
encoded in the user agent are: --duplicate, --force-
@@ -121,7 +121,7 @@ optional arguments:
(Example: Foo-Wrapper/1.0) (default: None)
automation:
- Arguments for automating execution & other tweaks
+ Flags for automating execution & other tweaks
--keep-until-expiring, --keep, --reinstall
If the requested certificate matches an existing
@@ -228,7 +228,7 @@ testing:
False)
paths:
- Arguments changing execution paths & servers
+ Flags for changing execution paths & servers
--cert-path CERT_PATH
Path to where certificate is saved (with auth --csr),
diff --git a/docs/using.rst b/docs/using.rst
index 4fd0b5ec8..ab4670052 100644
--- a/docs/using.rst
+++ b/docs/using.rst
@@ -106,7 +106,7 @@ specified ``--webroot-path``. So, for instance,
::
- certbot certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net
+ certbot certonly --webroot -w /var/www/example -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net
would obtain a single certificate for all of those names, using the
``/var/www/example`` webroot directory for the first two, and
diff --git a/letsencrypt-auto b/letsencrypt-auto
index 25f2ce889..444bee1b9 100755
--- a/letsencrypt-auto
+++ b/letsencrypt-auto
@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
-LE_AUTO_VERSION="0.19.0"
+LE_AUTO_VERSION="0.20.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -1062,9 +1062,10 @@ zope.interface==4.1.3 \
--hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \
--hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \
--hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392
-mock==2.0.0 \
- --hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \
- --hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba
+# Using an older version of mock here prevents regressions of #5276.
+mock==1.3.0 \
+ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \
+ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6
# Contains the requirements for the letsencrypt package.
#
@@ -1077,18 +1078,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
-certbot==0.19.0 \
- --hash=sha256:3207ee5319bfc37e855c25a43148275fcfb37869eefde9087405012049734a20 \
- --hash=sha256:a7230791dff5d085738119fc22d88ad9d8a35d0b6a3d67806fe33990c7c79d53
-acme==0.19.0 \
- --hash=sha256:c612eafe234d722d97bb5d3dbc49e5522f44be29611f7577954eb893e5c2d6de \
- --hash=sha256:1fa23d64d494aaf001e6fe857c461fcfff10f75a1c2c35ec831447f641e1e822
-certbot-apache==0.19.0 \
- --hash=sha256:fadb28b33bfabc85cdb962b5b149bef58b98f0606b78581db7895fe38323f37c \
- --hash=sha256:70306ca2d5be7f542af68d46883c0ae39527cf202f17ef92cd256fb0bc3f1619
-certbot-nginx==0.19.0 \
- --hash=sha256:4909cb3db49919fb35590793cac28e1c0b6dbd29cbedf887b9106e5fcef5362c \
- --hash=sha256:cb5a224a3f277092555c25096d1678fc735306fd3a43447649ebe524c7ca79e1
+certbot==0.20.0 \
+ --hash=sha256:c6b6bd288700898d1eb31a65b605e3a5fc10f1e3213ce468207d76a2decb9d35 \
+ --hash=sha256:cabf505b64fb400c4239dcdbaeb882079477eb6a8442268596a8791b9e34de88
+acme==0.20.0 \
+ --hash=sha256:8b0cee192c0d76d6f4045bdb14b3cfd29d9720e0dad2046794a2a555f1eaccb7 \
+ --hash=sha256:45121aed6c8cc2f31896ac1083068dfdeb613f3edeff9576dc0d10632ea5a3d5
+certbot-apache==0.20.0 \
+ --hash=sha256:f7e4dbc154d2e9d1461118b6dd3dbd16f6892da468f060eeaa162aff673347e2 \
+ --hash=sha256:0ba499706451ffbccb172bcf93d6ef4c6cc8599157077a4fa6dfbe5a83c7921f
+certbot-nginx==0.20.0 \
+ --hash=sha256:b6e372e8740b20dd9bd63837646157ac97b3c9a65affd3954571b8e872ae9ecf \
+ --hash=sha256:6379fdf20d9a7651fe30bb8d4b828cbea178cc263d7af5a380fc4508d793b9ae
UNLIKELY_EOF
# -------------------------------------------------------------------------
diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc
index 834358464..eeab78cd6 100644
--- a/letsencrypt-auto-source/certbot-auto.asc
+++ b/letsencrypt-auto-source/certbot-auto.asc
@@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
-iQEcBAABCAAGBQJZ1TJAAAoJEE0XyZXNl3XyWjAIAKxR5v0qbSyOEwM1LrSoLqud
-V3KkyEUlMq7IPHxoPKXbqUrIi4eZuhpJz+84LtVJe4ZQ6HYP9lPogX+PtmWW7dyO
-YerxA2rUVGB9rFZofZYwTuJyvO5Nc0aDyp1FHHPg/5khWWhhhxKpWqqG3zT01+Vf
-W8Lvvn7vr7sjTvxBdqHQ3z3hlUY62P2IKui9C5un5ozlSQpDrWh3Thi9r6CxbASL
-/r1PQ6EfnNdPAizVrJWe5iUd0Nzj7VMkFwZ02A3OlOUvrHGVb1H6oj0S1lZ8LEpj
-awOTys8PVBQ3vW2qbAL3Zk7Lr+CGfVfmoWC9TQEKiSN1woYFrFD39S527vB1onc=
-=Meks
+iQEcBAABCAAGBQJaKHMlAAoJEE0XyZXNl3Xy6OEH/iPg6D6+zco4NHMwxYIcTWVt
+XE4u3CjuLcEVsvEnJYNSA48NHyi9rIqMHd+IneLU+lCG2D7eBsisNNyVPIgHktTf
+p9i0WoZB+axe1glv9FJSZvjvr2d/ic4/wYHBF1c+szb9p8Z7o5Lhqa9/gtLJ/SZX
+OGU0wok4hPIB6emq5zvmi/+r1AiOECXE26lZ0STp6wDkvz+ahTJSk6UaPCDY+Az4
+X2VmnRSks/gk7Q8cloFnyiPXyFMQHdGIBRrIXsSix90QqmNUF7iYb8sbHksU23EI
+/LmIwSJlDm6KNOO2nllBB/uIg2ki7g0z7R4uf7XF4im+P95PAL/tQQ45lVj8DXE=
+=Is56
-----END PGP SIGNATURE-----
diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto
index 215b684cf..8d2e8a6b6 100755
--- a/letsencrypt-auto-source/letsencrypt-auto
+++ b/letsencrypt-auto-source/letsencrypt-auto
@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
-LE_AUTO_VERSION="0.20.0.dev0"
+LE_AUTO_VERSION="0.21.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -1062,9 +1062,10 @@ zope.interface==4.1.3 \
--hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \
--hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \
--hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392
-mock==2.0.0 \
- --hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \
- --hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba
+# Using an older version of mock here prevents regressions of #5276.
+mock==1.3.0 \
+ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \
+ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6
# Contains the requirements for the letsencrypt package.
#
@@ -1077,18 +1078,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
-certbot==0.19.0 \
- --hash=sha256:3207ee5319bfc37e855c25a43148275fcfb37869eefde9087405012049734a20 \
- --hash=sha256:a7230791dff5d085738119fc22d88ad9d8a35d0b6a3d67806fe33990c7c79d53
-acme==0.19.0 \
- --hash=sha256:c612eafe234d722d97bb5d3dbc49e5522f44be29611f7577954eb893e5c2d6de \
- --hash=sha256:1fa23d64d494aaf001e6fe857c461fcfff10f75a1c2c35ec831447f641e1e822
-certbot-apache==0.19.0 \
- --hash=sha256:fadb28b33bfabc85cdb962b5b149bef58b98f0606b78581db7895fe38323f37c \
- --hash=sha256:70306ca2d5be7f542af68d46883c0ae39527cf202f17ef92cd256fb0bc3f1619
-certbot-nginx==0.19.0 \
- --hash=sha256:4909cb3db49919fb35590793cac28e1c0b6dbd29cbedf887b9106e5fcef5362c \
- --hash=sha256:cb5a224a3f277092555c25096d1678fc735306fd3a43447649ebe524c7ca79e1
+certbot==0.20.0 \
+ --hash=sha256:c6b6bd288700898d1eb31a65b605e3a5fc10f1e3213ce468207d76a2decb9d35 \
+ --hash=sha256:cabf505b64fb400c4239dcdbaeb882079477eb6a8442268596a8791b9e34de88
+acme==0.20.0 \
+ --hash=sha256:8b0cee192c0d76d6f4045bdb14b3cfd29d9720e0dad2046794a2a555f1eaccb7 \
+ --hash=sha256:45121aed6c8cc2f31896ac1083068dfdeb613f3edeff9576dc0d10632ea5a3d5
+certbot-apache==0.20.0 \
+ --hash=sha256:f7e4dbc154d2e9d1461118b6dd3dbd16f6892da468f060eeaa162aff673347e2 \
+ --hash=sha256:0ba499706451ffbccb172bcf93d6ef4c6cc8599157077a4fa6dfbe5a83c7921f
+certbot-nginx==0.20.0 \
+ --hash=sha256:b6e372e8740b20dd9bd63837646157ac97b3c9a65affd3954571b8e872ae9ecf \
+ --hash=sha256:6379fdf20d9a7651fe30bb8d4b828cbea178cc263d7af5a380fc4508d793b9ae
UNLIKELY_EOF
# -------------------------------------------------------------------------
diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig
index 708bbbee6..e276aae53 100644
--- a/letsencrypt-auto-source/letsencrypt-auto.sig
+++ b/letsencrypt-auto-source/letsencrypt-auto.sig
@@ -1,2 +1,2 @@
-לHɭm+~ESM4KYjL7ZճddI.:dZMO|Km|g($bljЅ/A^`ra^0x咁w9ckN.[
?
-h/́8!\!U59bRlb-1(p>-%u2gn
\ No newline at end of file
+HtPdM-b_8Gݵx\cf<9n$-^56zOy3o-N~ֹn%ww''}q;̰:
+M4\@)-:ǺzDY=쟭*' $certbot_auto_constraints
dev_constraints="$(dirname $my_path)/pip_constraints.txt"
+set -x
+
# install the requested packages using the pinned requirements as constraints
-pip install --constraint $certbot_auto_constraints --constraint $dev_constraints "$@"
+pip install -q --constraint $certbot_auto_constraints --constraint $dev_constraints "$@"
diff --git a/tox.cover.sh b/tox.cover.sh
index 3f0a5f72e..2b5a3cf19 100755
--- a/tox.cover.sh
+++ b/tox.cover.sh
@@ -16,7 +16,7 @@ fi
cover () {
if [ "$1" = "certbot" ]; then
- min=97
+ min=98
elif [ "$1" = "acme" ]; then
min=100
elif [ "$1" = "certbot_apache" ]; then
@@ -24,23 +24,23 @@ cover () {
elif [ "$1" = "certbot_dns_cloudflare" ]; then
min=98
elif [ "$1" = "certbot_dns_cloudxns" ]; then
- min=98
+ min=99
elif [ "$1" = "certbot_dns_digitalocean" ]; then
min=98
elif [ "$1" = "certbot_dns_dnsimple" ]; then
min=98
elif [ "$1" = "certbot_dns_dnsmadeeasy" ]; then
- min=98
+ min=99
elif [ "$1" = "certbot_dns_google" ]; then
min=99
elif [ "$1" = "certbot_dns_luadns" ]; then
min=98
elif [ "$1" = "certbot_dns_nsone" ]; then
- min=98
+ min=99
elif [ "$1" = "certbot_dns_rfc2136" ]; then
min=99
elif [ "$1" = "certbot_dns_route53" ]; then
- min=91
+ min=92
elif [ "$1" = "certbot_nginx" ]; then
min=97
elif [ "$1" = "letshelp_certbot" ]; then
@@ -50,10 +50,12 @@ cover () {
exit 1
fi
- pytest --cov "$1" --cov-report term-missing \
- --cov-fail-under "$min" --numprocesses auto --pyargs "$1"
+ pkg_dir=$(echo "$1" | tr _ -)
+ pytest --cov "$pkg_dir" --cov-append --cov-report= --numprocesses auto --pyargs "$1"
+ coverage report --fail-under="$min" --include="$pkg_dir/*" --show-missing
}
+rm -f .coverage # --cov-append is on, make sure stats are correct
for pkg in $pkgs
do
cover $pkg