mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
This PR is the second part of #6497 to ease the integration, following the new plan propose by @bmw here: #6497 (comment) This PR creates the module certbot.compat.os, that delegates everything to os, and that will be the safeguard against problematic methods of the standard module. On top of that, a quality check wrapper is called in the lint tox environment. This wrapper calls pylint and ensures that standard os module is no used directly in the certbot codebase. Finally local oldest requirements are updated to ensure that tests will take the new logic when running. * Add executable permissions * Add the delegate certbot.compat.os module, add check coding style to enforce usage of certbot.compat.os instead of standard os * Load certbot.compat.os instead of os * Move existing compat test * Update local oldest requirements * Import sys * Update account_test.py * Update os.py * Update os.py * Update local oldest requirements * Implement the new linter_plugin * Fix local oldest for nginx * Remove check coding style * Update linter_plugin.py * Add several comments * Update the setup.py * Add documentation * Update acme dependencies * Update certbot/compat/os.py * Update docs/contributing.rst * Update linter_plugin.py * Handle os.path. Simplify checker. * Add a comment to a reference implementation * Update changelog * Fix module registering * Update docs/contributing.rst * Update config and changelog
211 lines
7.8 KiB
Python
211 lines
7.8 KiB
Python
"""A class that performs HTTP-01 challenges for Apache"""
|
|
import logging
|
|
|
|
from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module
|
|
|
|
from certbot import errors
|
|
from certbot.compat import os
|
|
from certbot.plugins import common
|
|
|
|
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
|
|
from certbot_apache.parser import get_aug_path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ApacheHttp01(common.TLSSNI01):
|
|
"""Class that performs HTTP-01 challenges within the Apache configurator."""
|
|
|
|
CONFIG_TEMPLATE22_PRE = """\
|
|
RewriteEngine on
|
|
RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [L]
|
|
|
|
"""
|
|
CONFIG_TEMPLATE22_POST = """\
|
|
<Directory {0}>
|
|
Order Allow,Deny
|
|
Allow from all
|
|
</Directory>
|
|
<Location /.well-known/acme-challenge>
|
|
Order Allow,Deny
|
|
Allow from all
|
|
</Location>
|
|
"""
|
|
|
|
CONFIG_TEMPLATE24_PRE = """\
|
|
RewriteEngine on
|
|
RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [END]
|
|
"""
|
|
CONFIG_TEMPLATE24_POST = """\
|
|
<Directory {0}>
|
|
Require all granted
|
|
</Directory>
|
|
<Location /.well-known/acme-challenge>
|
|
Require all granted
|
|
</Location>
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(ApacheHttp01, self).__init__(*args, **kwargs)
|
|
self.challenge_conf_pre = os.path.join(
|
|
self.configurator.conf("challenge-location"),
|
|
"le_http_01_challenge_pre.conf")
|
|
self.challenge_conf_post = os.path.join(
|
|
self.configurator.conf("challenge-location"),
|
|
"le_http_01_challenge_post.conf")
|
|
self.challenge_dir = os.path.join(
|
|
self.configurator.config.work_dir,
|
|
"http_challenges")
|
|
self.moded_vhosts = set() # type: Set[VirtualHost]
|
|
|
|
def perform(self):
|
|
"""Perform all HTTP-01 challenges."""
|
|
if not self.achalls:
|
|
return []
|
|
# Save any changes to the configuration as a precaution
|
|
# About to make temporary changes to the config
|
|
self.configurator.save("Changes before challenge setup", True)
|
|
|
|
self.configurator.ensure_listen(str(
|
|
self.configurator.config.http01_port))
|
|
self.prepare_http01_modules()
|
|
|
|
responses = self._set_up_challenges()
|
|
|
|
self._mod_config()
|
|
# Save reversible changes
|
|
self.configurator.save("HTTP Challenge", True)
|
|
|
|
return responses
|
|
|
|
def prepare_http01_modules(self):
|
|
"""Make sure that we have the needed modules available for http01"""
|
|
|
|
if self.configurator.conf("handle-modules"):
|
|
needed_modules = ["rewrite"]
|
|
if self.configurator.version < (2, 4):
|
|
needed_modules.append("authz_host")
|
|
else:
|
|
needed_modules.append("authz_core")
|
|
for mod in needed_modules:
|
|
if mod + "_module" not in self.configurator.parser.modules:
|
|
self.configurator.enable_mod(mod, temp=True)
|
|
|
|
def _mod_config(self):
|
|
selected_vhosts = [] # type: List[VirtualHost]
|
|
http_port = str(self.configurator.config.http01_port)
|
|
for chall in self.achalls:
|
|
# Search for matching VirtualHosts
|
|
for vh in self._matching_vhosts(chall.domain):
|
|
selected_vhosts.append(vh)
|
|
|
|
# Ensure that we have one or more VirtualHosts that we can continue
|
|
# with. (one that listens to port configured with --http-01-port)
|
|
found = False
|
|
for vhost in selected_vhosts:
|
|
if any(a.is_wildcard() or a.get_port() == http_port for a in vhost.addrs):
|
|
found = True
|
|
|
|
if not found:
|
|
for vh in self._relevant_vhosts():
|
|
selected_vhosts.append(vh)
|
|
|
|
# Add the challenge configuration
|
|
for vh in selected_vhosts:
|
|
self._set_up_include_directives(vh)
|
|
|
|
self.configurator.reverter.register_file_creation(
|
|
True, self.challenge_conf_pre)
|
|
self.configurator.reverter.register_file_creation(
|
|
True, self.challenge_conf_post)
|
|
|
|
if self.configurator.version < (2, 4):
|
|
config_template_pre = self.CONFIG_TEMPLATE22_PRE
|
|
config_template_post = self.CONFIG_TEMPLATE22_POST
|
|
else:
|
|
config_template_pre = self.CONFIG_TEMPLATE24_PRE
|
|
config_template_post = self.CONFIG_TEMPLATE24_POST
|
|
|
|
config_text_pre = config_template_pre.format(self.challenge_dir)
|
|
config_text_post = config_template_post.format(self.challenge_dir)
|
|
|
|
logger.debug("writing a pre config file with text:\n %s", config_text_pre)
|
|
with open(self.challenge_conf_pre, "w") as new_conf:
|
|
new_conf.write(config_text_pre)
|
|
logger.debug("writing a post config file with text:\n %s", config_text_post)
|
|
with open(self.challenge_conf_post, "w") as new_conf:
|
|
new_conf.write(config_text_post)
|
|
|
|
def _matching_vhosts(self, domain):
|
|
"""Return all VirtualHost objects that have the requested domain name or
|
|
a wildcard name that would match the domain in ServerName or ServerAlias
|
|
directive.
|
|
"""
|
|
matching_vhosts = []
|
|
for vhost in self.configurator.vhosts:
|
|
if self.configurator.domain_in_names(vhost.get_names(), domain):
|
|
# domain_in_names also matches the exact names, so no need
|
|
# to check "domain in vhost.get_names()" explicitly here
|
|
matching_vhosts.append(vhost)
|
|
|
|
return matching_vhosts
|
|
|
|
def _relevant_vhosts(self):
|
|
http01_port = str(self.configurator.config.http01_port)
|
|
relevant_vhosts = []
|
|
for vhost in self.configurator.vhosts:
|
|
if any(a.is_wildcard() or a.get_port() == http01_port for a in vhost.addrs):
|
|
if not vhost.ssl:
|
|
relevant_vhosts.append(vhost)
|
|
if not relevant_vhosts:
|
|
raise errors.PluginError(
|
|
"Unable to find a virtual host listening on port {0} which is"
|
|
" currently needed for Certbot to prove to the CA that you"
|
|
" control your domain. Please add a virtual host for port"
|
|
" {0}.".format(http01_port))
|
|
|
|
return relevant_vhosts
|
|
|
|
def _set_up_challenges(self):
|
|
if not os.path.isdir(self.challenge_dir):
|
|
os.makedirs(self.challenge_dir)
|
|
os.chmod(self.challenge_dir, 0o755)
|
|
|
|
responses = []
|
|
for achall in self.achalls:
|
|
responses.append(self._set_up_challenge(achall))
|
|
|
|
return responses
|
|
|
|
def _set_up_challenge(self, achall):
|
|
response, validation = achall.response_and_validation()
|
|
|
|
name = os.path.join(self.challenge_dir, achall.chall.encode("token"))
|
|
|
|
self.configurator.reverter.register_file_creation(True, name)
|
|
with open(name, 'wb') as f:
|
|
f.write(validation.encode())
|
|
os.chmod(name, 0o644)
|
|
|
|
return response
|
|
|
|
def _set_up_include_directives(self, vhost):
|
|
"""Includes override configuration to the beginning and to the end of
|
|
VirtualHost. Note that this include isn't added to Augeas search tree"""
|
|
|
|
if vhost not in self.moded_vhosts:
|
|
logger.debug(
|
|
"Adding a temporary challenge validation Include for name: %s " +
|
|
"in: %s", vhost.name, vhost.filep)
|
|
self.configurator.parser.add_dir_beginning(
|
|
vhost.path, "Include", self.challenge_conf_pre)
|
|
self.configurator.parser.add_dir(
|
|
vhost.path, "Include", self.challenge_conf_post)
|
|
|
|
if not vhost.enabled:
|
|
self.configurator.parser.add_dir(
|
|
get_aug_path(self.configurator.parser.loc["default"]),
|
|
"Include", vhost.filep)
|
|
|
|
self.moded_vhosts.add(vhost)
|