mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 00:35:50 +02:00
Add dns-01 challenge support to the Manual plugin
This commit is contained in:
+90
-36
@@ -4,26 +4,30 @@ import logging
|
||||
import pipes
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import six
|
||||
import zope.component
|
||||
import zope.interface
|
||||
|
||||
from functools import partial
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot.plugins import common
|
||||
from certbot.plugins import common, util
|
||||
from certbot.util import busy_wait
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SUPPORTED_CHALLENGES = [challenges.HTTP01, challenges.DNS01]
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
@@ -41,7 +45,16 @@ class Authenticator(common.Plugin):
|
||||
|
||||
description = "Manually configure an HTTP server"
|
||||
|
||||
MESSAGE_TEMPLATE = """\
|
||||
MESSAGE_TEMPLATE = {
|
||||
"dns-01": """\
|
||||
Make sure your dns configuration content the following key before continuing:
|
||||
|
||||
{validation}
|
||||
|
||||
if you didn't, make sure to add the validation as TXT record into your domain
|
||||
configuration.
|
||||
""",
|
||||
"http-01": """\
|
||||
Make sure your web server displays the following content at
|
||||
{uri} before continuing:
|
||||
|
||||
@@ -51,7 +64,7 @@ If you don't have HTTP server configured, you can run the following
|
||||
command on the target server (as root):
|
||||
|
||||
{command}
|
||||
"""
|
||||
"""}
|
||||
|
||||
# a disclaimer about your current IP being transmitted to Let's Encrypt's servers.
|
||||
IP_DISCLAIMER = """\
|
||||
@@ -86,10 +99,23 @@ s.serve_forever()" """
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
validator = partial(util.supported_challenges_validator,
|
||||
supported=SUPPORTED_CHALLENGES)
|
||||
|
||||
add("test-mode", action="store_true",
|
||||
help="Test mode. Executes the manual command in subprocess.")
|
||||
add("public-ip-logging-ok", action="store_true",
|
||||
help="Automatically allows public IP logging.")
|
||||
add("supported-challenges",
|
||||
help="Supported challenges. Preferred in the order they are listed.",
|
||||
type=validator,
|
||||
default="http-01")
|
||||
|
||||
@property
|
||||
def supported_challenges(self):
|
||||
"""Challenges supported by this plugin."""
|
||||
return [challenges.Challenge.TYPES[name] for name in
|
||||
self.conf("supported-challenges").split(",")]
|
||||
|
||||
def prepare(self): # pylint: disable=missing-docstring,no-self-use
|
||||
if self.config.noninteractive_mode and not self.conf("test-mode"):
|
||||
@@ -97,38 +123,35 @@ s.serve_forever()" """
|
||||
|
||||
def more_info(self): # pylint: disable=missing-docstring,no-self-use
|
||||
return ("This plugin requires user's manual intervention in setting "
|
||||
"up an HTTP server for solving http-01 challenges and thus "
|
||||
"up an HTTP server when solving http-01 challenges and thus "
|
||||
"does not need to be run as a privileged process. "
|
||||
"Alternatively shows instructions on how to use Python's "
|
||||
"built-in HTTP server.")
|
||||
"built-in HTTP server."
|
||||
"When solving dns-01 challenges, it simply needs to wait for "
|
||||
"the proper configuration of the domain's dns")
|
||||
|
||||
def get_chall_pref(self, domain):
|
||||
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||
return [challenges.HTTP01]
|
||||
return self.supported_challenges
|
||||
|
||||
def perform(self, achalls): # pylint: disable=missing-docstring
|
||||
def perform(self, achalls):
|
||||
# pylint: disable=missing-docstring
|
||||
mapping = {"http-01": self._perform_http01_challenge,
|
||||
"dns-01": self._perform_dns01_challenge}
|
||||
responses = []
|
||||
# TODO: group achalls by the same socket.gethostbyname(_ex)
|
||||
# and prompt only once per server (one "echo -n" per domain)
|
||||
for achall in achalls:
|
||||
responses.append(self._perform_single(achall))
|
||||
responses.append(mapping[achall.typ](achall))
|
||||
return responses
|
||||
|
||||
@classmethod
|
||||
def _test_mode_busy_wait(cls, port):
|
||||
while True:
|
||||
time.sleep(1)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect(("localhost", port))
|
||||
except socket.error: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
sock.close()
|
||||
def cleanup(self, achalls):
|
||||
# pylint: disable=missing-docstring
|
||||
for achall in achalls:
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
self._cleanup_http01_challenge(achall)
|
||||
|
||||
def _perform_single(self, achall):
|
||||
def _perform_http01_challenge(self, achall):
|
||||
# same path for each challenge response would be easier for
|
||||
# users, but will not work if multiple domains point at the
|
||||
# same server: default command doesn't support virtual hosts
|
||||
@@ -161,7 +184,8 @@ s.serve_forever()" """
|
||||
logger.debug("Manual command running as PID %s.", self._httpd.pid)
|
||||
# give it some time to bootstrap, before we try to verify
|
||||
# (cert generation in case of simpleHttpS might take time)
|
||||
self._test_mode_busy_wait(port)
|
||||
busy_wait(port)
|
||||
|
||||
if self._httpd.poll() is not None:
|
||||
raise errors.Error("Couldn't execute manual command")
|
||||
else:
|
||||
@@ -171,10 +195,14 @@ s.serve_forever()" """
|
||||
cli_flag="--manual-public-ip-logging-ok"):
|
||||
raise errors.PluginError("Must agree to IP logging to proceed")
|
||||
|
||||
self._notify_and_wait(self.MESSAGE_TEMPLATE.format(
|
||||
validation=validation, response=response,
|
||||
message = self._get_message(achall)
|
||||
|
||||
self._notify_and_wait(message.format(
|
||||
validation=validation,
|
||||
response=response,
|
||||
uri=achall.chall.uri(achall.domain),
|
||||
command=command))
|
||||
command=command
|
||||
))
|
||||
|
||||
if not response.simple_verify(
|
||||
achall.chall, achall.domain,
|
||||
@@ -183,15 +211,30 @@ s.serve_forever()" """
|
||||
|
||||
return response
|
||||
|
||||
def _notify_and_wait(self, message): # pylint: disable=no-self-use
|
||||
# TODO: IDisplay wraps messages, breaking the command
|
||||
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
# message=message, height=25, pause=True)
|
||||
sys.stdout.write(message)
|
||||
six.moves.input("Press ENTER to continue")
|
||||
def _perform_dns01_challenge(self, achall):
|
||||
response, validation = achall.response_and_validation()
|
||||
if not self.conf("test-mode"):
|
||||
if not self.conf("public-ip-logging-ok"):
|
||||
if not zope.component.getUtility(interfaces.IDisplay).yesno(
|
||||
self.IP_DISCLAIMER, "Yes", "No",
|
||||
cli_flag="--manual-public-ip-logging-ok"):
|
||||
raise errors.PluginError("Must agree to IP logging to proceed")
|
||||
|
||||
def cleanup(self, achalls):
|
||||
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||
message = self._get_message(achall)
|
||||
formated_message = message.format(validation=validation,
|
||||
response=response)
|
||||
|
||||
self._notify_and_wait(formated_message)
|
||||
|
||||
if not response.simple_verify(
|
||||
achall.chall, achall.domain,
|
||||
achall.account_key.public_key()):
|
||||
logger.warning("Self-verify of challenge failed.")
|
||||
|
||||
return response
|
||||
|
||||
def _cleanup_http01_challenge(self, achall):
|
||||
# pylint: disable=missing-docstring,unused-argument
|
||||
if self.conf("test-mode"):
|
||||
assert self._httpd is not None, (
|
||||
"cleanup() must be called after perform()")
|
||||
@@ -202,3 +245,14 @@ s.serve_forever()" """
|
||||
logger.debug("Manual command process already terminated "
|
||||
"with %s code", self._httpd.returncode)
|
||||
shutil.rmtree(self._root)
|
||||
|
||||
def _notify_and_wait(self, message): # pylint: disable=no-self-use
|
||||
# TODO: IDisplay wraps messages, breaking the command
|
||||
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
# message=message, height=25, pause=True)
|
||||
sys.stdout.write(message)
|
||||
six.moves.input("Press ENTER to continue")
|
||||
|
||||
def _get_message(self, achall):
|
||||
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||
return self.MESSAGE_TEMPLATE.get(achall.chall.typ, "")
|
||||
|
||||
@@ -24,10 +24,16 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
from certbot.plugins.manual import Authenticator
|
||||
self.config = mock.MagicMock(
|
||||
http01_port=8080, manual_test_mode=False,
|
||||
manual_public_ip_logging_ok=False, noninteractive_mode=True)
|
||||
manual_public_ip_logging_ok=False, noninteractive_mode=True,
|
||||
standalone_supported_challenges="dns-01,http-01")
|
||||
self.auth = Authenticator(config=self.config, name="manual")
|
||||
self.achalls = [achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)]
|
||||
|
||||
self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)
|
||||
self.dns01 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.DNS01_P, domain="foo.com", account_key=KEY)
|
||||
|
||||
self.achalls = [self.http01, self.dns01]
|
||||
|
||||
config_test_mode = mock.MagicMock(
|
||||
http01_port=8080, manual_test_mode=True, noninteractive_mode=True)
|
||||
@@ -56,19 +62,21 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
mock_verify.return_value = True
|
||||
mock_interaction().yesno.return_value = True
|
||||
|
||||
resp = self.achalls[0].response(KEY)
|
||||
self.assertEqual([resp], self.auth.perform(self.achalls))
|
||||
self.assertEqual(1, mock_raw_input.call_count)
|
||||
resp_http = self.http01.response(KEY)
|
||||
resp_dns = self.dns01.response(KEY)
|
||||
|
||||
self.assertEqual([resp_http, resp_dns], self.auth.perform(self.achalls))
|
||||
self.assertEqual(2, mock_raw_input.call_count)
|
||||
mock_verify.assert_called_with(
|
||||
self.achalls[0].challb.chall, "foo.com", KEY.public_key(), 8080)
|
||||
self.http01.challb.chall, "foo.com", KEY.public_key(), 8080)
|
||||
|
||||
message = mock_stdout.write.mock_calls[0][1][0]
|
||||
self.assertTrue(self.achalls[0].chall.encode("token") in message)
|
||||
self.assertTrue(self.http01.chall.encode("token") in message)
|
||||
|
||||
mock_verify.return_value = False
|
||||
with mock.patch("certbot.plugins.manual.logger") as mock_logger:
|
||||
self.auth.perform(self.achalls)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
self.assertEqual(2, mock_logger.warning.call_count)
|
||||
|
||||
@mock.patch("certbot.plugins.manual.zope.component.getUtility")
|
||||
@mock.patch("certbot.plugins.manual.Authenticator._notify_and_wait")
|
||||
@@ -82,10 +90,10 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
@mock.patch("certbot.plugins.manual.subprocess.Popen", autospec=True)
|
||||
def test_perform_test_command_oserror(self, mock_popen):
|
||||
mock_popen.side_effect = OSError
|
||||
self.assertEqual([False], self.auth_test_mode.perform(self.achalls))
|
||||
self.assertEqual([False], self.auth_test_mode.perform([self.http01]))
|
||||
|
||||
@mock.patch("certbot.plugins.manual.socket.socket")
|
||||
@mock.patch("certbot.plugins.manual.time.sleep", autospec=True)
|
||||
@mock.patch("certbot.util.socket.socket")
|
||||
@mock.patch("certbot.util.time.sleep", autospec=True)
|
||||
@mock.patch("certbot.plugins.manual.subprocess.Popen", autospec=True)
|
||||
def test_perform_test_command_run_failure(
|
||||
self, mock_popen, unused_mock_sleep, unused_mock_socket):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Standalone Authenticator."""
|
||||
import argparse
|
||||
import collections
|
||||
import logging
|
||||
import socket
|
||||
@@ -9,6 +8,8 @@ import OpenSSL
|
||||
import six
|
||||
import zope.interface
|
||||
|
||||
from functools import partial
|
||||
|
||||
from acme import challenges
|
||||
from acme import standalone as acme_standalone
|
||||
|
||||
@@ -113,36 +114,6 @@ class ServerManager(object):
|
||||
SUPPORTED_CHALLENGES = [challenges.TLSSNI01, challenges.HTTP01]
|
||||
|
||||
|
||||
def supported_challenges_validator(data):
|
||||
"""Supported challenges validator for the `argparse`.
|
||||
|
||||
It should be passed as `type` argument to `add_argument`.
|
||||
|
||||
"""
|
||||
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
|
||||
if name not in challenges.Challenge.TYPES]
|
||||
if unrecognized:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Unrecognized challenges: {0}".format(", ".join(unrecognized)))
|
||||
|
||||
choices = set(chall.typ for chall in SUPPORTED_CHALLENGES)
|
||||
if not set(challs).issubset(choices):
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Plugin does not support the following (valid) "
|
||||
"challenges: {0}".format(", ".join(set(challs) - choices)))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
@@ -176,9 +147,12 @@ class Authenticator(common.Plugin):
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
validator = partial(util.supported_challenges_validator,
|
||||
supported=SUPPORTED_CHALLENGES)
|
||||
|
||||
add("supported-challenges",
|
||||
help="Supported challenges. Preferred in the order they are listed.",
|
||||
type=supported_challenges_validator,
|
||||
type=validator,
|
||||
default=",".join(chall.typ for chall in SUPPORTED_CHALLENGES))
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Tests for certbot.plugins.standalone."""
|
||||
import argparse
|
||||
import socket
|
||||
import unittest
|
||||
|
||||
@@ -64,33 +63,6 @@ class ServerManagerTest(unittest.TestCase):
|
||||
self.assertEqual(self.mgr.running(), {})
|
||||
|
||||
|
||||
class SupportedChallengesValidatorTest(unittest.TestCase):
|
||||
"""Tests for plugins.standalone.supported_challenges_validator."""
|
||||
|
||||
def _call(self, data):
|
||||
from certbot.plugins.standalone import (
|
||||
supported_challenges_validator)
|
||||
return supported_challenges_validator(data)
|
||||
|
||||
def test_correct(self):
|
||||
self.assertEqual("tls-sni-01", self._call("tls-sni-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):
|
||||
assert "foo" not in challenges.Challenge.TYPES
|
||||
self.assertRaises(argparse.ArgumentTypeError, self._call, "foo")
|
||||
|
||||
def test_not_subset(self):
|
||||
self.assertRaises(argparse.ArgumentTypeError, 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"))
|
||||
|
||||
|
||||
class AuthenticatorTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.standalone.Authenticator."""
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Plugin utilities."""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
import zope.component
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
||||
@@ -154,3 +157,36 @@ def already_listening_psutil(port, renewer=False):
|
||||
# name (AccessDenied).
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def supported_challenges_validator(data, supported=None):
|
||||
"""Supported challenges validator for the `argparse`.
|
||||
|
||||
It should be passed as `type` argument to `add_argument`.
|
||||
|
||||
:param str data: input value representing the supported_challenges
|
||||
:returns: original value if valid
|
||||
"""
|
||||
challs = data.split(",")
|
||||
supported = supported or []
|
||||
|
||||
# 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
|
||||
if name not in challenges.Challenge.TYPES]
|
||||
if unrecognized:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Unrecognized challenges: {0}".format(", ".join(unrecognized)))
|
||||
|
||||
choices = set(chall.typ for chall in supported)
|
||||
if not set(challs).issubset(choices):
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Plugin does not support the following (valid) "
|
||||
"challenges: {0}".format(", ".join(set(challs) - choices)))
|
||||
|
||||
return data
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Tests for certbot.plugins.util."""
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
import sys
|
||||
@@ -181,5 +182,42 @@ class AlreadyListeningTestPsutil(unittest.TestCase):
|
||||
mock_net.side_effect = psutil.AccessDenied("")
|
||||
self.assertFalse(self._call(12345))
|
||||
|
||||
|
||||
class SupportedChallengesValidatorTest(unittest.TestCase):
|
||||
"""Tests for plugins.standalone.supported_challenges_validator."""
|
||||
|
||||
def _call(self, data):
|
||||
from certbot.plugins.util import supported_challenges_validator
|
||||
from acme import challenges
|
||||
|
||||
supported = [challenges.HTTP01, challenges.DNS01, challenges.TLSSNI01]
|
||||
|
||||
return supported_challenges_validator(data, supported=supported)
|
||||
|
||||
def test_correct(self):
|
||||
self.assertEqual("tls-sni-01", self._call("tls-sni-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):
|
||||
from acme import challenges
|
||||
|
||||
assert "foo" not in challenges.Challenge.TYPES
|
||||
self.assertRaises(argparse.ArgumentTypeError, self._call, "foo")
|
||||
|
||||
def test_not_subset(self):
|
||||
self.assertRaises(argparse.ArgumentTypeError, 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 test_dns01(self):
|
||||
self.assertEqual("dns-01", self._call("dns-01"))
|
||||
self.assertEqual("http-01,dns-01", self._call("http-01,dns-01"))
|
||||
self.assertEqual("dns-01,http-01", self._call("dns-01,http-01"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -17,9 +17,9 @@ HTTP01 = challenges.HTTP01(
|
||||
token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA")
|
||||
TLSSNI01 = challenges.TLSSNI01(
|
||||
token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA"))
|
||||
DNS = challenges.DNS(token=b"17817c66b60ce2e4012dfad92657527a")
|
||||
DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a")
|
||||
|
||||
CHALLENGES = [HTTP01, TLSSNI01, DNS]
|
||||
CHALLENGES = [HTTP01, TLSSNI01, DNS01]
|
||||
|
||||
|
||||
def gen_combos(challbs):
|
||||
@@ -45,9 +45,9 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
|
||||
# Pending ChallengeBody objects
|
||||
TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING)
|
||||
HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING)
|
||||
DNS_P = chall_to_challb(DNS, messages.STATUS_PENDING)
|
||||
DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING)
|
||||
|
||||
CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS_P]
|
||||
CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS01_P]
|
||||
|
||||
|
||||
def gen_authzr(authz_status, domain, challs, statuses, combos=True):
|
||||
|
||||
@@ -111,7 +111,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
||||
|
||||
mock_poll.side_effect = self._validate_all
|
||||
self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)
|
||||
self.mock_auth.get_chall_pref.return_value.append(challenges.DNS)
|
||||
self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01)
|
||||
|
||||
authzr = self.handler.get_authorizations(["0"])
|
||||
|
||||
@@ -125,7 +125,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
||||
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
|
||||
# Test if list first element is TLSSNI01, use typ because it is an achall
|
||||
for achall in self.mock_auth.cleanup.call_args[0][0]:
|
||||
self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns"])
|
||||
self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns-01"])
|
||||
|
||||
# Length of authorizations list
|
||||
self.assertEqual(len(authzr), 1)
|
||||
@@ -240,7 +240,7 @@ class PollChallengesTest(unittest.TestCase):
|
||||
from certbot.auth_handler import challb_to_achall
|
||||
self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid
|
||||
self.chall_update[self.doms[0]].append(
|
||||
challb_to_achall(acme_util.DNS_P, "key", self.doms[0]))
|
||||
challb_to_achall(acme_util.DNS01_P, "key", self.doms[0]))
|
||||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler._poll_challenges,
|
||||
self.chall_update, False)
|
||||
@@ -342,7 +342,7 @@ class GenChallengePathTest(unittest.TestCase):
|
||||
self.assertTrue(self._call(challbs[::-1], prefs, None))
|
||||
|
||||
def test_not_supported(self):
|
||||
challbs = (acme_util.DNS_P, acme_util.TLSSNI01_P)
|
||||
challbs = (acme_util.DNS01_P, acme_util.TLSSNI01_P)
|
||||
prefs = [challenges.TLSSNI01]
|
||||
combos = ((0, 1),)
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ class FaiiledChallengesTest(unittest.TestCase):
|
||||
from certbot.errors import FailedChallenges
|
||||
self.error = FailedChallenges(set([achallenges.DNS(
|
||||
domain="example.com", challb=messages.ChallengeBody(
|
||||
chall=acme_util.DNS, uri=None,
|
||||
chall=acme_util.DNS01, uri=None,
|
||||
error=messages.Error(typ="tls", detail="detail")))]))
|
||||
|
||||
def test_str(self):
|
||||
self.assertTrue(str(self.error).startswith(
|
||||
"Failed authorization procedure. example.com (dns): tls"))
|
||||
"Failed authorization procedure. example.com (dns-01): tls"))
|
||||
|
||||
|
||||
class StandaloneBindErrorTest(unittest.TestCase):
|
||||
|
||||
@@ -14,6 +14,7 @@ import socket
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import configargparse
|
||||
|
||||
@@ -474,3 +475,23 @@ def get_strict_version(normalized):
|
||||
# strict version ending with "a" and a number designates a pre-release
|
||||
# pylint: disable=no-member
|
||||
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))
|
||||
|
||||
|
||||
def busy_wait(port, host="localhost"):
|
||||
"""Artificialy wait a fixed amount of time on a specific host and port
|
||||
|
||||
:param str port: port of the connection
|
||||
:param str host: hostname of the connection, "localhost" if None
|
||||
|
||||
"""
|
||||
while True:
|
||||
time.sleep(1)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect((host, port))
|
||||
except socket.error: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
Reference in New Issue
Block a user