From cd7c424c4870bdf1b93c43b9283b25fb803361eb Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Fri, 13 Mar 2009 15:39:37 -0400 Subject: Minion-to-minion support, certmaster half. --- scripts/certmaster-sync | 139 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 scripts/certmaster-sync (limited to 'scripts') diff --git a/scripts/certmaster-sync b/scripts/certmaster-sync new file mode 100644 index 0000000..bd27af5 --- /dev/null +++ b/scripts/certmaster-sync @@ -0,0 +1,139 @@ +#!/usr/bin/python -tt + +# Syncs the valid CA-signed certificates from certmaster to all known +# hosts via func. To be called during the post-sign hook to copy new +# certificates and from the post-clean hook in order to purge stale +# certificates. Requires 'sync_certs' to be set in certmaster.conf. + +import os +import sys +import sha +import xmlrpclib +from glob import glob +from time import sleep +from certmaster import certmaster as certmaster +from func.overlord.client import Client +from func.CommonErrors import Func_Client_Exception +import func.jobthing as jobthing + +def syncable(cert_list): + """ + Calls out to known hosts to find out who is configured for + peering. Returns a list of hostnames who support peering. + """ + try: + fc = Client('*', async=True, nforks=len(cert_list)) + except Func_Client_Exception: + # we are either: + # - signing the first minion + # - cleaning the only minion + # so there's nothing to hit. This shouldn't happen + # when we get called from the 'post-fetch' trigger + # (future work) + return None + + # Only wait for a few seconds. Assume anything that doesn't get + # back by then is a lost cause. Don't want this trigger to spin + # too long. + ticks = 0 + return_code = jobthing.JOB_ID_RUNNING + results = None + job_id = fc.certmastermod.peering_enabled() + while return_code != jobthing.JOB_ID_FINISHED and ticks < 3: + sleep(1) + (return_code, results) = fc.job_status(job_id) + ticks += 1 + + hosts = [] + for host, result in results.iteritems(): + if result == True: + hosts.append(host) + return hosts + +def remote_peers(hosts): + """ + Calls out to hosts to collect peer information + """ + fc = Client(';'.join(hosts)) + return fc.certmastermod.known_peers() + +def local_certs(): + """ + Returns (hostname, sha1) hash of local certs + """ + globby = '*.%s' % cm.cfg.cert_extension + globby = os.path.join(cm.cfg.certroot, globby) + files = glob(globby) + results = [] + for f in files: + hostname = os.path.basename(f).replace('.' + cm.cfg.cert_extension, '') + digest = checksum(f) + results.append([hostname, digest]) + return results + +def checksum(f): + thissum = sha.new() + if os.path.exists(f): + fo = open(f, 'r') + data = fo.read() + fo.close() + thissum.update(data) + + return thissum.hexdigest() + +def remove_stale_certs(local, remote): + """ + For each cert on each remote host, make sure it exists locally. + If not then it has been cleaned locally and needs unlinked + remotely. + """ + local = [foo[0] for foo in local] # don't care about checksums + for host, peers in remote.iteritems(): + fc = Client(host) + die = [] + for peer in peers: + if peer[0] not in local: + die.append(peer[0]) + if die != []: + fc.certmastermod.remove_peer_certs(die) + +def copy_updated_certs(local, remote): + """ + For each local cert, make sure it exists on the remote with the + correct hash. If not, copy it over! + """ + for host, peers in remote.iteritems(): + fc = Client(host) + for cert in local: + if cert not in peers: + cert_name = '%s.%s' % (cert[0], cm.cfg.cert_extension) + full_path = os.path.join(cm.cfg.certroot, cert_name) + fd = open(full_path) + certblob = fd.read() + fd.close() + fc.certmastermod.copy_peer_cert(cert[0], xmlrpclib.Binary(certblob)) + +def main(): + forced = False + try: + if sys.argv[1] in ['-f', '--force']: + forced = True + except IndexError: + pass + + if not cm.cfg.sync_certs and not forced: + sys.exit(0) + + certs = glob(os.path.join(cm.cfg.certroot, + '*.%s' % cm.cfg.cert_extension)) + hosts = syncable(certs) + if not hosts: + return 0 + remote = remote_peers(hosts) + local = local_certs() + remove_stale_certs(local, remote) + copy_updated_certs(local, remote) + +if __name__ == "__main__": + cm = certmaster.CertMaster() + main() -- cgit