summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--certmaster.spec2
-rw-r--r--certmaster/certmaster.py7
-rw-r--r--certmaster/commonconfig.py4
-rw-r--r--scripts/certmaster-sync139
-rw-r--r--setup.py2
5 files changed, 152 insertions, 2 deletions
diff --git a/certmaster.spec b/certmaster.spec
index e03ec78..f7fa30c 100644
--- a/certmaster.spec
+++ b/certmaster.spec
@@ -74,6 +74,7 @@ rm -fr $RPM_BUILD_ROOT
%{_bindir}/certmaster
%{_bindir}/certmaster-request
%{_bindir}/certmaster-ca
+%{_bindir}/certmaster-sync
/etc/init.d/certmaster
%dir %{_sysconfdir}/%{name}
%dir %{_sysconfdir}/%{name}/minion-acl.d/
@@ -85,6 +86,7 @@ rm -fr $RPM_BUILD_ROOT
%{python_sitelib}/certmaster/*.py*
%dir /var/log/certmaster
%dir /var/lib/certmaster
+%dir /var/lib/certmaster/peers
%dir /var/lib/certmaster/triggers/sign/
%dir /var/lib/certmaster/triggers/sign/pre
%dir /var/lib/certmaster/triggers/sign/post
diff --git a/certmaster/certmaster.py b/certmaster/certmaster.py
index 506a029..58cb50b 100644
--- a/certmaster/certmaster.py
+++ b/certmaster/certmaster.py
@@ -276,6 +276,13 @@ class CertMaster(object):
return signed_certs
+ def get_peer_certs(self):
+ """
+ Returns a list of all certs under peerroot
+ """
+ myglob = os.path.join(self.cfg.peerroot, '*.%s' % self.cfg.cert_extension)
+ return glob.glob(myglob)
+
# return a list of the cert hash string we use to identify systems
def get_cert_hashes(self, hostglobs=None):
certglob = "%s/*.cert" % (self.cfg.certroot)
diff --git a/certmaster/commonconfig.py b/certmaster/commonconfig.py
index 4be491e..5d0361e 100644
--- a/certmaster/commonconfig.py
+++ b/certmaster/commonconfig.py
@@ -26,10 +26,12 @@ class CMConfig(BaseConfig):
csrroot = Option('/var/lib/certmaster/certmaster/csrs')
cert_extension = Option('cert')
autosign = BoolOption(False)
+ sync_certs = BoolOption(False)
+ peering = BoolOption(True)
+ peerroot = Option('/var/lib/certmaster/peers')
class MinionConfig(BaseConfig):
log_level = Option('INFO')
certmaster = Option('certmaster')
certmaster_port = IntOption(51235)
cert_dir = Option('/etc/pki/certmaster')
-
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()
diff --git a/setup.py b/setup.py
index c647170..8cf70eb 100644
--- a/setup.py
+++ b/setup.py
@@ -31,7 +31,7 @@ if __name__ == "__main__":
license = "GPL",
scripts = [
"scripts/certmaster", "scripts/certmaster-ca",
- "scripts/certmaster-request",
+ "scripts/certmaster-request", "scripts/certmaster-sync",
],
# package_data = { '' : ['*.*'] },
package_dir = {"%s" % NAME: "%s" % NAME