(Not working yet, but the changeset was getting too big: The service starts, but...
[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 dirname = os.path.dirname(f)
83 digest = checksum(f)
84 results.append([hostname, digest, dirname])
85 return results
86
87 def checksum(f):
88 thissum = hashlib.new('sha1')
89 if os.path.exists(f):
90 fo = open(f, 'r')
91 data = fo.read()
92 fo.close()
93 thissum.update(data)
94
95 return thissum.hexdigest()
96
97 def remove_stale_certs(local, remote):
98 """
99 For each cert on each remote host, make sure it exists locally.
100 If not then it has been cleaned locally and needs unlinked
101 remotely.
102 """
103 local = [foo[0] for foo in local] # don't care about checksums
104 for host, peers in remote.iteritems():
105 fc = Client(host)
106 die = []
107 for peer in peers:
108 if peer[0] not in local:
109 die.append(peer[0])
110 if die != []:
111 fc.certmastermod.remove_peer_certs(die)
112
113 def copy_updated_certs(local, remote):
114 """
115 For each local cert, make sure it exists on the remote with the
116 correct hash. If not, copy it over!
117 """
118 for host, peers in remote.iteritems():
119 fc = Client(host)
120 for cert in local:
121 if cert not in peers:
122 cert_name = '%s.%s' % (cert[0], cm.cfg.cert_extension)
123 full_path = os.path.join(cert[2], cert_name)
124 fd = open(full_path)
125 certblob = fd.read()
126 fd.close()
127 fc.certmastermod.copy_peer_cert(cert[0], xmlrpclib.Binary(certblob))
128
129 def main():
130 forced = False
131 try:
132 if sys.argv[1] in ['-f', '--force']:
133 forced = True
134 except IndexError:
135 pass
136
137 if not cm.cfg.sync_certs and not forced:
138 sys.exit(0)
139
140 certs = glob(os.path.join(cm.cfg.certroot,
141 '*.%s' % cm.cfg.cert_extension))
142 hosts = syncable(certs)
143 if not hosts:
144 return 0
145 remote = remote_peers(hosts)
146 local = local_certs()
147 remove_stale_certs(local, remote)
148 copy_updated_certs(local, remote)
149
150 if __name__ == "__main__":
151 cm = certmaster.CertMaster()
152 main()