fd1db93defa429c4319a06745e08d8f24b72e49c
[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 from func.overlord.client import Client
28 from func.CommonErrors import Func_Client_Exception
29 import func.jobthing as jobthing
30
31 def syncable(cert_list):
32 """
33 Calls out to known hosts to find out who is configured for
34 peering. Returns a list of hostnames who support peering.
35 """
36 try:
37 fc = Client('*', async=True, nforks=len(cert_list))
38 except Func_Client_Exception:
39 # we are either:
40 # - signing the first minion
41 # - cleaning the only minion
42 # so there's nothing to hit. This shouldn't happen
43 # when we get called from the 'post-fetch' trigger
44 # (future work)
45 return None
46
47 # Only wait for a few seconds. Assume anything that doesn't get
48 # back by then is a lost cause. Don't want this trigger to spin
49 # too long.
50 ticks = 0
51 return_code = jobthing.JOB_ID_RUNNING
52 results = None
53 job_id = fc.certmastermod.peering_enabled()
54 while return_code != jobthing.JOB_ID_FINISHED and ticks < 3:
55 sleep(1)
56 (return_code, results) = fc.job_status(job_id)
57 ticks += 1
58
59 hosts = []
60 for host, result in results.iteritems():
61 if result == True:
62 hosts.append(host)
63 return hosts
64
65 def remote_peers(hosts):
66 """
67 Calls out to hosts to collect peer information
68 """
69 fc = Client(';'.join(hosts))
70 return fc.certmastermod.known_peers()
71
72 def local_certs():
73 """
74 Returns (hostname, sha1) hash of local certs
75 """
76 globby = '*.%s' % cm.cfg.cert_extension
77 globby = os.path.join(cm.cfg.certroot, globby)
78 files = glob(globby)
79 results = []
80 for f in files:
81 hostname = os.path.basename(f).replace('.' + cm.cfg.cert_extension, '')
82 digest = checksum(f)
83 results.append([hostname, digest])
84 return results
85
86 def checksum(f):
87 thissum = hashlib.new('sha1')
88 if os.path.exists(f):
89 fo = open(f, 'r')
90 data = fo.read()
91 fo.close()
92 thissum.update(data)
93
94 return thissum.hexdigest()
95
96 def remove_stale_certs(local, remote):
97 """
98 For each cert on each remote host, make sure it exists locally.
99 If not then it has been cleaned locally and needs unlinked
100 remotely.
101 """
102 local = [foo[0] for foo in local] # don't care about checksums
103 for host, peers in remote.iteritems():
104 fc = Client(host)
105 die = []
106 for peer in peers:
107 if peer[0] not in local:
108 die.append(peer[0])
109 if die != []:
110 fc.certmastermod.remove_peer_certs(die)
111
112 def copy_updated_certs(local, remote):
113 """
114 For each local cert, make sure it exists on the remote with the
115 correct hash. If not, copy it over!
116 """
117 for host, peers in remote.iteritems():
118 fc = Client(host)
119 for cert in local:
120 if cert not in peers:
121 cert_name = '%s.%s' % (cert[0], cm.cfg.cert_extension)
122 full_path = os.path.join(cm.cfg.certroot, cert_name)
123 fd = open(full_path)
124 certblob = fd.read()
125 fd.close()
126 fc.certmastermod.copy_peer_cert(cert[0], xmlrpclib.Binary(certblob))
127
128 def main():
129 forced = False
130 try:
131 if sys.argv[1] in ['-f', '--force']:
132 forced = True
133 except IndexError:
134 pass
135
136 if not cm.cfg.sync_certs and not forced:
137 sys.exit(0)
138
139 certs = glob(os.path.join(cm.cfg.certroot,
140 '*.%s' % cm.cfg.cert_extension))
141 hosts = syncable(certs)
142 if not hosts:
143 return 0
144 remote = remote_peers(hosts)
145 local = local_certs()
146 remove_stale_certs(local, remote)
147 copy_updated_certs(local, remote)
148
149 if __name__ == "__main__":
150 cm = certmaster.CertMaster()
151 main()