diff options
Diffstat (limited to 'certbot-apache/certbot_apache/_internal/override_centos.py')
-rw-r--r-- | certbot-apache/certbot_apache/_internal/override_centos.py | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py new file mode 100644 index 000000000..a3ef2d760 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -0,0 +1,215 @@ +""" Distribution specific override class for CentOS family (RHEL, Fedora) """ +import logging + +import pkg_resources +import zope.interface + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot.errors import MisconfigurationError +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser + +logger = logging.getLogger(__name__) + + +@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", + ctl="apachectl", + version_cmd=['apachectl', '-v'], + restart_cmd=['apachectl', 'graceful'], + restart_cmd_alt=['apachectl', 'restart'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) + + def config_test(self): + """ + Override config_test to mitigate configtest error in vanilla installation + of mod_ssl in Fedora. The error is caused by non-existent self-signed + certificates referenced by the configuration, that would be autogenerated + during the first (re)start of httpd. + """ + + os_info = util.get_os_info() + fedora = os_info[0].lower() == "fedora" + + try: + super(CentOSConfigurator, self).config_test() + except errors.MisconfigurationError: + if fedora: + self._try_restart_fedora() + else: + raise + + def _try_restart_fedora(self): + """ + Tries to restart httpd using systemctl to generate the self signed keypair. + """ + + try: + util.run_script(['systemctl', 'restart', 'httpd']) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + # Finish with actual config check to see if systemctl restart helped + super(CentOSConfigurator, self).config_test() + + def _prepare_options(self): + """ + Override the options dictionary initialization in order to support + alternative restart cmd used in CentOS. + """ + super(CentOSConfigurator, self)._prepare_options() + self.options["restart_cmd_alt"][0] = self.option("ctl") + + def get_parser(self): + """Initializes the ApacheParser""" + return CentOSParser( + self.option("server_root"), self.option("vhost_root"), + self.version, configurator=self) + + def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ + """ + Override _deploy_cert in order to ensure that the Apache configuration + has "LoadModule ssl_module..." before parsing the VirtualHost configuration + that was created by Certbot + """ + super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs) + if self.version < (2, 4, 0): + self._deploy_loadmodule_ssl_if_needed() + + def _deploy_loadmodule_ssl_if_needed(self): + """ + Add "LoadModule ssl_module <pre-existing path>" to main httpd.conf if + it doesn't exist there already. + """ + + loadmods = self.parser.find_dir("LoadModule", "ssl_module", exclude=False) + + correct_ifmods = [] # type: List[str] + loadmod_args = [] # type: List[str] + loadmod_paths = [] # type: List[str] + for m in loadmods: + noarg_path = m.rpartition("/")[0] + path_args = self.parser.get_all_args(noarg_path) + if loadmod_args: + if loadmod_args != path_args: + msg = ("Certbot encountered multiple LoadModule directives " + "for LoadModule ssl_module with differing library paths. " + "Please remove or comment out the one(s) that are not in " + "use, and run Certbot again.") + raise MisconfigurationError(msg) + else: + loadmod_args = path_args + + if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member + if self.parser.loc["default"] in noarg_path: + # LoadModule already in the main configuration file + if ("ifmodule/" in noarg_path.lower() or + "ifmodule[1]" in noarg_path.lower()): + # It's the first or only IfModule in the file + return + # Populate the list of known !mod_ssl.c IfModules + nodir_path = noarg_path.rpartition("/directive")[0] + correct_ifmods.append(nodir_path) + else: + loadmod_paths.append(noarg_path) + + if not loadmod_args: + # Do not try to enable mod_ssl + return + + # Force creation as the directive wasn't found from the beginning of + # httpd.conf + rootconf_ifmod = self.parser.create_ifmod( + parser.get_aug_path(self.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + # parser.get_ifmod returns a path postfixed with "/", remove that + self.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", loadmod_args) + correct_ifmods.append(rootconf_ifmod[:-1]) + self.save_notes += "Added LoadModule ssl_module to main configuration.\n" + + # Wrap LoadModule mod_ssl inside of <IfModule !mod_ssl.c> if it's not + # configured like this already. + for loadmod_path in loadmod_paths: + nodir_path = loadmod_path.split("/directive")[0] + # Remove the old LoadModule directive + self.parser.aug.remove(loadmod_path) + + # Create a new IfModule !mod_ssl.c if not already found on path + ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", + beginning=True)[:-1] + if ssl_ifmod not in correct_ifmods: + self.parser.add_dir(ssl_ifmod, "LoadModule", loadmod_args) + correct_ifmods.append(ssl_ifmod) + self.save_notes += ("Wrapped pre-existing LoadModule ssl_module " + "inside of <IfModule !mod_ssl> block.\n") + + +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): + """ Override for update_runtime_variables for custom parsing """ + # Opportunistic, works if SELinux not enforced + super(CentOSParser, self).update_runtime_variables() + 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: + self.variables[k] = defines[k] + + def not_modssl_ifmodule(self, path): + """Checks if the provided Augeas path has argument !mod_ssl""" + + if "ifmodule" not in path.lower(): + return False + + # Trim the path to the last ifmodule + workpath = path.lower() + while workpath: + # Get path to the last IfModule (ignore the tail) + parts = workpath.rpartition("ifmodule") + + if not parts[0]: + # IfModule not found + break + ifmod_path = parts[0] + parts[1] + # Check if ifmodule had an index + if parts[2].startswith("["): + # Append the index from tail + ifmod_path += parts[2].partition("/")[0] + # Get the original path trimmed to correct length + # This is required to preserve cases + ifmod_real_path = path[0:len(ifmod_path)] + if "!mod_ssl.c" in self.get_all_args(ifmod_real_path): + return True + # Set the workpath to the heading part + workpath = parts[0] + + return False |