mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 19:02:52 +02:00
Manual SimpleHTTP integration tests.
This commit is contained in:
@@ -156,7 +156,7 @@ class AuthHandler(object):
|
|||||||
active_achalls = []
|
active_achalls = []
|
||||||
for achall, resp in itertools.izip(achalls, resps):
|
for achall, resp in itertools.izip(achalls, resps):
|
||||||
# Don't send challenges for None and False authenticator responses
|
# Don't send challenges for None and False authenticator responses
|
||||||
if resp:
|
if resp is not None and resp:
|
||||||
self.network.answer_challenge(achall.challb, resp)
|
self.network.answer_challenge(achall.challb, resp)
|
||||||
# TODO: answer_challenge returns challr, with URI,
|
# TODO: answer_challenge returns challr, with URI,
|
||||||
# that can be used in _find_updated_challr
|
# that can be used in _find_updated_challr
|
||||||
@@ -166,6 +166,11 @@ class AuthHandler(object):
|
|||||||
chall_update[achall.domain].append(achall)
|
chall_update[achall.domain].append(achall)
|
||||||
else:
|
else:
|
||||||
chall_update[achall.domain] = [achall]
|
chall_update[achall.domain] = [achall]
|
||||||
|
else: # resp is None or not resp
|
||||||
|
# XXX: make sure that achalls corresponding to None or
|
||||||
|
# False returned from Authenticator are removed from
|
||||||
|
# the queue and thus avoid infinite loop
|
||||||
|
active_achalls.append(achall)
|
||||||
|
|
||||||
return active_achalls
|
return active_achalls
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
"""Manual plugin."""
|
"""Manual plugin."""
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
import zope.component
|
import zope.component
|
||||||
import zope.interface
|
import zope.interface
|
||||||
@@ -8,10 +14,14 @@ import zope.interface
|
|||||||
from acme import challenges
|
from acme import challenges
|
||||||
from acme import jose
|
from acme import jose
|
||||||
|
|
||||||
|
from letsencrypt import errors
|
||||||
from letsencrypt import interfaces
|
from letsencrypt import interfaces
|
||||||
from letsencrypt.plugins import common
|
from letsencrypt.plugins import common
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ManualAuthenticator(common.Plugin):
|
class ManualAuthenticator(common.Plugin):
|
||||||
"""Manual Authenticator.
|
"""Manual Authenticator.
|
||||||
|
|
||||||
@@ -43,8 +53,8 @@ command on the target server (as root):
|
|||||||
# anything recursively under the cwd
|
# anything recursively under the cwd
|
||||||
|
|
||||||
HTTP_TEMPLATE = """\
|
HTTP_TEMPLATE = """\
|
||||||
mkdir -p /tmp/letsencrypt/public_html/{response.URI_ROOT_PATH}
|
mkdir -p {root}/public_html/{response.URI_ROOT_PATH}
|
||||||
cd /tmp/letsencrypt/public_html
|
cd {root}/public_html
|
||||||
echo -n {achall.token} > {response.URI_ROOT_PATH}/{response.path}
|
echo -n {achall.token} > {response.URI_ROOT_PATH}/{response.path}
|
||||||
# run only once per server:
|
# run only once per server:
|
||||||
python -c "import BaseHTTPServer, SimpleHTTPServer; \\
|
python -c "import BaseHTTPServer, SimpleHTTPServer; \\
|
||||||
@@ -55,8 +65,8 @@ s.serve_forever()" """
|
|||||||
|
|
||||||
# https://www.piware.de/2011/01/creating-an-https-server-in-python/
|
# https://www.piware.de/2011/01/creating-an-https-server-in-python/
|
||||||
HTTPS_TEMPLATE = """\
|
HTTPS_TEMPLATE = """\
|
||||||
mkdir -p /tmp/letsencrypt/public_html/{response.URI_ROOT_PATH}
|
mkdir -p {root}/public_html/{response.URI_ROOT_PATH}
|
||||||
cd /tmp/letsencrypt/public_html
|
cd {root}/public_html
|
||||||
echo -n {achall.token} > {response.URI_ROOT_PATH}/{response.path}
|
echo -n {achall.token} > {response.URI_ROOT_PATH}/{response.path}
|
||||||
# run only once per server:
|
# run only once per server:
|
||||||
openssl req -new -newkey rsa:4096 -subj "/" -days 1 -nodes -x509 -keyout ../key.pem -out ../cert.pem
|
openssl req -new -newkey rsa:4096 -subj "/" -days 1 -nodes -x509 -keyout ../key.pem -out ../cert.pem
|
||||||
@@ -77,6 +87,14 @@ s.serve_forever()" """
|
|||||||
super(ManualAuthenticator, self).__init__(*args, **kwargs)
|
super(ManualAuthenticator, self).__init__(*args, **kwargs)
|
||||||
self.template = (self.HTTP_TEMPLATE if self.config.no_simple_http_tls
|
self.template = (self.HTTP_TEMPLATE if self.config.no_simple_http_tls
|
||||||
else self.HTTPS_TEMPLATE)
|
else self.HTTPS_TEMPLATE)
|
||||||
|
self._root = (tempfile.mkdtemp() if self.conf("test-mode")
|
||||||
|
else "/tmp/letsencrypt")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_parser_arguments(cls, add):
|
||||||
|
add("test-mode", action="store_true",
|
||||||
|
help="Test mode. Executes the manual command in subprocess. "
|
||||||
|
"Requires openssl to be installed unless --no-simple-http-tls.")
|
||||||
|
|
||||||
def prepare(self): # pylint: disable=missing-docstring,no-self-use
|
def prepare(self): # pylint: disable=missing-docstring,no-self-use
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
@@ -110,17 +128,44 @@ binary for temporary key/certificate generation.""".replace("\n", "")
|
|||||||
tls=(not self.config.no_simple_http_tls))
|
tls=(not self.config.no_simple_http_tls))
|
||||||
assert response.good_path # is encoded os.urandom(18) good?
|
assert response.good_path # is encoded os.urandom(18) good?
|
||||||
|
|
||||||
self._notify_and_wait(self.MESSAGE_TEMPLATE.format(
|
command = self.template.format(
|
||||||
achall=achall, response=response, uri=response.uri(achall.domain),
|
root=self._root, achall=achall, response=response,
|
||||||
ct=response.CONTENT_TYPE, command=self.template.format(
|
ct=response.CONTENT_TYPE, port=(
|
||||||
achall=achall, response=response, ct=response.CONTENT_TYPE,
|
response.port if self.config.simple_http_port is None
|
||||||
port=(response.port if self.config.simple_http_port is None
|
else self.config.simple_http_port))
|
||||||
else self.config.simple_http_port))))
|
if self.conf("test-mode"):
|
||||||
|
logger.debug("Test mode. Executing the manual command: %s", command)
|
||||||
|
try:
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
self._httpd = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
# don't care about setting stdout and stderr,
|
||||||
|
# we're in test mode anyway
|
||||||
|
shell=True,
|
||||||
|
# "preexec_fn" is UNIX specific, but so is "command"
|
||||||
|
preexec_fn=os.setsid)
|
||||||
|
except OSError as error: # ValueError should not happen!
|
||||||
|
logging.debug(
|
||||||
|
"Couldn't execute manual command", error, exc_info=True)
|
||||||
|
return False
|
||||||
|
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)
|
||||||
|
time.sleep(4) # XXX
|
||||||
|
if self._httpd.poll():
|
||||||
|
raise errors.Error("Couldn't execute manual command")
|
||||||
|
else:
|
||||||
|
self._notify_and_wait(self.MESSAGE_TEMPLATE.format(
|
||||||
|
achall=achall, response=response,
|
||||||
|
uri=response.uri(achall.domain), ct=response.CONTENT_TYPE,
|
||||||
|
command=command))
|
||||||
|
|
||||||
if response.simple_verify(
|
if response.simple_verify(
|
||||||
achall.challb, achall.domain, self.config.simple_http_port):
|
achall.challb, achall.domain, self.config.simple_http_port):
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
|
if self.conf("test-mode") and self._httpd.poll():
|
||||||
|
return False
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _notify_and_wait(self, message): # pylint: disable=no-self-use
|
def _notify_and_wait(self, message): # pylint: disable=no-self-use
|
||||||
@@ -130,5 +175,13 @@ binary for temporary key/certificate generation.""".replace("\n", "")
|
|||||||
sys.stdout.write(message)
|
sys.stdout.write(message)
|
||||||
raw_input("Press ENTER to continue")
|
raw_input("Press ENTER to continue")
|
||||||
|
|
||||||
def cleanup(self, achalls): # pylint: disable=missing-docstring,no-self-use
|
def cleanup(self, achalls):
|
||||||
pass # pragma: no cover
|
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||||
|
if self.conf("test-mode"):
|
||||||
|
if self._httpd.poll() is None:
|
||||||
|
logger.debug("Terminating manual command process")
|
||||||
|
os.killpg(self._httpd.pid, signal.SIGTERM)
|
||||||
|
else:
|
||||||
|
logger.debug("Manual command process already terminated "
|
||||||
|
"with %s code", self._httpd.returncode)
|
||||||
|
shutil.rmtree(self._root)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class ManualAuthenticatorTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.plugins.manual import ManualAuthenticator
|
from letsencrypt.plugins.manual import ManualAuthenticator
|
||||||
self.config = mock.MagicMock(
|
self.config = mock.MagicMock(
|
||||||
no_simple_http_tls=True, simple_http_port=4430)
|
no_simple_http_tls=True, simple_http_port=4430,
|
||||||
|
manual_test_mode=False)
|
||||||
self.auth = ManualAuthenticator(config=self.config, name="manual")
|
self.auth = ManualAuthenticator(config=self.config, name="manual")
|
||||||
self.achalls = [achallenges.SimpleHTTP(
|
self.achalls = [achallenges.SimpleHTTP(
|
||||||
challb=acme_util.SIMPLE_HTTP, domain="foo.com", key=None)]
|
challb=acme_util.SIMPLE_HTTP, domain="foo.com", key=None)]
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ class StandaloneAuthenticator(common.Plugin):
|
|||||||
"""
|
"""
|
||||||
signal.signal(signal.SIGINT, self.subproc_signal_handler)
|
signal.signal(signal.SIGINT, self.subproc_signal_handler)
|
||||||
self.sock = socket.socket()
|
self.sock = socket.socket()
|
||||||
|
# SO_REUSEADDR flag tells the kernel to reuse a local socket
|
||||||
|
# in TIME_WAIT state, without waiting for its natural timeout
|
||||||
|
# to expire.
|
||||||
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
try:
|
try:
|
||||||
self.sock.bind(("0.0.0.0", port))
|
self.sock.bind(("0.0.0.0", port))
|
||||||
except socket.error, error:
|
except socket.error, error:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ common() {
|
|||||||
|
|
||||||
common --domains le1.wtf auth
|
common --domains le1.wtf auth
|
||||||
common --domains le2.wtf run
|
common --domains le2.wtf run
|
||||||
|
common -a manual -d le.wtf auth
|
||||||
|
common -a manual -d le.wtf --no-simple-http-tls auth
|
||||||
|
|
||||||
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
|
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
|
||||||
OPENSSL_CNF=examples/openssl.cnf
|
OPENSSL_CNF=examples/openssl.cnf
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ store_flags="$store_flags --logs-dir $root/logs"
|
|||||||
export root store_flags
|
export root store_flags
|
||||||
|
|
||||||
letsencrypt_test () {
|
letsencrypt_test () {
|
||||||
# first three flags required, rest is handy defaults
|
|
||||||
letsencrypt \
|
letsencrypt \
|
||||||
--server "${SERVER:-http://localhost:4000/acme/new-reg}" \
|
--server "${SERVER:-http://localhost:4000/acme/new-reg}" \
|
||||||
--no-verify-ssl \
|
--no-verify-ssl \
|
||||||
--dvsni-port 5001 \
|
--dvsni-port 5001 \
|
||||||
|
--simple-http-port 5001 \
|
||||||
|
--manual-test-mode \
|
||||||
$store_flags \
|
$store_flags \
|
||||||
--text \
|
--text \
|
||||||
--agree-eula \
|
--agree-eula \
|
||||||
|
|||||||
Reference in New Issue
Block a user