From ab4f437cb4cda7caa5f723d8ed7e4d421de4b6b0 Mon Sep 17 00:00:00 2001 From: Christoph Egger Date: Sat, 6 Feb 2016 22:34:49 +0100 Subject: [PATCH] Add addrecord utility --- addrecord | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 addrecord diff --git a/addrecord b/addrecord new file mode 100644 index 0000000..fa4c840 --- /dev/null +++ b/addrecord @@ -0,0 +1,109 @@ +#!/usr/bin/python3 +# +# ssh-based server to allow hosts to update their own TLS-related RRs +# intended to be used like +# command="/srv/tls/bin/addrecord hepworth.siccegge.de" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOIlLx3R+Q5LgBZbJ6USuzam/uAEQITl6vzOn/ylk4fq christoph@mitoraj + +import os +import sys +import re +import datetime +import fcntl + +import yaml + +class Addrecord: + def __init__(self, host): + self._host = host + self._ports = {'www': [443], + 'smtp': [25, 587, 465], + 'imap': [993, 143], + 'pop': [110, 995], + 'xmpp': [5222, 5269], + } + self._tlsa_re = re.compile('TLSA 3 1 1 [0-9a-f]{64}') + self._acme_re = re.compile(r'[a-zA-Z0-9_-]{43}') + with open('inventory.yaml') as inv: + self._inventory = yaml.load(inv) + + + def tlsa(self): + host = input("Hostname: ") + service = input("Service: ") + value = input("Value: ") + if host not in self._inventory[self._host][service]: + sys.stderr.write("Not authorized to update entries for service '%s' on '%s'\n" + % (service, host)) + return 1 + + if re.fullmatch(self._tlsa_re, value) is None: + sys.stderr.write("Not a valid TLSA record: '%s'\n" % value) + return 2 + + records = [] + for port in self._ports[service]: + records.append("{0:<35s}\t{1}\n".format("_%d._tcp.%s" % (port, host), + value)) + self._update_records('tlsa', records) + print("OK") + return 0 + + + def acme(self): + host = input("Hostname: ") + value = input("Value: ") + allowed_hosts = set() + for value in self._inventory[self._host].values(): + allowed_hosts = allowed_hosts.union(value) + + if host not in allowed_hosts: + sys.stderr.write("Not authorized to update entries for host '%s'\n" % host) + return 1 + + if re.fullmatch(self._acme_re, value) is None: + sys.stderr.write("Not a valid ACME challenge record: '%s'\n" % value) + return 2 + + records = [ "{0:<35s}\t{1}\n".format("_acme-challenge.%s" % host, value) ] + self._update_records('acme', records) + print("OK") + return 0 + + + def _update_records(self, sort, records): + to_remove = [ i.split()[0] for i in records ] + + with open('%s/%s.m4' % (sort, self._host), 'r') as oldzone: + fcntl.flock(oldzone, fcntl.LOCK_EX) + lines = oldzone.readlines() + lines = [ line for line in lines if line == '\n' or line.split()[0] not in to_remove ] + + lines.append('\n') + lines.append("; Last updated %s by %s\n" % (datetime.datetime.utcnow().isoformat(), + self._host)) + lines = lines + records + + with open('%s/%s.m4.new' % (sort, self._host), 'w') as newzone: + newtext = ''.join(lines) + newtext = re.sub(r'\n[\n]+', '\n\n', newtext) + newtext = re.sub(r'\n;.*\n\n;', '\n;', newtext) + newzone.write(newtext) + + os.rename('%s/%s.m4.new' % (sort, self._host), + '%s/%s.m4' % (sort, self._host)) + fcntl.flock(oldzone, fcntl.LOCK_UN) + + +def main(): + command = os.environ['SSH_ORIGINAL_COMMAND'] + host = sys.argv[1] + addrecord = Addrecord(host) + + if command == 'acme': + return addrecord.acme() + elif command == 'tlsa': + return addrecord.tlsa() + + +if __name__ == '__main__': + sys.exit(main()) -- 2.39.2