2015-11-25 12:44:17 -05:00
|
|
|
"""A class that performs TLS-SNI-01 challenges for Apache"""
|
2015-11-19 12:12:25 -05:00
|
|
|
|
2014-12-23 06:54:30 -05:00
|
|
|
import os
|
2016-01-08 07:58:17 -05:00
|
|
|
import logging
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2016-04-13 19:30:57 -04:00
|
|
|
from certbot.plugins import common
|
2016-05-02 02:45:27 -04:00
|
|
|
from certbot.errors import PluginError, MissingCommandlineFlag
|
2015-06-02 09:55:16 -04:00
|
|
|
|
2016-04-13 19:30:57 -04:00
|
|
|
from certbot_apache import obj
|
2014-12-23 06:54:30 -05:00
|
|
|
|
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
|
|
|
|
2015-11-19 12:12:25 -05:00
|
|
|
class ApacheTlsSni01(common.TLSSNI01):
|
|
|
|
|
"""Class that performs TLS-SNI-01 challenges within the Apache configurator
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2015-02-02 09:14:32 -05:00
|
|
|
:ivar configurator: ApacheConfigurator object
|
2015-03-26 20:23:17 -04:00
|
|
|
:type configurator: :class:`~apache.configurator.ApacheConfigurator`
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2015-11-19 12:12:25 -05:00
|
|
|
:ivar list achalls: Annotated TLS-SNI-01
|
2015-11-07 13:10:56 -05:00
|
|
|
(`.KeyAuthorizationAnnotatedChallenge`) challenges.
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2015-03-26 20:23:17 -04:00
|
|
|
:param list indices: Meant to hold indices of challenges in a
|
2015-11-19 12:12:25 -05:00
|
|
|
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,
|
2015-11-19 12:12:25 -05:00
|
|
|
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
|
|
|
|
|
|
2014-12-23 06:54:30 -05:00
|
|
|
"""
|
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>
|
|
|
|
|
|
|
|
|
|
"""
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2015-07-19 05:22:10 -04:00
|
|
|
def __init__(self, *args, **kwargs):
|
2015-11-19 12:12:25 -05:00
|
|
|
super(ApacheTlsSni01, self).__init__(*args, **kwargs)
|
2015-07-09 21:55:08 -04:00
|
|
|
|
|
|
|
|
self.challenge_conf = os.path.join(
|
2015-12-07 04:07:31 -05:00
|
|
|
self.configurator.conf("challenge-location"),
|
2015-11-19 12:12:25 -05:00
|
|
|
"le_tls_sni_01_cert_challenge.conf")
|
2015-07-09 21:55:08 -04:00
|
|
|
|
2014-12-23 06:54:30 -05:00
|
|
|
def perform(self):
|
2015-11-19 12:12:25 -05:00
|
|
|
"""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 []
|
2014-12-23 06:54:30 -05:00
|
|
|
# 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)
|
2014-12-23 06:54:30 -05:00
|
|
|
|
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
|
|
|
|
2014-12-23 06:54:30 -05: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))
|
2014-12-23 06:54:30 -05:00
|
|
|
|
|
|
|
|
# Setup the configuration
|
2015-11-19 12:12:25 -05:00
|
|
|
addrs = self._mod_config()
|
2016-01-22 14:47:49 -05:00
|
|
|
self.configurator.save("Don't lose mod_config changes", True)
|
2015-11-19 12:12:25 -05:00
|
|
|
self.configurator.make_addrs_sni_ready(addrs)
|
2014-12-23 06:54:30 -05:00
|
|
|
|
|
|
|
|
# Save reversible changes
|
2015-02-02 09:14:32 -05:00
|
|
|
self.configurator.save("SNI Challenge", True)
|
2014-12-23 06:54:30 -05:00
|
|
|
|
|
|
|
|
return responses
|
|
|
|
|
|
2015-07-15 17:34:24 -04:00
|
|
|
def _mod_config(self):
|
2014-12-23 06:54:30 -05:00
|
|
|
"""Modifies Apache config files to include challenge vhosts.
|
|
|
|
|
|
|
|
|
|
Result: Apache config includes virtual servers for issued challs
|
|
|
|
|
|
2015-11-19 12:12:25 -05:00
|
|
|
:returns: All TLS-SNI-01 addresses used
|
2015-07-15 17:34:24 -04:00
|
|
|
:rtype: set
|
2014-12-23 06:54:30 -05:00
|
|
|
|
|
|
|
|
"""
|
2015-11-19 12:12:25 -05:00
|
|
|
addrs = set()
|
2014-12-23 06:54:30 -05:00
|
|
|
config_text = "<IfModule mod_ssl.c>\n"
|
2015-07-15 17:34:24 -04:00
|
|
|
|
|
|
|
|
for achall in self.achalls:
|
2015-11-19 12:12:25 -05:00
|
|
|
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
|
|
|
|
2014-12-23 06:54:30 -05:00
|
|
|
config_text += "</IfModule>\n"
|
|
|
|
|
|
2017-09-25 15:03:09 -04:00
|
|
|
self.configurator.parser.add_include(
|
|
|
|
|
self.configurator.parser.loc["default"], self.challenge_conf)
|
2015-02-02 09:14:32 -05:00
|
|
|
self.configurator.reverter.register_file_creation(
|
|
|
|
|
True, self.challenge_conf)
|
2014-12-23 06:54:30 -05:00
|
|
|
|
2016-02-17 18:57:14 -05:00
|
|
|
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:
|
2014-12-23 06:54:30 -05:00
|
|
|
new_conf.write(config_text)
|
|
|
|
|
|
2015-11-19 12:12:25 -05:00
|
|
|
return addrs
|
2015-07-15 17:34:24 -04:00
|
|
|
|
2015-11-19 12:12:25 -05: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.
|
2015-11-19 12:12:25 -05:00
|
|
|
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
|
|
|
|
2016-05-02 02:30:32 -04:00
|
|
|
try:
|
|
|
|
|
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
|
2016-06-25 13:51:14 -04:00
|
|
|
except (PluginError, MissingCommandlineFlag):
|
2016-05-02 02:30:32 -04:00
|
|
|
# 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.
|
2016-07-19 13:13:50 -04:00
|
|
|
logger.warning("Falling back to default vhost %s...", default_addr)
|
2016-05-02 02:30:32 -04:00
|
|
|
addrs.add(default_addr)
|
|
|
|
|
return addrs
|
|
|
|
|
|
2015-07-15 17:34:24 -04:00
|
|
|
for addr in vhost.addrs:
|
|
|
|
|
if "_default_" == addr.get_addr():
|
2015-11-19 12:12:25 -05:00
|
|
|
addrs.add(default_addr)
|
2015-07-15 17:34:24 -04:00
|
|
|
else:
|
2015-11-19 12:12:25 -05:00
|
|
|
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
|
|
|
|
2015-11-19 12:12:25 -05: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):
|
2014-12-23 06:54:30 -05:00
|
|
|
"""Chocolate virtual server configuration text
|
|
|
|
|
|
2015-11-07 13:10:56 -05:00
|
|
|
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
|
2015-11-19 12:12:25 -05:00
|
|
|
TLS-SNI-01 challenge.
|
2015-02-13 17:37:45 -05:00
|
|
|
|
2014-12-23 06:54:30 -05:00
|
|
|
:param list ip_addrs: addresses of challenged domain
|
2015-07-15 17:34:24 -04:00
|
|
|
:class:`list` of type `~.obj.Addr`
|
2014-12-23 06:54:30 -05:00
|
|
|
|
|
|
|
|
:returns: virtual host configuration text
|
|
|
|
|
:rtype: str
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
ips = " ".join(str(i) for i in ip_addrs)
|
2015-02-02 16:11:59 -05:00
|
|
|
document_root = os.path.join(
|
2015-11-19 12:12:25 -05:00
|
|
|
self.configurator.config.work_dir, "tls_sni_01_page/")
|
2017-01-26 19:21:54 -05:00
|
|
|
# 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,
|
2017-02-24 21:21:21 -05:00
|
|
|
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),
|
Rewrite acccounts and registration.
Save accounts to:
/etc/letsencrypt/accounts/www.letsencrypt-dmeo.org/acme/new-reg/ \
kuba.le.wtf@2015-07-04T14:04:10Z/ \
{regr.json,meta.json,private_key.json}
Account now represents a combination of private key, Registration
Resource and client account metadata. `Account.id` based on the
account metadata (creation host and datetime). UI interface
(`cli._determine_account`) based on the `id`, and not on email as
previously.
Add `AccountStorage` interface and `AccountFileStorage`,
`AccountMemoryStorage` implementations (latter, in-memory, useful for
testing).
Create Account only after Registration Resource is received
(`register()` returns `Account`).
Allow `client.Client(..., acme=acme, ...)`: API client might reuse
acme.client.Client as returned by `register()`.
Move report_new_account to letsencrypt.account, client.Client.register
into client.register.
Use Registration.from_data acme API.
achallenges.AChallenge.key is now the `acme.jose.JWK`, not
`le_util.Key`. Plugins have to export PEM/DER as necessary
(c.f. `letsencrypt.plugins.common.Dvsni.get_key_path`)
Add --agree-tos, save --agree-eula to "args.eula". Prompt for EULA as
soon as client is launched, add prompt for TOS.
Remove unnecessary letsencrypt.network. Remove, now irrelevant,
`IConfig.account_keys_dir`.
Based on the draft from
https://github.com/letsencrypt/letsencrypt/pull/362#issuecomment-97946817.
2015-07-04 02:46:36 -04:00
|
|
|
key_path=self.get_key_path(achall),
|
2015-03-26 20:39:08 -04:00
|
|
|
document_root=document_root).replace("\n", os.linesep)
|