standalone webserver mode

This patch adds the ability to start a simple
webserver that is sufficient to solve the ACME
challenge.
This commit is contained in:
David Klaftenegger 2016-02-13 00:48:38 +01:00 committed by Markus Hauschild
parent 60ae8f2452
commit abba505c9f
1 changed files with 54 additions and 0 deletions

View File

@ -16,8 +16,18 @@ import re
import shutil
import subprocess
import tempfile
import threading
import yaml
try:
from SimpleHTTPServer import SimpleHTTPRequestHandler
except ImportError:
from http.server import SimpleHTTPRequestHandler
try:
from SocketServer import TCPServer as HTTPServer
except ImportError:
from http.server import HTTPServer
ACME_DIR="/etc/acme/"
ACME_CONF=ACME_DIR + "acme.conf"
@ -31,6 +41,36 @@ class FileNotFoundError(OSError):
class InvalidCertificateError(Exception):
pass
# @brief custom request handler for ACME challenges
# @note current working directory is temporarily changed by the script before
# the webserver starts, which allows using SimpleHTTPRequestHandler
class ACMERequestHandler(SimpleHTTPRequestHandler):
# @brief remove directories from GET URL
# @details the current working directory contains the challenge files,
# there is no need for creating subdirectories for the path
# that ACME expects.
# Additionally, this allows redirecting the ACME path to this
# webserver without having to know which subdirectory is
# redirected, which simplifies integration with existing
# webservers.
def translate_path(self, path):
spath = path.split('/')
assert(spath[0] == '')
spath = spath[1:]
if spath[0] == '.well-known':
spath = spath[1:]
if spath[0] == 'acme-challenge':
spath = spath[1:]
assert(len(spath) == 1)
spath.insert(0, '')
path = '/'.join(spath)
return SimpleHTTPRequestHandler.translate_path(self, path)
# @brief start the standalone webserver
# @param server the HTTPServer object
# @note this function is used to be passed to threading.Thread
def start_standalone(server):
server.serve_forever()
# @brief check whether existing certificate is still valid or expiring soon
# @param crt_file string containing the path to the certificate file
@ -87,6 +127,16 @@ def cert_get(domain, settings):
if not os.path.isdir(challenge_dir):
raise FileNotFoundError("Challenge directory (%s) does not exist!" % challenge_dir)
if settings['mode'] == 'standalone':
port = settings.get('port', 80)
current_dir = os.getcwd()
os.chdir(challenge_dir)
HTTPServer.allow_reuse_address = True
server = HTTPServer(("", port), ACMERequestHandler)
server_thread = threading.Thread(target=start_standalone, args=(server, ))
server_thread.start()
try:
cr = subprocess.check_output(['openssl', 'req', '-new', '-sha256', '-key', key_file, '-out', csr_file, '-subj', '/CN=%s' % domain])
@ -101,6 +151,10 @@ def cert_get(domain, settings):
shutil.copy2(crt_file, crt_final)
finally:
if settings['mode'] == 'standalone':
os.chdir(current_dir)
server.shutdown()
server_thread.join()
os.remove(csr_file)
os.remove(crt_file)