docker_ssl_endpoint/start.py

159 lines
5.0 KiB
Python

#!/usr/bin/python3.4
import os
import json
import sys
import signal
import logging
import time
import hashlib
import threading
import list_domains
from docker import Client
cert_path='/data/haproxy'
cert_file='/data/haproxy/cert.pem'
pid_file='/haproxy.pid'
def hash_cert_file():
"""Creates the sha256 hash of the certifcate file for haproxy. If the file
does not exist, an empty string is returned.
"""
if not os.path.isfile(cert_file):
return ''
aFile = open(cert_file, 'rb')
hasher = hashlib.sha256()
buf = aFile.read(65536)
while len(buf) > 0:
hasher.update(buf)
buf = aFile.read(65536)
return hasher.digest()
def setup_logging():
"""Sets up logging with a nice format"""
logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', level=logging.INFO)
def get_pid():
"""This function reads the process id from the given file and returns as int."""
with open(pid_file, 'r') as file:
return int(file.read())
def kill_haproxy():
"""Stops the currently running instance of haproxy by issueing a kill signal to its pid."""
logging.info('killing haproxy')
try:
os.kill(get_pid(), signal.SIGKILL)
except OSError:
pass
def start_haproxy_ssl():
logging.info('starting haproxy SSL')
os.system('/usr/sbin/haproxy -f /haproxy_ssl.conf -p /haproxy.pid')
return is_haproxy_running()
def start_haproxy():
logging.info('starting haproxy NON SSL')
os.system('/usr/sbin/haproxy -f /haproxy.conf -p /haproxy.pid')
return False
def is_haproxy_running():
try:
os.kill(get_pid(), 0)
return True
except OSError:
return False
def ssl_possible():
"""Check if a certificate is available."""
if not os.path.isfile(cert_file):
return False
else:
return True
def create_haproxy_cert():
"""Combines the freshly created fullchain.pem and privkey.pem into /data/haproxy/cert.pem"""
logging.info('updating %s', cert_file)
os.system('DIR=`ls -td /data/config/live/*/ | head -1`; echo ${DIR}; mkdir -p /data/haproxy; cat ${DIR}/fullchain.pem ${DIR}/privkey.pem > /data/haproxy/cert.pem')
def create_cert_data_standalone(domains):
domains = " -d ".join(domains)
os.system('/letsencrypt/letsencrypt-auto --config letencrypt.conf certonly --expand --force-renewal --duplicate --allow-subset-of-names --standalone-supported-challenges http-01 --http-01-port 54321 -d ' + domains)
def cert_watcher():
SSL_RUNNING=True
cert_file_hash = hash_cert_file()
while True:
logging.info('ping')
time.sleep(60)
if ssl_possible() and not SSL_RUNNING:
kill_haproxy()
start_haproxy_ssl()
if is_haproxy_running():
cert_file_hash = hash_cert_file()
logging.info('NON SSL -> SSL')
SSL_RUNNING=True
else:
start_haproxy()
SSL_RUNNING=False
if SSL_RUNNING and cert_file_hash != hash_cert_file():
logging.info('cert has changed')
kill_haproxy()
start_haproxy_ssl()
if is_haproxy_running():
cert_file_hash = hash_cert_file()
logging.info('SSL -> SSL')
SSL_RUNNING=True
else:
start_haproxy()
logging.info('SSL -> NON SSL')
SSL_RUNNING=False
if __name__ == '__main__':
setup_logging()
logging.info('starting')
if not os.path.exists(cert_path):
logging.info('creating cert_path path: %s', cert_path)
os.mkdir(cert_path)
client = Client(base_url='unix://var/run/docker.sock', version='1.15')
cert_file_hash = hash_cert_file()
# try to start in SSL mode, no problem if that fails
logging.info('try in SSL mode')
SSL_RUNNING = start_haproxy_ssl()
if not SSL_RUNNING:
logging.info('SSL mode failed')
if not is_haproxy_running():
# tried to start haproxy and this failed, so we need to create a certificate and try again:
# - start non ssl haproxy to be able to get a valid cert
logging.info('try in NON SSL mode')
SSL_RUNNING = start_haproxy()
# - get all domains
resolved_domains = list_domains.get_resolving_domains_from_containers(client)
# - create cert
create_cert_data_standalone(resolved_domains)
create_haproxy_cert()
# now we should have it up and running or something weird happened.
if not is_haproxy_running():
logging.error('could not start after generating cert. See output above.')
sys.exit(1)
t = threading.Thread(target=cert_watcher)
t.start()
for line in client.events():
line_str = line.decode("utf-8")
event = json.loads(line_str)
if event['Action'] in ['start', 'destroy']:
resolved_domains = list_domains.get_resolving_domains_from_containers(client)
create_cert_data_standalone(resolved_domains)
create_haproxy_cert()