#!/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']: # check if there is any domain name configured container_id = event['id'] if len(list_domains.handle_container()) > 0: resolved_domains = list_domains.get_resolving_domains_from_containers(client) create_cert_data_standalone(resolved_domains) create_haproxy_cert()