certbot/certbot-apache/certbot_apache/tls_sni_01.py

173 lines
5.8 KiB
Python
Raw Permalink Normal View History

"""A class that performs TLS-SNI-01 challenges for Apache"""
import os
2016-01-08 07:58:17 -05:00
import logging
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag
from certbot_apache import obj
2016-01-08 07:58:17 -05:00
logger = logging.getLogger(__name__)
2015-01-09 08:30:15 -05:00
2016-01-14 06:25:15 -05:00
class ApacheTlsSni01(common.TLSSNI01):
"""Class that performs TLS-SNI-01 challenges within the Apache configurator
:ivar configurator: ApacheConfigurator object
:type configurator: :class:`~apache.configurator.ApacheConfigurator`
:ivar list achalls: Annotated TLS-SNI-01
2015-11-07 13:10:56 -05:00
(`.KeyAuthorizationAnnotatedChallenge`) challenges.
:param list indices: Meant to hold indices of challenges in a
larger array. ApacheTlsSni01 is capable of solving many challenges
2015-01-10 01:25:36 -05:00
at once which causes an indexing issue within ApacheConfigurator
who must return all responses in order. Imagine ApacheConfigurator
2015-11-01 03:46:00 -05:00
maintaining state about where all of the http-01 Challenges,
TLS-SNI-01 Challenges belong in the response array. This is an
optional utility.
2015-01-10 01:25:36 -05:00
:param str challenge_conf: location of the challenge config file
"""
2015-03-25 06:21:01 -04:00
VHOST_TEMPLATE = """\
<VirtualHost {vhost}>
ServerName {server_name}
UseCanonicalName on
SSLStrictSNIVHostCheck on
LimitRequestBody 1048576
Include {ssl_options_conf_path}
SSLCertificateFile {cert_path}
SSLCertificateKeyFile {key_path}
DocumentRoot {document_root}
</VirtualHost>
"""
2015-07-19 05:22:10 -04:00
def __init__(self, *args, **kwargs):
super(ApacheTlsSni01, self).__init__(*args, **kwargs)
2015-07-09 21:55:08 -04:00
self.challenge_conf = os.path.join(
self.configurator.conf("challenge-location"),
"le_tls_sni_01_cert_challenge.conf")
2015-07-09 21:55:08 -04:00
def perform(self):
"""Perform a TLS-SNI-01 challenge."""
2015-02-13 17:37:45 -05:00
if not self.achalls:
2015-03-24 20:49:38 -04:00
return []
# Save any changes to the configuration as a precaution
# About to make temporary changes to the config
2016-02-19 15:20:32 -05:00
self.configurator.save("Changes before challenge setup", True)
2015-07-19 05:22:10 -04:00
# Prepare the server for HTTPS
self.configurator.prepare_server_https(
2015-11-07 09:21:58 -05:00
str(self.configurator.config.tls_sni_01_port), True)
2015-07-19 05:22:10 -04:00
responses = []
# Create all of the challenge certs
2015-02-13 17:37:45 -05:00
for achall in self.achalls:
responses.append(self._setup_challenge_cert(achall))
# Setup the configuration
addrs = self._mod_config()
2016-01-22 14:47:49 -05:00
self.configurator.save("Don't lose mod_config changes", True)
self.configurator.make_addrs_sni_ready(addrs)
# Save reversible changes
self.configurator.save("SNI Challenge", True)
return responses
2015-07-15 17:34:24 -04:00
def _mod_config(self):
"""Modifies Apache config files to include challenge vhosts.
Result: Apache config includes virtual servers for issued challs
:returns: All TLS-SNI-01 addresses used
2015-07-15 17:34:24 -04:00
:rtype: set
"""
addrs = set()
config_text = "<IfModule mod_ssl.c>\n"
2015-07-15 17:34:24 -04:00
for achall in self.achalls:
achall_addrs = self._get_addrs(achall)
addrs.update(achall_addrs)
2015-07-15 17:34:24 -04:00
2015-07-19 05:22:10 -04:00
config_text += self._get_config_text(achall, achall_addrs)
2015-07-15 17:34:24 -04:00
config_text += "</IfModule>\n"
Do not parse disabled configuration files from under sites-available on Debian / Ubuntu (#4104) This changes the apache plugin behaviour to only parse enabled configuration files and respecting the --apache-vhost-root CLI parameter for new SSL vhost creation. If --apache-vhost-root isn't defined, or doesn't exist, the SSL vhost will be created to originating non-SSL vhost directory. This PR also implements actual check for vhost enabled state, and makes sure parser.parse_file() does not discard changes in Augeas DOM, by doing an autosave. Also handles enabling the new SSL vhost, if it's on a path that's not parsed by Apache. Fixes: #1328 Fixes: #3545 Fixes: #3791 Fixes: #4523 Fixes: #4837 Fixes: #4905 * First changes * Handle rest of the errors * Test fixes * Final fixes * Make parse_files accessible and fix linter problems * Activate vhost at later time * Cleanup * Add a new test case, and fix old * Enable site later in deploy_cert * Make apache-conf-test default dummy configuration enabled * Remove is_sites_available as obsolete * Cleanup * Brought back conditional vhost_path parsing * Parenthesis * Fix merge leftovers * Fix to work with the recent changes to new file creation * Added fix and tests for non-symlink vhost in sites-enabled * Made vhostroot parameter for ApacheParser optional, and removed extra_path * Respect vhost-root, and add Include statements to root configuration if needed * Fixed site enabling order to prevent apache restart error while enabling mod_ssl * Don't exclude Ubuntu / Debian vhost-root cli argument * Changed the SSL vhost directory selection priority * Requested fixes for paths and vhost discovery * Make sure the Augeas DOM is written to disk before loading new files * Actual checking for if the file is parsed within existing Apache configuration * Fix the order of dummy SSL directives addition and enabling modules * Restructured site_enabled checks * Enabling vhost correctly for non-debian systems
2017-09-25 15:03:09 -04:00
self.configurator.parser.add_include(
self.configurator.parser.loc["default"], self.challenge_conf)
self.configurator.reverter.register_file_creation(
True, self.challenge_conf)
logger.debug("writing a config file with text:\n %s", config_text)
2015-12-23 19:08:33 -05:00
with open(self.challenge_conf, "w") as new_conf:
new_conf.write(config_text)
return addrs
2015-07-15 17:34:24 -04:00
def _get_addrs(self, achall):
2015-12-01 13:46:13 -05:00
"""Return the Apache addresses needed for TLS-SNI-01."""
2015-07-15 17:34:24 -04:00
# TODO: Checkout _default_ rules.
addrs = set()
2015-11-07 09:21:58 -05:00
default_addr = obj.Addr(("*", str(
self.configurator.config.tls_sni_01_port)))
2015-07-15 17:34:24 -04:00
try:
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
2016-06-25 13:51:14 -04:00
except (PluginError, MissingCommandlineFlag):
# We couldn't find the virtualhost for this domain, possibly
2017-07-05 10:03:02 -04:00
# because it's a new vhost that's not configured yet
# (GH #677). See also GH #2600.
logger.warning("Falling back to default vhost %s...", default_addr)
addrs.add(default_addr)
return addrs
2015-07-15 17:34:24 -04:00
for addr in vhost.addrs:
if "_default_" == addr.get_addr():
addrs.add(default_addr)
2015-07-15 17:34:24 -04:00
else:
addrs.add(
2016-01-14 06:25:15 -05:00
addr.get_sni_addr(
self.configurator.config.tls_sni_01_port))
2015-07-15 17:34:24 -04:00
return addrs
2015-07-15 17:34:24 -04:00
2015-02-13 17:37:45 -05:00
def _get_config_text(self, achall, ip_addrs):
"""Chocolate virtual server configuration text
2015-11-07 13:10:56 -05:00
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
TLS-SNI-01 challenge.
2015-02-13 17:37:45 -05:00
:param list ip_addrs: addresses of challenged domain
2015-07-15 17:34:24 -04:00
:class:`list` of type `~.obj.Addr`
:returns: virtual host configuration text
:rtype: str
"""
ips = " ".join(str(i) for i in ip_addrs)
document_root = os.path.join(
self.configurator.config.work_dir, "tls_sni_01_page/")
# TODO: Python docs is not clear how multiline string literal
2015-03-25 06:21:01 -04:00
# newlines are parsed on different platforms. At least on
2015-03-25 06:25:27 -04:00
# Linux (Debian sid), when source file uses CRLF, Python still
2015-03-26 20:39:08 -04:00
# parses it as "\n"... c.f.:
2015-03-25 06:21:01 -04:00
# https://docs.python.org/2.7/reference/lexical_analysis.html
return self.VHOST_TEMPLATE.format(
2015-08-05 18:39:31 -04:00
vhost=ips,
server_name=achall.response(achall.account_key).z_domain.decode('ascii'),
2015-07-19 22:49:44 -04:00
ssl_options_conf_path=self.configurator.mod_ssl_conf,
2015-07-09 04:19:54 -04:00
cert_path=self.get_cert_path(achall),
key_path=self.get_key_path(achall),
2015-03-26 20:39:08 -04:00
document_root=document_root).replace("\n", os.linesep)