Skip to content

Commit

Permalink
multiple resolvers, dump certs at start, fix keys
Browse files Browse the repository at this point in the history
  • Loading branch information
kurzondax committed Mar 12, 2020
1 parent 0f4cab4 commit 99b52e9
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
certs/
certs_flat/
data/

acme.json
# Python ignores
__pycache__/
*.py[cod]
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Forked from [DanielHuisman/traefik-certificate-extractor](https://github.com/Dan

Tool to extract Let's Encrypt certificates from Traefik's ACME storage file. Can automatically restart containers using the docker API.

Note: This version differs from original in that:
* Changed requirements.txt to use watchdog instead of watchdog3 which doesn't appear to exist any more
* Support for ACME v1 has been removed. You must be using the acme_v2 url in your resolver(s)
* Added support for multiple resolvers
* An initial dump of the certs will be performed before starting to watch the acme.json file for changes
* _**Not thoroughly tested**_

## Installation
```shell
git clone https://github.com/snowmb/traefik-certificate-extractor
Expand Down
143 changes: 73 additions & 70 deletions extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path

from pprint import pprint

class PathType(object):
def __init__(self, exists=True, type='file', dash_ok=True):
Expand Down Expand Up @@ -88,85 +88,85 @@ def restartContainerWithDomains(domains):
def createCerts(args):
# Read JSON file
data = json.loads(open(args.certificate).read())
resolvers = []
names = []

# Determine ACME version
acme_version = 2 if 'acme-v02' in data['Account']['Registration']['uri'] else 1
for k in data:
print(k)
resolvers.append(data[k])

# Find certificates
if acme_version == 1:
certs = data['DomainsCertificate']['Certs']
elif acme_version == 2:
certs = data['Certificates']
for resolver in resolvers:
if 'Certificates' not in resolver.keys():
print('Unable to locate account or certificates in json')
return

# Loop over all certificates
names = []

for c in certs:
if acme_version == 1:
name = c['Certificate']['Domain']
privatekey = c['Certificate']['PrivateKey']
fullchain = c['Certificate']['Certificate']
sans = c['Domains']['SANs']
elif acme_version == 2:
name = c['Domain']['Main']
privatekey = c['Key']
fullchain = c['Certificate']
sans = c['Domain']['SANs']

if (args.include and name not in args.include) or (args.exclude and name in args.exclude):
continue

# Decode private key, certificate and chain
privatekey = b64decode(privatekey).decode('utf-8')
fullchain = b64decode(fullchain).decode('utf-8')
start = fullchain.find('-----BEGIN CERTIFICATE-----', 1)
cert = fullchain[0:start]
chain = fullchain[start:]

if not args.dry:
# Create domain directory if it doesn't exist
directory = Path(args.directory)
if not directory.exists():
directory.mkdir()

if args.flat:
# Write private key, certificate and chain to flat files
with (directory / name + '.key').open('w') as f:
f.write(privatekey)
with (directory / name + '.crt').open('w') as f:
f.write(fullchain)
with (directory / name + '.chain.pem').open('w') as f:
f.write(chain)

if sans:
for name in sans:
with (directory / name + '.key').open('w') as f:
f.write(privatekey)
with (directory / name + '.crt').open('w') as f:
f.write(fullchain)
with (directory / name + '.chain.pem').open('w') as f:
f.write(chain)
for c in resolver['Certificates']:
pprint(c)
name = c['domain']['main']
privatekey = c['key']
fullchain = c['certificate']
if 'sans' in c['domain'].keys():
sans = c['domain']['sans']
else:
directory = directory / name
if not directory.exists():
directory.mkdir()
sans = None
# sans = c['Domain']['SANs']

# Write private key, certificate and chain to file
with (directory / 'privkey.pem').open('w') as f:
f.write(privatekey)
if (args.include and name not in args.include) or (args.exclude and name in args.exclude):
continue

with (directory / 'cert.pem').open('w') as f:
f.write(cert)
# Decode private key, certificate and chain
privatekey = b64decode(privatekey).decode('utf-8')
fullchain = b64decode(fullchain).decode('utf-8')
start = fullchain.find('-----BEGIN CERTIFICATE-----', 1)
cert = fullchain[0:start]
chain = fullchain[start:]

with (directory / 'chain.pem').open('w') as f:
f.write(chain)
if not args.dry:
# Create domain directory if it doesn't exist
directory = Path(args.directory)
if not directory.exists():
directory.mkdir()

with (directory / 'fullchain.pem').open('w') as f:
f.write(fullchain)
if args.flat:
# Write private key, certificate and chain to flat files
with (directory / name + '.key').open('w') as f:
f.write(privatekey)
with (directory / name + '.crt').open('w') as f:
f.write(fullchain)
with (directory / name + '.chain.pem').open('w') as f:
f.write(chain)

if sans:
for name in sans:
with (directory / name + '.key').open('w') as f:
f.write(privatekey)
with (directory / name + '.crt').open('w') as f:
f.write(fullchain)
with (directory / name + '.chain.pem').open('w') as f:
f.write(chain)
else:
directory = directory / name
if not directory.exists():
directory.mkdir()

# Write private key, certificate and chain to file
with (directory / 'privkey.pem').open('w') as f:
f.write(privatekey)

with (directory / 'cert.pem').open('w') as f:
f.write(cert)

with (directory / 'chain.pem').open('w') as f:
f.write(chain)

with (directory / 'fullchain.pem').open('w') as f:
f.write(fullchain)

print('Extracted certificate for: ' + name +
(', ' + ', '.join(sans) if sans else ''))
names.append(name)

print('Extracted certificate for: ' + name +
(', ' + ', '.join(sans) if sans else ''))
names.append(name)
return names


Expand Down Expand Up @@ -226,6 +226,9 @@ def doTheWork(self):

args = parser.parse_args()

print('Generating certs')
createCerts(args)

print('DEBUG: watching path: ' + str(args.certificate))
print('DEBUG: output path: ' + str(args.directory))

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
watchdog3
watchdog
docker

0 comments on commit 99b52e9

Please sign in to comment.