From 3711251c3a081db69a8793e354bdcd14d3a489d7 Mon Sep 17 00:00:00 2001 From: Daniel Huisman Date: Tue, 27 Jun 2017 14:09:51 +0200 Subject: [PATCH] Added basic extractor --- .gitignore | 23 ++++++++++++++ Dockerfile | 18 +++++++++++ README.md | 29 ++++++++++++++++- extractor.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 extractor.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5c00c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +certs/ +data/ + +# Python ignores +__pycache__/ +*.py[cod] +*$py.class +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82c2c53 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Use Python on Alpine Linux as base image +FROM python:alpine + +# Create working directory +RUN mkdir -p /app +WORKDIR /app + +# Copy requirements.txt to force Docker not to use the cache +COPY requirements.txt /app + +# Install app dependencies +RUN pip3 install -r requirements.txt + +# Copy app source +COPY . /app + +# Define entrypoint of the app +ENTRYPOINT ["python3", "extractor.py"] diff --git a/README.md b/README.md index fc3ac5a..13c8e6f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ -# traefik-certificate-extractor +# Traefik Certificate Extractor + Tool to extract Let's Encrypt certificates from Traefik's ACME storage file. + +## Installation +``` +git clone https://github.com/DanielHuisman/traefik-certificate-extractor +``` + +## Usage +``` +python3 extractor.py [directory] +``` +Default directory is `./data`. The output directory is `./certs`. + +## Output +``` +certs/ + example.com/ + cert.pem + chain.pem + fullchain.pem + privkey.pem + sub.example.nl/ + cert.pem + chain.pem + fullchain.pem + privkey.pem +``` diff --git a/extractor.py b/extractor.py new file mode 100644 index 0000000..d7d62fc --- /dev/null +++ b/extractor.py @@ -0,0 +1,83 @@ +import sys +import os +import errno +import time +import json +from base64 import b64decode +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +class Handler(FileSystemEventHandler): + def on_created(self, event): + self.handle(event) + + def on_modified(self, event): + self.handle(event) + + def handle(self, event): + # Check if it's a JSON file + if not event.is_directory and event.src_path.endswith('.json'): + print('Certificates changed') + + # Read JSON file + data = json.loads(open(event.src_path).read()) + certs = data['DomainsCertificate']['Certs'] + + # Loop over all certificates + for c in certs: + # Decode private key, certificate and chain + privatekey = b64decode(c['Certificate']['PrivateKey']).decode('utf-8') + fullchain = b64decode(c['Certificate']['Certificate']).decode('utf-8') + start = fullchain.find('-----BEGIN CERTIFICATE-----', 1) + cert = fullchain[0:start] + chain = fullchain[start:] + + # Create domain directory if it doesn't exist + directory = 'certs/' + c['Certificate']['Domain'] + '/' + try: + os.makedirs(directory) + except OSError as error: + if error.errno != errno.EEXIST: + raise + + # Write private key, certificate and chain to file + with open(directory + 'privkey.pem', 'w') as f: + f.write(privatekey) + + with open(directory + 'cert.pem', 'w') as f: + f.write(cert) + + with open(directory + 'chain.pem', 'w') as f: + f.write(chain) + + with open(directory + 'fullchain.pem', 'w') as f: + f.write(fullchain) + + print('Extracted certificate for: ' + c['Domains']['Main'] + (', ' + ', '.join(c['Domains']['SANs']) if c['Domains']['SANs'] else '')) + +if __name__ == "__main__": + # Determine path to watch + path = sys.argv[1] if len(sys.argv) > 1 else './data' + + # Create output directory if it doesn't exist + try: + os.makedirs('certs') + except OSError as error: + if error.errno != errno.EEXIST: + raise + + # Create event handler and observer + event_handler = Handler() + observer = Observer() + + # Register the directory to watch + observer.schedule(event_handler, path) + + # Main loop to watch the directory + observer.start() + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + observer.join() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e59495e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +watchdog