Looks like certmaster-request and certmaster-ca are working with the new --ca flag.
[certmaster.git] / scripts / certmaster-sync
1 #!/usr/bin/python -tt
2
3 # Syncs the valid CA-signed certificates from certmaster to all known
4 # hosts via func. To be called during the post-sign hook to copy new
5 # certificates and from the post-clean hook in order to purge stale
6 # certificates. Requires 'sync_certs' to be set in certmaster.conf.
7
8 import os
9 import sys
10 try:
11 import hashlib
12 except ImportError:
13 # Python-2.4.z ... gah! (or even 2.3!)
14 import sha
15 class hashlib:
16 @staticmethod
17 def new(algo):
18 if algo == 'sha1':
19 return sha.new()
20 raise ValueError, "Bad checksum type"
21
22
23 import xmlrpclib
24 from glob import glob
25 from time import sleep
26 from certmaster import certmaster as certmaster
27
28 func_import_failure = None
29 try:
30 from func.overlord.client import Client
31 from func.CommonErrors import Func_Client_Exception
32 import func.jobthing as jobthing
33 except ImportError, e:
34 func_import_failure = str(e)
35
36 def syncable(cert_list):
37 """
38 Calls out to known hosts to find out who is configured for
39 peering. Returns a list of hostnames who support peering.
40 """
41 try:
42 fc = Client('*', async=True, nforks=len(cert_list))
43 except Func_Client_Exception:
44 # we are either:
45 # - signing the first minion
46 # - cleaning the only minion
47 # so there's nothing to hit. This shouldn't happen
48 # when we get called from the 'post-fetch' trigger
49 # (future work)
50 return None
51
52 # Only wait for a few seconds. Assume anything that doesn't get
53 # back by then is a lost cause. Don't want this trigger to spin
54 # too long.
55 ticks = 0
56 return_code = jobthing.JOB_ID_RUNNING
57 results = None
58 job_id = fc.certmastermod.peering_enabled()
59 while return_code != jobthing.JOB_ID_FINISHED and ticks < 3:
60 sleep(1)
61 (return_code, results) = fc.job_status(job_id)
62 ticks += 1
63
64 hosts = []
65 for host, result in results.iteritems():
66 if result == True:
67 hosts.append(host)
68 return hosts
69
70 def remote_peers(hosts):
71 """
72 Calls out to hosts to collect peer information
73 """
74 fc = Client(';'.join(hosts))
75 return fc.certmastermod.known_peers()
76
77 def local_certs():
78 """
79 Returns (hostname, sha1) hash of local certs
80 """
81 globby = '*.%s' % cm.cfg.cert_extension
82 globby = os.path.join(cm.cfg.certroot, globby)
83 files = glob(globby)
84 results = []
85 for f in files:
86 hostname = os.path.basename(f).replace('.' + cm.cfg.cert_extension, '')
87 dirname = os.path.dirname(f)
88 digest = checksum(f)
89 results.append([hostname, digest, dirname])
90 return results
91
92 def checksum(f):
93 thissum = hashlib.new('sha1')
94 if os.path.exists(f):
95 fo = open(f, 'r')
96 data = fo.read()
97 fo.close()
98 thissum.update(data)
99
100 return thissum.hexdigest()
101
102 def remove_stale_certs(local, remote):
103 """
104 For each cert on each remote host, make sure it exists locally.
105 If not then it has been cleaned locally and needs unlinked
106 remotely.
107 """
108 local = [foo[0] for foo in local] # don't care about checksums
109 for host, peers in remote.iteritems():
110 fc = Client(host)
111 die = []
112 for peer in peers:
113 if peer[0] not in local:
114 die.append(peer[0])
115 if die != []:
116 fc.certmastermod.remove_peer_certs(die)
117
118 def copy_updated_certs(local, remote):
119 """
120 For each local cert, make sure it exists on the remote with the
121 correct hash. If not, copy it over!
122 """
123 for host, peers in remote.iteritems():
124 fc = Client(host)
125 for cert in local:
126 if cert not in peers:
127 cert_name = '%s.%s' % (cert[0], cm.cfg.cert_extension)
128 full_path = os.path.join(cert[2], cert_name)
129 fd = open(full_path)
130 certblob = fd.read()
131 fd.close()
132 fc.certmastermod.copy_peer_cert(cert[0], xmlrpclib.Binary(certblob))
133
134 def main():
135 forced = False
136 try:
137 if sys.argv[1] in ['-f', '--force']:
138 forced = True
139 except IndexError:
140 pass
141
142 if not cm.cfg.sync_certs and not forced:
143 sys.exit(0)
144
145 # Don't complain about func not being available until you actually want it
146 if func_import_failure != None:
147 print >> sys.stderr, "errors importing func: %s" % func_import_failure
148 sys.exit(1)
149
150 certs = glob(os.path.join(cm.cfg.certroot,
151 '*.%s' % cm.cfg.cert_extension))
152 hosts = syncable(certs)
153 if not hosts:
154 return 0
155 remote = remote_peers(hosts)
156 local = local_certs()
157 remove_stale_certs(local, remote)
158 copy_updated_certs(local, remote)
159
160 if __name__ == "__main__":
161 cm = certmaster.CertMaster()
162 main()