Merge branch 'master' into certbot-ci-part2

This commit is contained in:
Adrien Ferrand
2019-03-18 11:29:33 +01:00
15 changed files with 87 additions and 572 deletions
+4 -19
View File
@@ -32,7 +32,6 @@ from certbot_apache import display_ops
from certbot_apache import http_01 from certbot_apache import http_01
from certbot_apache import obj from certbot_apache import obj
from certbot_apache import parser from certbot_apache import parser
from certbot_apache import tls_sni_01
from collections import defaultdict from collections import defaultdict
@@ -215,15 +214,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
@property @property
def mod_ssl_conf(self): def mod_ssl_conf(self):
"""Full absolute path to SSL configuration file.""" """Full absolute path to SSL configuration file."""
return os.path.join(self.config.config_dir, return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST)
constants.MOD_SSL_CONF_DEST)
@property @property
def updated_mod_ssl_conf_digest(self): def updated_mod_ssl_conf_digest(self):
"""Full absolute path to digest of updated SSL configuration file.""" """Full absolute path to digest of updated SSL configuration file."""
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
def prepare(self): def prepare(self):
"""Prepare the authenticator/installer. """Prepare the authenticator/installer.
@@ -396,7 +393,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if len(name.split(".")) == len(domain.split(".")): if len(name.split(".")) == len(domain.split(".")):
return fnmatch.fnmatch(name, domain) return fnmatch.fnmatch(name, domain)
def _choose_vhosts_wildcard(self, domain, create_ssl=True): def _choose_vhosts_wildcard(self, domain, create_ssl=True):
"""Prompts user to choose vhosts to install a wildcard certificate for""" """Prompts user to choose vhosts to install a wildcard certificate for"""
@@ -441,7 +437,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._wildcard_vhosts[domain] = return_vhosts self._wildcard_vhosts[domain] = return_vhosts
return return_vhosts return return_vhosts
def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path):
""" """
Helper function for deploy_cert() that handles the actual deployment Helper function for deploy_cert() that handles the actual deployment
@@ -449,8 +444,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
domain originally passed for deploy_cert(). This is especially true domain originally passed for deploy_cert(). This is especially true
with wildcard certificates with wildcard certificates
""" """
# This is done first so that ssl module is enabled and cert_path, # This is done first so that ssl module is enabled and cert_path,
# cert_key... can all be parsed appropriately # cert_key... can all be parsed appropriately
self.prepare_server_https("443") self.prepare_server_https("443")
@@ -1925,7 +1918,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.parser.add_dir(vhost.path, "RewriteRule", self.parser.add_dir(vhost.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS) constants.REWRITE_HTTPS_ARGS)
def _verify_no_certbot_redirect(self, vhost): def _verify_no_certbot_redirect(self, vhost):
"""Checks to see if a redirect was already installed by certbot. """Checks to see if a redirect was already installed by certbot.
@@ -2193,7 +2185,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:raises .errors.MisconfigurationError: If reload fails :raises .errors.MisconfigurationError: If reload fails
""" """
error = ""
try: try:
util.run_script(self.option("restart_cmd")) util.run_script(self.option("restart_cmd"))
except errors.SubprocessError as err: except errors.SubprocessError as err:
@@ -2267,7 +2258,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
########################################################################### ###########################################################################
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
"""Return list of challenge preferences.""" """Return list of challenge preferences."""
return [challenges.HTTP01, challenges.TLSSNI01] return [challenges.HTTP01]
def perform(self, achalls): def perform(self, achalls):
"""Perform the configuration related challenge. """Perform the configuration related challenge.
@@ -2280,20 +2271,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._chall_out.update(achalls) self._chall_out.update(achalls)
responses = [None] * len(achalls) responses = [None] * len(achalls)
http_doer = http_01.ApacheHttp01(self) http_doer = http_01.ApacheHttp01(self)
sni_doer = tls_sni_01.ApacheTlsSni01(self)
for i, achall in enumerate(achalls): for i, achall in enumerate(achalls):
# Currently also have chall_doer hold associated index of the # Currently also have chall_doer hold associated index of the
# challenge. This helps to put all of the responses back together # challenge. This helps to put all of the responses back together
# when they are all complete. # when they are all complete.
if isinstance(achall.chall, challenges.HTTP01): http_doer.add_chall(achall, i)
http_doer.add_chall(achall, i)
else: # tls-sni-01
sni_doer.add_chall(achall, i)
http_response = http_doer.perform() http_response = http_doer.perform()
sni_response = sni_doer.perform() if http_response:
if http_response or sni_response:
# Must reload in order to activate the challenges. # Must reload in order to activate the challenges.
# Handled here because we may be able to load up other challenge # Handled here because we may be able to load up other challenge
# types # types
@@ -2304,7 +2290,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
time.sleep(3) time.sleep(3)
self._update_responses(responses, http_response, http_doer) self._update_responses(responses, http_response, http_doer)
self._update_responses(responses, sni_response, sni_doer)
return responses return responses
@@ -812,32 +812,19 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(self.config.add_name_vhost.call_count, 2) self.assertEqual(self.config.add_name_vhost.call_count, 2)
@mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform") @mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform")
@mock.patch("certbot_apache.configurator.tls_sni_01.ApacheTlsSni01.perform")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_perform(self, mock_restart, mock_tls_perform, mock_http_perform): def test_perform(self, mock_restart, mock_http_perform):
# Only tests functionality specific to configurator.perform # Only tests functionality specific to configurator.perform
# Note: As more challenges are offered this will have to be expanded # Note: As more challenges are offered this will have to be expanded
account_key, achalls = self.get_key_and_achalls() account_key, achalls = self.get_key_and_achalls()
all_expected = [] expected = [achall.response(account_key) for achall in achalls]
http_expected = [] mock_http_perform.return_value = expected
tls_expected = []
for achall in achalls:
response = achall.response(account_key)
if isinstance(achall.chall, challenges.HTTP01):
http_expected.append(response)
else:
tls_expected.append(response)
all_expected.append(response)
mock_http_perform.return_value = http_expected
mock_tls_perform.return_value = tls_expected
responses = self.config.perform(achalls) responses = self.config.perform(achalls)
self.assertEqual(mock_http_perform.call_count, 1) self.assertEqual(mock_http_perform.call_count, 1)
self.assertEqual(mock_tls_perform.call_count, 1) self.assertEqual(responses, expected)
self.assertEqual(responses, all_expected)
self.assertEqual(mock_restart.call_count, 1) self.assertEqual(mock_restart.call_count, 1)
@@ -1,151 +0,0 @@
"""Test for certbot_apache.tls_sni_01."""
import shutil
import unittest
import mock
from certbot import errors
from certbot.plugins import common_test
from certbot_apache import obj
from certbot_apache.tests import util
from six.moves import xrange # pylint: disable=redefined-builtin, import-error
class TlsSniPerformTest(util.ApacheTest):
"""Test the ApacheTlsSni01 challenge."""
auth_key = common_test.AUTH_KEY
achalls = common_test.ACHALLS
def setUp(self): # pylint: disable=arguments-differ
super(TlsSniPerformTest, self).setUp()
config = util.get_apache_configurator(
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
self.sni = tls_sni_01.ApacheTlsSni01(config)
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
def test_perform0(self):
resp = self.sni.perform()
self.assertEqual(len(resp), 0)
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot.util.run_script")
def test_perform1(self, _, mock_exists):
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()
achall = self.achalls[0]
self.sni.add_chall(achall)
response = self.achalls[0].response(self.auth_key)
mock_setup_cert = mock.MagicMock(return_value=response)
# pylint: disable=protected-access
self.sni._setup_challenge_cert = mock_setup_cert
responses = self.sni.perform()
mock_setup_cert.assert_called_once_with(achall)
# Check to make sure challenge config path is included in apache config
self.assertEqual(
len(self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)), 1)
self.assertEqual(len(responses), 1)
self.assertEqual(responses[0], response)
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)
acme_responses.append(achall.response(self.auth_key))
mock_setup_cert = mock.MagicMock(side_effect=acme_responses)
# pylint: disable=protected-access
self.sni._setup_challenge_cert = mock_setup_cert
with mock.patch(
"certbot_apache.override_debian.DebianConfigurator.enable_mod"):
sni_responses = self.sni.perform()
self.assertEqual(mock_setup_cert.call_count, 2)
# Make sure calls made to mocked function were correct
self.assertEqual(
mock_setup_cert.call_args_list[0], mock.call(self.achalls[0]))
self.assertEqual(
mock_setup_cert.call_args_list[1], mock.call(self.achalls[1]))
self.assertEqual(
len(self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)),
1)
self.assertEqual(len(sni_responses), 2)
for i in xrange(2):
self.assertEqual(sni_responses[i], acme_responses[i])
def test_mod_config(self):
z_domains = []
for achall in self.achalls:
self.sni.add_chall(achall)
z_domain = achall.response(self.auth_key).z_domain
z_domains.append(set([z_domain.decode('ascii')]))
self.sni._mod_config() # pylint: disable=protected-access
self.sni.configurator.save()
self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)
vh_match = self.sni.configurator.aug.match(
"/files" + self.sni.challenge_conf + "//VirtualHost")
vhs = []
for match in vh_match:
# pylint: disable=protected-access
vhs.append(self.sni.configurator._create_vhost(match))
self.assertEqual(len(vhs), 2)
for vhost in vhs:
self.assertEqual(vhost.addrs, set([obj.Addr.fromstring("*:443")]))
names = vhost.get_names()
self.assertTrue(names in z_domains)
def test_get_addrs_default(self):
self.sni.configurator.choose_vhost = mock.Mock(
return_value=obj.VirtualHost(
"path", "aug_path",
set([obj.Addr.fromstring("_default_:443")]),
False, False)
)
# pylint: disable=protected-access
self.assertEqual(
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
def test_get_addrs_no_vhost_found(self):
self.sni.configurator.choose_vhost = mock.Mock(
side_effect=errors.MissingCommandlineFlag(
"Failed to run Apache plugin non-interactively"))
# pylint: disable=protected-access
self.assertEqual(
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
if __name__ == "__main__":
unittest.main() # pragma: no cover
-174
View File
@@ -1,174 +0,0 @@
"""A class that performs TLS-SNI-01 challenges for Apache"""
import os
import logging
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag
from certbot_apache import obj
logger = logging.getLogger(__name__)
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
(`.KeyAuthorizationAnnotatedChallenge`) challenges.
:param list indices: Meant to hold indices of challenges in a
larger array. ApacheTlsSni01 is capable of solving many challenges
at once which causes an indexing issue within ApacheConfigurator
who must return all responses in order. Imagine ApacheConfigurator
maintaining state about where all of the http-01 Challenges,
TLS-SNI-01 Challenges belong in the response array. This is an
optional utility.
:param str challenge_conf: location of the challenge config file
"""
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>
"""
def __init__(self, *args, **kwargs):
super(ApacheTlsSni01, self).__init__(*args, **kwargs)
self.challenge_conf = os.path.join(
self.configurator.conf("challenge-location"),
"le_tls_sni_01_cert_challenge.conf")
def perform(self):
"""Perform a TLS-SNI-01 challenge."""
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)
# Prepare the server for HTTPS
self.configurator.prepare_server_https(
str(self.configurator.config.tls_sni_01_port), True)
responses = []
# Create all of the challenge certs
for achall in self.achalls:
responses.append(self._setup_challenge_cert(achall))
# Setup the configuration
addrs = self._mod_config()
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
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
:rtype: set
"""
addrs = set() # type: Set[obj.Addr]
config_text = "<IfModule mod_ssl.c>\n"
for achall in self.achalls:
achall_addrs = self._get_addrs(achall)
addrs.update(achall_addrs)
config_text += self._get_config_text(achall, achall_addrs)
config_text += "</IfModule>\n"
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)
with open(self.challenge_conf, "w") as new_conf:
new_conf.write(config_text)
return addrs
def _get_addrs(self, achall):
"""Return the Apache addresses needed for TLS-SNI-01."""
# TODO: Checkout _default_ rules.
addrs = set()
default_addr = obj.Addr(("*", str(
self.configurator.config.tls_sni_01_port)))
try:
vhost = self.configurator.choose_vhost(achall.domain,
create_if_no_ssl=False)
except (PluginError, MissingCommandlineFlag):
# We couldn't find the virtualhost for this domain, possibly
# 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
for addr in vhost.addrs:
if "_default_" == addr.get_addr():
addrs.add(default_addr)
else:
addrs.add(
addr.get_sni_addr(
self.configurator.config.tls_sni_01_port))
return addrs
def _get_config_text(self, achall, ip_addrs):
"""Chocolate virtual server configuration text
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
TLS-SNI-01 challenge.
:param list ip_addrs: addresses of challenged domain
: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
# newlines are parsed on different platforms. At least on
# Linux (Debian sid), when source file uses CRLF, Python still
# parses it as "\n"... c.f.:
# https://docs.python.org/2.7/reference/lexical_analysis.html
return self.VHOST_TEMPLATE.format(
vhost=ips,
server_name=achall.response(achall.account_key).z_domain.decode('ascii'),
ssl_options_conf_path=self.configurator.mod_ssl_conf,
cert_path=self.get_cert_path(achall),
key_path=self.get_key_path(achall),
document_root=document_root).replace("\n", os.linesep)
@@ -23,6 +23,9 @@ class Proxy(object):
def __init__(self, args): def __init__(self, args):
"""Initializes the plugin with the given command line args""" """Initializes the plugin with the given command line args"""
self._temp_dir = tempfile.mkdtemp() self._temp_dir = tempfile.mkdtemp()
# tempfile.mkdtemp() creates folders with too restrictive permissions to be accessible
# to an Apache worker, leading to HTTP challenge failures. Let's fix that.
os.chmod(self._temp_dir, 0o755)
self.le_config = util.create_le_config(self._temp_dir) self.le_config = util.create_le_config(self._temp_dir)
config_dir = util.extract_configs(args.configs, self._temp_dir) config_dir = util.extract_configs(args.configs, self._temp_dir)
self._configs = [ self._configs = [
@@ -1,5 +1,6 @@
"""Tests Certbot plugins against different server configurations.""" """Tests Certbot plugins against different server configurations."""
import argparse import argparse
import contextlib
import filecmp import filecmp
import logging import logging
import os import os
@@ -7,6 +8,7 @@ import shutil
import tempfile import tempfile
import time import time
import sys import sys
from urllib3.util import connection
import OpenSSL import OpenSSL
@@ -64,18 +66,19 @@ def test_authenticator(plugin, config, temp_dir):
"Plugin failed to complete %s for %s in %s", "Plugin failed to complete %s for %s in %s",
type(achalls[i]), achalls[i].domain, config) type(achalls[i]), achalls[i].domain, config)
success = False success = False
elif isinstance(responses[i], challenges.TLSSNI01Response): elif isinstance(responses[i], challenges.HTTP01Response):
verified = responses[i].simple_verify(achalls[i].chall, # We fake the DNS resolution to ensure that any domain is resolved
achalls[i].domain, # to the local HTTP server setup for the compatibility tests
util.JWK.public_key(), with _fake_dns_resolution("127.0.0.1"):
host="127.0.0.1", verified = responses[i].simple_verify(
port=plugin.https_port) achalls[i].chall, achalls[i].domain,
util.JWK.public_key(), port=plugin.http_port)
if verified: if verified:
logger.info( logger.info(
"tls-sni-01 verification for %s succeeded", achalls[i].domain) "http-01 verification for %s succeeded", achalls[i].domain)
else: else:
logger.error( logger.error(
"**** tls-sni-01 verification for %s in %s failed", "**** http-01 verification for %s in %s failed",
achalls[i].domain, config) achalls[i].domain, config)
success = False success = False
@@ -102,9 +105,9 @@ def _create_achalls(plugin):
for domain in names: for domain in names:
prefs = plugin.get_chall_pref(domain) prefs = plugin.get_chall_pref(domain)
for chall_type in prefs: for chall_type in prefs:
if chall_type == challenges.TLSSNI01: if chall_type == challenges.HTTP01:
chall = challenges.TLSSNI01( chall = challenges.HTTP01(
token=os.urandom(challenges.TLSSNI01.TOKEN_SIZE)) token=os.urandom(challenges.HTTP01.TOKEN_SIZE))
challb = acme_util.chall_to_challb( challb = acme_util.chall_to_challb(
chall, messages.STATUS_PENDING) chall, messages.STATUS_PENDING)
achall = achallenges.KeyAuthorizationAnnotatedChallenge( achall = achallenges.KeyAuthorizationAnnotatedChallenge(
@@ -369,5 +372,21 @@ def main():
sys.exit(1) sys.exit(1)
@contextlib.contextmanager
def _fake_dns_resolution(resolved_ip):
"""Monkey patch urllib3 to make any hostname be resolved to the provided IP"""
_original_create_connection = connection.create_connection
def _patched_create_connection(address, *args, **kwargs):
_, port = address
return _original_create_connection((resolved_ip, port), *args, **kwargs)
try:
connection.create_connection = _patched_create_connection
yield
finally:
connection.create_connection = _original_create_connection
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+1 -3
View File
@@ -43,8 +43,6 @@ nginx -v
reload_nginx reload_nginx
certbot_test_nginx --domains nginx.wtf run certbot_test_nginx --domains nginx.wtf run
test_deployment_and_rollback nginx.wtf test_deployment_and_rollback nginx.wtf
certbot_test_nginx --domains nginx-tls.wtf run --preferred-challenges tls-sni
test_deployment_and_rollback nginx-tls.wtf
certbot_test_nginx --domains nginx2.wtf --preferred-challenges http certbot_test_nginx --domains nginx2.wtf --preferred-challenges http
test_deployment_and_rollback nginx2.wtf test_deployment_and_rollback nginx2.wtf
# Overlapping location block and server-block-level return 301 # Overlapping location block and server-block-level return 301
@@ -70,4 +68,4 @@ test_deployment_and_rollback nginx6.wtf
# top # top
nginx -c $nginx_root/nginx.conf -s stop nginx -c $nginx_root/nginx.conf -s stop
coverage report --fail-under 75 --include 'certbot-nginx/*' --show-missing coverage report --fail-under 72 --include 'certbot-nginx/*' --show-missing
+6 -70
View File
@@ -15,34 +15,6 @@ from certbot import reverter
from certbot.plugins import common from certbot.plugins import common
class ManualTlsSni01(common.TLSSNI01):
"""TLS-SNI-01 authenticator for the Manual plugin
:ivar configurator: Authenticator object
:type configurator: :class:`~certbot.plugins.manual.Authenticator`
:ivar list achalls: Annotated
class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
challenges
:param list indices: Meant to hold indices of challenges in a
larger array. NginxTlsSni01 is capable of solving many challenges
at once which causes an indexing issue within NginxConfigurator
who must return all responses in order. Imagine NginxConfigurator
maintaining state about where all of the http-01 Challenges,
TLS-SNI-01 Challenges belong in the response array. This is an
optional utility.
:param str challenge_conf: location of the challenge config file
"""
def perform(self):
"""Create the SSL certificates and private keys"""
for achall in self.achalls:
self._setup_challenge_cert(achall)
@zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory) @zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(common.Plugin): class Authenticator(common.Plugin):
@@ -63,14 +35,9 @@ class Authenticator(common.Plugin):
'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' 'type of challenge. $CERTBOT_DOMAIN will always contain the domain '
'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION '
'is the validation string, and $CERTBOT_TOKEN is the filename of the ' 'is the validation string, and $CERTBOT_TOKEN is the filename of the '
'resource requested when performing an HTTP-01 challenge. When ' 'resource requested when performing an HTTP-01 challenge. An additional '
'performing a TLS-SNI-01 challenge, $CERTBOT_SNI_DOMAIN will contain ' 'cleanup script can also be provided and can use the additional variable '
'the SNI name for which the ACME server expects to be presented with ' '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.')
'the self-signed certificate located at $CERTBOT_CERT_PATH. The '
'secret key needed to complete the TLS handshake is located at '
'$CERTBOT_KEY_PATH. An additional cleanup script can also be '
'provided and can use the additional variable $CERTBOT_AUTH_OUTPUT '
'which contains the stdout output from the auth script.')
_DNS_INSTRUCTIONS = """\ _DNS_INSTRUCTIONS = """\
Please deploy a DNS TXT record under the name Please deploy a DNS TXT record under the name
{domain} with the following value: {domain} with the following value:
@@ -86,14 +53,6 @@ Create a file containing just this data:
And make it available on your web server at this URL: And make it available on your web server at this URL:
{uri} {uri}
"""
_TLSSNI_INSTRUCTIONS = """\
Configure the service listening on port {port} to present the certificate
{cert}
using the secret key
{key}
when it receives a TLS ClientHello with the SNI extension set to
{sni_domain}
""" """
_SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """
(This must be set up in addition to the previous challenges; do not remove, (This must be set up in addition to the previous challenges; do not remove,
@@ -112,7 +71,6 @@ permitted by DNS standards.)
self.reverter.recovery_routine() self.reverter.recovery_routine()
self.env = dict() \ self.env = dict() \
# type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]]
self.tls_sni_01 = None
self.subsequent_dns_challenge = False self.subsequent_dns_challenge = False
self.subsequent_any_challenge = False self.subsequent_any_challenge = False
@@ -149,7 +107,7 @@ permitted by DNS standards.)
def get_chall_pref(self, domain): def get_chall_pref(self, domain):
# pylint: disable=missing-docstring,no-self-use,unused-argument # pylint: disable=missing-docstring,no-self-use,unused-argument
return [challenges.HTTP01, challenges.DNS01, challenges.TLSSNI01] return [challenges.HTTP01, challenges.DNS01]
def perform(self, achalls): # pylint: disable=missing-docstring def perform(self, achalls): # pylint: disable=missing-docstring
self._verify_ip_logging_ok() self._verify_ip_logging_ok()
@@ -160,12 +118,6 @@ permitted by DNS standards.)
responses = [] responses = []
for achall in achalls: for achall in achalls:
if isinstance(achall.chall, challenges.TLSSNI01):
# Make a new ManualTlsSni01 instance for each challenge
# because the manual plugin deals with one challenge at a time.
self.tls_sni_01 = ManualTlsSni01(self)
self.tls_sni_01.add_chall(achall)
self.tls_sni_01.perform()
perform_achall(achall) perform_achall(achall)
responses.append(achall.response(achall.account_key)) responses.append(achall.response(achall.account_key))
return responses return responses
@@ -191,16 +143,6 @@ permitted by DNS standards.)
env['CERTBOT_TOKEN'] = achall.chall.encode('token') env['CERTBOT_TOKEN'] = achall.chall.encode('token')
else: else:
os.environ.pop('CERTBOT_TOKEN', None) os.environ.pop('CERTBOT_TOKEN', None)
if isinstance(achall.chall, challenges.TLSSNI01):
env['CERTBOT_CERT_PATH'] = self.tls_sni_01.get_cert_path(achall)
env['CERTBOT_KEY_PATH'] = self.tls_sni_01.get_key_path(achall)
env['CERTBOT_SNI_DOMAIN'] = self.tls_sni_01.get_z_domain(achall)
os.environ.pop('CERTBOT_VALIDATION', None)
env.pop('CERTBOT_VALIDATION')
else:
os.environ.pop('CERTBOT_CERT_PATH', None)
os.environ.pop('CERTBOT_KEY_PATH', None)
os.environ.pop('CERTBOT_SNI_DOMAIN', None)
os.environ.update(env) os.environ.update(env)
_, out = self._execute_hook('auth-hook') _, out = self._execute_hook('auth-hook')
env['CERTBOT_AUTH_OUTPUT'] = out.strip() env['CERTBOT_AUTH_OUTPUT'] = out.strip()
@@ -213,17 +155,11 @@ permitted by DNS standards.)
achall=achall, encoded_token=achall.chall.encode('token'), achall=achall, encoded_token=achall.chall.encode('token'),
port=self.config.http01_port, port=self.config.http01_port,
uri=achall.chall.uri(achall.domain), validation=validation) uri=achall.chall.uri(achall.domain), validation=validation)
elif isinstance(achall.chall, challenges.DNS01): else:
assert isinstance(achall.chall, challenges.DNS01)
msg = self._DNS_INSTRUCTIONS.format( msg = self._DNS_INSTRUCTIONS.format(
domain=achall.validation_domain_name(achall.domain), domain=achall.validation_domain_name(achall.domain),
validation=validation) validation=validation)
else:
assert isinstance(achall.chall, challenges.TLSSNI01)
msg = self._TLSSNI_INSTRUCTIONS.format(
cert=self.tls_sni_01.get_cert_path(achall),
key=self.tls_sni_01.get_key_path(achall),
port=self.config.tls_sni_01_port,
sni_domain=self.tls_sni_01.get_z_domain(achall))
if isinstance(achall.chall, challenges.DNS01): if isinstance(achall.chall, challenges.DNS01):
if self.subsequent_dns_challenge: if self.subsequent_dns_challenge:
# 2nd or later dns-01 challenge # 2nd or later dns-01 challenge
+7 -44
View File
@@ -22,8 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase):
self.http_achall = acme_util.HTTP01_A self.http_achall = acme_util.HTTP01_A
self.dns_achall = acme_util.DNS01_A self.dns_achall = acme_util.DNS01_A
self.dns_achall_2 = acme_util.DNS01_A_2 self.dns_achall_2 = acme_util.DNS01_A_2
self.tls_sni_achall = acme_util.TLSSNI01_A self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2]
self.achalls = [self.http_achall, self.dns_achall, self.tls_sni_achall, self.dns_achall_2]
for d in ["config_dir", "work_dir", "in_progress"]: for d in ["config_dir", "work_dir", "in_progress"]:
os.mkdir(os.path.join(self.tempdir, d)) os.mkdir(os.path.join(self.tempdir, d))
# "backup_dir" and "temp_checkpoint_dir" get created in # "backup_dir" and "temp_checkpoint_dir" get created in
@@ -38,8 +37,7 @@ class AuthenticatorTest(test_util.TempDirTestCase):
backup_dir=os.path.join(self.tempdir, "backup_dir"), backup_dir=os.path.join(self.tempdir, "backup_dir"),
temp_checkpoint_dir=os.path.join( temp_checkpoint_dir=os.path.join(
self.tempdir, "temp_checkpoint_dir"), self.tempdir, "temp_checkpoint_dir"),
in_progress_dir=os.path.join(self.tempdir, "in_progess"), in_progress_dir=os.path.join(self.tempdir, "in_progess"))
tls_sni_01_port=5001)
from certbot.plugins.manual import Authenticator from certbot.plugins.manual import Authenticator
self.auth = Authenticator(self.config, name='manual') self.auth = Authenticator(self.config, name='manual')
@@ -58,9 +56,7 @@ class AuthenticatorTest(test_util.TempDirTestCase):
def test_get_chall_pref(self): def test_get_chall_pref(self):
self.assertEqual(self.auth.get_chall_pref('example.org'), self.assertEqual(self.auth.get_chall_pref('example.org'),
[challenges.HTTP01, [challenges.HTTP01, challenges.DNS01])
challenges.DNS01,
challenges.TLSSNI01])
@test_util.patch_get_utility() @test_util.patch_get_utility()
def test_ip_logging_not_ok(self, mock_get_utility): def test_ip_logging_not_ok(self, mock_get_utility):
@@ -79,18 +75,13 @@ class AuthenticatorTest(test_util.TempDirTestCase):
'{0} -c "from __future__ import print_function;' '{0} -c "from __future__ import print_function;'
'import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' 'import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));'
'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));'
'print(os.environ.get(\'CERTBOT_CERT_PATH\', \'nocert\'));'
'print(os.environ.get(\'CERTBOT_KEY_PATH\', \'nokey\'));'
'print(os.environ.get(\'CERTBOT_SNI_DOMAIN\', \'nosnidomain\'));'
'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"'
.format(sys.executable)) .format(sys.executable))
dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( dns_expected = '{0}\n{1}\n{2}'.format(
self.dns_achall.domain, 'notoken', self.dns_achall.domain, 'notoken',
'nocert', 'nokey', 'nosnidomain',
self.dns_achall.validation(self.dns_achall.account_key)) self.dns_achall.validation(self.dns_achall.account_key))
http_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( http_expected = '{0}\n{1}\n{2}'.format(
self.http_achall.domain, self.http_achall.chall.encode('token'), self.http_achall.domain, self.http_achall.chall.encode('token'),
'nocert', 'nokey', 'nosnidomain',
self.http_achall.validation(self.http_achall.account_key)) self.http_achall.validation(self.http_achall.account_key))
self.assertEqual( self.assertEqual(
@@ -102,17 +93,6 @@ class AuthenticatorTest(test_util.TempDirTestCase):
self.assertEqual( self.assertEqual(
self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'], self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'],
http_expected) http_expected)
# tls_sni_01 challenge must be perform()ed above before we can
# get the cert_path and key_path.
tls_sni_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format(
self.tls_sni_achall.domain, 'notoken',
self.auth.tls_sni_01.get_cert_path(self.tls_sni_achall),
self.auth.tls_sni_01.get_key_path(self.tls_sni_achall),
self.auth.tls_sni_01.get_z_domain(self.tls_sni_achall),
'novalidation')
self.assertEqual(
self.auth.env[self.tls_sni_achall]['CERTBOT_AUTH_OUTPUT'],
tls_sni_expected)
@test_util.patch_get_utility() @test_util.patch_get_utility()
def test_manual_perform(self, mock_get_utility): def test_manual_perform(self, mock_get_utility):
@@ -122,13 +102,8 @@ class AuthenticatorTest(test_util.TempDirTestCase):
[achall.response(achall.account_key) for achall in self.achalls]) [achall.response(achall.account_key) for achall in self.achalls])
for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list): for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list):
achall = self.achalls[i] achall = self.achalls[i]
if isinstance(achall.chall, challenges.TLSSNI01): self.assertTrue(
self.assertTrue( achall.validation(achall.account_key) in args[0])
self.auth.tls_sni_01.get_cert_path(
self.tls_sni_achall) in args[0])
else:
self.assertTrue(
achall.validation(achall.account_key) in args[0])
self.assertFalse(kwargs['wrap']) self.assertFalse(kwargs['wrap'])
@test_util.broken_on_windows @test_util.broken_on_windows
@@ -153,18 +128,6 @@ class AuthenticatorTest(test_util.TempDirTestCase):
achall.chall.encode('token')) achall.chall.encode('token'))
else: else:
self.assertFalse('CERTBOT_TOKEN' in os.environ) self.assertFalse('CERTBOT_TOKEN' in os.environ)
if isinstance(achall.chall, challenges.TLSSNI01):
self.assertEqual(
os.environ['CERTBOT_CERT_PATH'],
self.auth.tls_sni_01.get_cert_path(achall))
self.assertEqual(
os.environ['CERTBOT_KEY_PATH'],
self.auth.tls_sni_01.get_key_path(achall))
self.assertFalse(
os.path.exists(os.environ['CERTBOT_CERT_PATH']))
self.assertFalse(
os.path.exists(os.environ['CERTBOT_KEY_PATH']))
if __name__ == '__main__': if __name__ == '__main__':
+10 -43
View File
@@ -6,7 +6,7 @@ import socket
# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi
from socket import errno as socket_errors # type: ignore from socket import errno as socket_errors # type: ignore
import OpenSSL import OpenSSL # pylint: disable=unused-import
import six import six
import zope.interface import zope.interface
@@ -55,25 +55,21 @@ class ServerManager(object):
:param int port: Port to run the server on. :param int port: Port to run the server on.
:param challenge_type: Subclass of `acme.challenges.Challenge`, :param challenge_type: Subclass of `acme.challenges.Challenge`,
either `acme.challenge.HTTP01` or `acme.challenges.TLSSNI01`. currently only `acme.challenge.HTTP01`.
:param str listenaddr: (optional) The address to listen on. Defaults to all addrs. :param str listenaddr: (optional) The address to listen on. Defaults to all addrs.
:returns: DualNetworkedServers instance. :returns: DualNetworkedServers instance.
:rtype: ACMEServerMixin :rtype: ACMEServerMixin
""" """
assert challenge_type in (challenges.TLSSNI01, challenges.HTTP01) assert challenge_type == challenges.HTTP01
if port in self._instances: if port in self._instances:
return self._instances[port] return self._instances[port]
address = (listenaddr, port) address = (listenaddr, port)
try: try:
if challenge_type is challenges.TLSSNI01: servers = acme_standalone.HTTP01DualNetworkedServers(
servers = acme_standalone.TLSSNI01DualNetworkedServers( address, self.http_01_resources)
address, self.certs) # type: acme_standalone.BaseDualNetworkedServers
else: # challenges.HTTP01
servers = acme_standalone.HTTP01DualNetworkedServers(
address, self.http_01_resources)
except socket.error as error: except socket.error as error:
raise errors.StandaloneBindError(error, port) raise errors.StandaloneBindError(error, port)
@@ -96,8 +92,6 @@ class ServerManager(object):
for sockname in instance.getsocknames(): for sockname in instance.getsocknames():
logger.debug("Stopping server at %s:%d...", logger.debug("Stopping server at %s:%d...",
*sockname[:2]) *sockname[:2])
# Not calling server_close causes problems when renewing multiple
# certs with `certbot renew` using TLSSNI01 and PyOpenSSL 0.13
instance.shutdown_and_server_close() instance.shutdown_and_server_close()
del self._instances[port] del self._instances[port]
@@ -114,7 +108,7 @@ class ServerManager(object):
return self._instances.copy() return self._instances.copy()
SUPPORTED_CHALLENGES = [challenges.HTTP01, challenges.TLSSNI01] \ SUPPORTED_CHALLENGES = [challenges.HTTP01] \
# type: List[Type[challenges.KeyAuthorizationChallenge]] # type: List[Type[challenges.KeyAuthorizationChallenge]]
@@ -132,8 +126,6 @@ class SupportedChallengesAction(argparse.Action):
def _convert_and_validate(self, data): def _convert_and_validate(self, data):
"""Validate the value of supported challenges provided by the user. """Validate the value of supported challenges provided by the user.
References to "dvsni" are automatically converted to "tls-sni-01".
:param str data: comma delimited list of challenge types :param str data: comma delimited list of challenge types
:returns: validated and converted list of challenge types :returns: validated and converted list of challenge types
@@ -141,15 +133,6 @@ class SupportedChallengesAction(argparse.Action):
""" """
challs = data.split(",") challs = data.split(",")
# tls-sni-01 was dvsni during private beta
if "dvsni" in challs:
logger.info(
"Updating legacy standalone_supported_challenges value")
challs = [challenges.TLSSNI01.typ if chall == "dvsni" else chall
for chall in challs]
data = ",".join(challs)
unrecognized = [name for name in challs unrecognized = [name for name in challs
if name not in challenges.Challenge.TYPES] if name not in challenges.Challenge.TYPES]
@@ -177,7 +160,7 @@ class Authenticator(common.Plugin):
"""Standalone Authenticator. """Standalone Authenticator.
This authenticator creates its own ephemeral TCP listener on the This authenticator creates its own ephemeral TCP listener on the
necessary port in order to respond to incoming tls-sni-01 and http-01 necessary port in order to respond to incoming http-01
challenges from the certificate authority. Therefore, it does not challenges from the certificate authority. Therefore, it does not
rely on any existing server program. rely on any existing server program.
""" """
@@ -187,10 +170,6 @@ class Authenticator(common.Plugin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs) super(Authenticator, self).__init__(*args, **kwargs)
# one self-signed key for all tls-sni-01 certificates
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
self.served = collections.defaultdict(set) # type: ServedType self.served = collections.defaultdict(set) # type: ServedType
# Stuff below is shared across threads (i.e. servers read # Stuff below is shared across threads (i.e. servers read
@@ -219,9 +198,8 @@ class Authenticator(common.Plugin):
def more_info(self): # pylint: disable=missing-docstring def more_info(self): # pylint: disable=missing-docstring
return("This authenticator creates its own ephemeral TCP listener " return("This authenticator creates its own ephemeral TCP listener "
"on the necessary port in order to respond to incoming " "on the necessary port in order to respond to incoming "
"tls-sni-01 and http-01 challenges from the certificate " "http-01 challenges from the certificate authority. Therefore, "
"authority. Therefore, it does not rely on any existing " "it does not rely on any existing server program.")
"server program.")
def prepare(self): # pylint: disable=missing-docstring def prepare(self): # pylint: disable=missing-docstring
pass pass
@@ -241,10 +219,7 @@ class Authenticator(common.Plugin):
_handle_perform_error(error) _handle_perform_error(error)
def _perform_single(self, achall): def _perform_single(self, achall):
if isinstance(achall.chall, challenges.HTTP01): servers, response = self._perform_http_01(achall)
servers, response = self._perform_http_01(achall)
else: # tls-sni-01
servers, response = self._perform_tls_sni_01(achall)
self.served[servers].add(achall) self.served[servers].add(achall)
return response return response
@@ -258,14 +233,6 @@ class Authenticator(common.Plugin):
self.http_01_resources.add(resource) self.http_01_resources.add(resource)
return servers, response return servers, response
def _perform_tls_sni_01(self, achall):
port = self.config.tls_sni_01_port
addr = self.config.tls_sni_01_address
servers = self.servers.run(port, challenges.TLSSNI01, listenaddr=addr)
response, (cert, _) = achall.response_and_validation(cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
return servers, response
def cleanup(self, achalls): # pylint: disable=missing-docstring def cleanup(self, achalls): # pylint: disable=missing-docstring
# reduce self.served and close servers if no challenges are served # reduce self.served and close servers if no challenges are served
for unused_servers, server_achalls in self.served.items(): for unused_servers, server_achalls in self.served.items():
+10 -22
View File
@@ -44,9 +44,6 @@ class ServerManagerTest(unittest.TestCase):
self.mgr.stop(port=port) self.mgr.stop(port=port)
self.assertEqual(self.mgr.running(), {}) self.assertEqual(self.mgr.running(), {})
def test_run_stop_tls_sni_01(self):
self._test_run_stop(challenges.TLSSNI01)
def test_run_stop_http_01(self): def test_run_stop_http_01(self):
self._test_run_stop(challenges.HTTP01) self._test_run_stop(challenges.HTTP01)
@@ -98,10 +95,7 @@ class SupportedChallengesActionTest(unittest.TestCase):
self.parser.add_argument(self.flag, action=SupportedChallengesAction) self.parser.add_argument(self.flag, action=SupportedChallengesAction)
def test_correct(self): def test_correct(self):
self.assertEqual("tls-sni-01", self._call("tls-sni-01"))
self.assertEqual("http-01", self._call("http-01")) self.assertEqual("http-01", self._call("http-01"))
self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01"))
def test_unrecognized(self): def test_unrecognized(self):
assert "foo" not in challenges.Challenge.TYPES assert "foo" not in challenges.Challenge.TYPES
@@ -110,11 +104,6 @@ class SupportedChallengesActionTest(unittest.TestCase):
def test_not_subset(self): def test_not_subset(self):
self.assertRaises(SystemExit, self._call, "dns") self.assertRaises(SystemExit, self._call, "dns")
def test_dvsni(self):
self.assertEqual("tls-sni-01", self._call("dvsni"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,dvsni"))
self.assertEqual("tls-sni-01,http-01", self._call("dvsni,http-01"))
def get_open_port(): def get_open_port():
"""Gets an open port number from the OS.""" """Gets an open port number from the OS."""
@@ -132,31 +121,31 @@ class AuthenticatorTest(unittest.TestCase):
from certbot.plugins.standalone import Authenticator from certbot.plugins.standalone import Authenticator
self.config = mock.MagicMock( self.config = mock.MagicMock(
tls_sni_01_port=get_open_port(), http01_port=get_open_port(), http01_port=get_open_port(),
standalone_supported_challenges="tls-sni-01,http-01") standalone_supported_challenges="http-01")
self.auth = Authenticator(self.config, name="standalone") self.auth = Authenticator(self.config, name="standalone")
self.auth.servers = mock.MagicMock() self.auth.servers = mock.MagicMock()
def test_supported_challenges(self): def test_supported_challenges(self):
self.assertEqual(self.auth.supported_challenges, self.assertEqual(self.auth.supported_challenges,
[challenges.TLSSNI01, challenges.HTTP01]) [challenges.HTTP01])
def test_supported_challenges_configured(self): def test_supported_challenges_configured(self):
self.config.standalone_supported_challenges = "tls-sni-01" self.config.standalone_supported_challenges = "http-01"
self.assertEqual(self.auth.supported_challenges, self.assertEqual(self.auth.supported_challenges,
[challenges.TLSSNI01]) [challenges.HTTP01])
def test_more_info(self): def test_more_info(self):
self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) self.assertTrue(isinstance(self.auth.more_info(), six.string_types))
def test_get_chall_pref(self): def test_get_chall_pref(self):
self.assertEqual(self.auth.get_chall_pref(domain=None), self.assertEqual(self.auth.get_chall_pref(domain=None),
[challenges.TLSSNI01, challenges.HTTP01]) [challenges.HTTP01])
def test_get_chall_pref_configured(self): def test_get_chall_pref_configured(self):
self.config.standalone_supported_challenges = "tls-sni-01" self.config.standalone_supported_challenges = "http-01"
self.assertEqual(self.auth.get_chall_pref(domain=None), self.assertEqual(self.auth.get_chall_pref(domain=None),
[challenges.TLSSNI01]) [challenges.HTTP01])
def test_perform(self): def test_perform(self):
achalls = self._get_achalls() achalls = self._get_achalls()
@@ -212,10 +201,8 @@ class AuthenticatorTest(unittest.TestCase):
key = jose.JWK.load(test_util.load_vector('rsa512_key.pem')) key = jose.JWK.load(test_util.load_vector('rsa512_key.pem'))
http_01 = achallenges.KeyAuthorizationAnnotatedChallenge( http_01 = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, domain=domain, account_key=key) challb=acme_util.HTTP01_P, domain=domain, account_key=key)
tls_sni_01 = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.TLSSNI01_P, domain=domain, account_key=key)
return [http_01, tls_sni_01] return [http_01]
def test_cleanup(self): def test_cleanup(self):
self.auth.servers.running.return_value = { self.auth.servers.running.return_value = {
@@ -243,5 +230,6 @@ class AuthenticatorTest(unittest.TestCase):
"server1": set(), "server2": set([])}) "server1": set(), "server2": set([])})
self.auth.servers.stop.assert_called_with(2) self.auth.servers.stop.assert_called_with(2)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() # pragma: no cover unittest.main() # pragma: no cover
-6
View File
@@ -12,12 +12,6 @@ fi
cd ${BOULDERPATH} cd ${BOULDERPATH}
# Since https://github.com/letsencrypt/boulder/commit/92e8e1708a725e9d08a5da2f4a7132320ed2158b,
# Boulder support for tls-sni-01 challenges is disabled. We still need to support it until this
# challenge is officially removed from ACME CA server on production, and also removed from Certbot.
# This sed command reactivate tls-sni-01 challenges inplace temporarily.
sed -i 's/tls-alpn-01/tls-sni-01/g' test/config/ra.json
docker-compose up -d boulder docker-compose up -d boulder
set +x # reduce verbosity while waiting for boulder set +x # reduce verbosity while waiting for boulder
+8 -8
View File
@@ -223,20 +223,20 @@ common plugins --init --prepare | grep webroot
# We start a server listening on the port for the # We start a server listening on the port for the
# unrequested challenge to prevent regressions in #3601. # unrequested challenge to prevent regressions in #3601.
python ./tests/run_http_server.py $http_01_port & python ./tests/run_http_server.py $tls_alpn_01_port &
python_server_pid=$! python_server_pid=$!
certname="le1.wtf" certname="le1.wtf"
common --domains le1.wtf --preferred-challenges tls-sni-01 auth \ common --domains le1.wtf --preferred-challenges http-01 auth \
--cert-name $certname \ --cert-name $certname \
--pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \ --pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \
--post-hook 'echo wtf.post >> "$HOOK_TEST"'\ --post-hook 'echo wtf.post >> "$HOOK_TEST"'\
--deploy-hook 'echo deploy >> "$HOOK_TEST"' --deploy-hook 'echo deploy >> "$HOOK_TEST"'
kill $python_server_pid
CheckDeployHook $certname CheckDeployHook $certname
python ./tests/run_http_server.py $tls_sni_01_port & # Previous test used to be a tls-sni-01 challenge that is not supported anymore.
python_server_pid=$! # Now it is a http-01 challenge and this makes it a duplicate of the following test.
# But removing it would break many tests here, as they are strongly coupled.
# See https://github.com/certbot/certbot/pull/6852
certname="le2.wtf" certname="le2.wtf"
common --domains le2.wtf --preferred-challenges http-01 run \ common --domains le2.wtf --preferred-challenges http-01 run \
--cert-name $certname \ --cert-name $certname \
@@ -256,7 +256,7 @@ common certonly -a manual -d le.wtf --rsa-key-size 4096 --cert-name $certname \
CheckRenewHook $certname CheckRenewHook $certname
certname="dns.le.wtf" certname="dns.le.wtf"
common -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni run \ common -a manual -d dns.le.wtf --preferred-challenges dns run \
--cert-name $certname \ --cert-name $certname \
--manual-auth-hook ./tests/manual-dns-auth.sh \ --manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh \ --manual-cleanup-hook ./tests/manual-dns-cleanup.sh \
@@ -398,7 +398,7 @@ CheckDirHooks 1
# with fail. # with fail.
common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \ common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \
--allow-subset-of-names \ --allow-subset-of-names \
--preferred-challenges dns,tls-sni \ --preferred-challenges dns \
--manual-auth-hook ./tests/manual-dns-auth.sh \ --manual-auth-hook ./tests/manual-dns-auth.sh \
--manual-cleanup-hook ./tests/manual-dns-cleanup.sh --manual-cleanup-hook ./tests/manual-dns-cleanup.sh
+3 -3
View File
@@ -3,10 +3,10 @@
root=${root:-$(mktemp -d -t leitXXXX)} root=${root:-$(mktemp -d -t leitXXXX)}
echo "Root integration tests directory: $root" echo "Root integration tests directory: $root"
config_dir="$root/conf" config_dir="$root/conf"
tls_sni_01_port=5001 tls_alpn_01_port=5001
http_01_port=5002 http_01_port=5002
sources="acme/,$(ls -dm certbot*/ | tr -d ' \n')" sources="acme/,$(ls -dm certbot*/ | tr -d ' \n')"
export root config_dir tls_sni_01_port http_01_port sources export root config_dir tls_alpn_01_port http_01_port sources
certbot_path="$(command -v certbot)" certbot_path="$(command -v certbot)"
# Flags that are added here will be added to Certbot calls within # Flags that are added here will be added to Certbot calls within
# certbot_test_no_force_renew. # certbot_test_no_force_renew.
@@ -60,7 +60,7 @@ certbot_test_no_force_renew () {
"$certbot_path" \ "$certbot_path" \
--server "${SERVER:-http://localhost:4000/directory}" \ --server "${SERVER:-http://localhost:4000/directory}" \
--no-verify-ssl \ --no-verify-ssl \
--tls-sni-01-port $tls_sni_01_port \ --tls-sni-01-port $tls_alpn_01_port \
--http-01-port $http_01_port \ --http-01-port $http_01_port \
--manual-public-ip-logging-ok \ --manual-public-ip-logging-ok \
$other_flags \ $other_flags \
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# Developer virtualenv setup for Certbot client # Developer virtualenv setup for Certbot client
import _venv_common import _venv_common