permissions cleanup on source files
[certmaster.git] / certmaster / certmaster.py
1 # FIXME: more intelligent fault raises
2
3 """
4 cert master listener
5
6 Copyright 2007, Red Hat, Inc
7 see AUTHORS
8
9 This software may be freely redistributed under the terms of the GNU
10 general public license.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 """
16
17 # standard modules
18 import SimpleXMLRPCServer
19 import sys
20 import os
21 import os.path
22 from OpenSSL import crypto
23 import sha
24 import glob
25 import socket
26 import exceptions
27
28 import certs
29 import codes
30 import utils
31
32 import logger
33
34 from config import read_config
35 from commonconfig import CMConfig
36
37 CERTMASTER_LISTEN_PORT = 51235
38 CERTMASTER_CONFIG = "/etc/certmaster/certmaster.conf"
39
40 class CertMaster(object):
41 def __init__(self, conf_file=CERTMASTER_CONFIG):
42 self.cfg = read_config(conf_file, CMConfig)
43
44 usename = utils.get_hostname(talk_to_certmaster=False)
45
46 mycn = '%s-CA-KEY' % usename
47 self.ca_key_file = '%s/certmaster.key' % self.cfg.cadir
48 self.ca_cert_file = '%s/certmaster.crt' % self.cfg.cadir
49
50 self.logger = logger.Logger().logger
51 self.audit_logger = logger.AuditLogger()
52
53 try:
54 if not os.path.exists(self.cfg.cadir):
55 os.makedirs(self.cfg.cadir)
56 if not os.path.exists(self.ca_key_file) and not os.path.exists(self.ca_cert_file):
57 certs.create_ca(CN=mycn, ca_key_file=self.ca_key_file, ca_cert_file=self.ca_cert_file)
58 except (IOError, OSError), e:
59 print 'Cannot make certmaster certificate authority keys/certs, aborting: %s' % e
60 sys.exit(1)
61
62
63 # open up the cakey and cacert so we have them available
64 self.cakey = certs.retrieve_key_from_file(self.ca_key_file)
65 self.cacert = certs.retrieve_cert_from_file(self.ca_cert_file)
66
67 for dirpath in [self.cfg.cadir, self.cfg.certroot, self.cfg.csrroot]:
68 if not os.path.exists(dirpath):
69 os.makedirs(dirpath)
70
71 # setup handlers
72 self.handlers = {
73 'wait_for_cert': self.wait_for_cert,
74 }
75
76
77 def _dispatch(self, method, params):
78 if method == 'trait_names' or method == '_getAttributeNames':
79 return self.handlers.keys()
80
81
82 if method in self.handlers.keys():
83 return self.handlers[method](*params)
84 else:
85 self.logger.info("Unhandled method call for method: %s " % method)
86 raise codes.InvalidMethodException
87
88 def _sanitize_cn(self, commonname):
89 commonname = commonname.replace('/', '')
90 commonname = commonname.replace('\\', '')
91 return commonname
92
93 def wait_for_cert(self, csrbuf, with_triggers=True):
94 """
95 takes csr as a string
96 returns True, caller_cert, ca_cert
97 returns False, '', ''
98 """
99
100 try:
101 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
102 except crypto.Error, e:
103 #XXX need to raise a fault here and document it - but false is just as good
104 return False, '', ''
105
106 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
107
108 if with_triggers:
109 self._run_triggers(requesting_host, '/var/lib/certmaster/triggers/request/pre/*')
110
111 self.logger.info("%s requested signing of cert %s" % (requesting_host,csrreq.get_subject().CN))
112 # get rid of dodgy characters in the filename we're about to make
113
114 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
115 csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
116
117 # check for old csr on disk
118 # if we have it - compare the two - if they are not the same - raise a fault
119 self.logger.debug("csrfile: %s certfile: %s" % (csrfile, certfile))
120
121 if os.path.exists(csrfile):
122 oldfo = open(csrfile)
123 oldcsrbuf = oldfo.read()
124 oldsha = sha.new()
125 oldsha.update(oldcsrbuf)
126 olddig = oldsha.hexdigest()
127 newsha = sha.new()
128 newsha.update(csrbuf)
129 newdig = newsha.hexdigest()
130 if not newdig == olddig:
131 self.logger.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host))
132 # XXX raise a proper fault
133 return False, '', ''
134
135
136 # look for a cert:
137 # if we have it, then return True, etc, etc
138 if os.path.exists(certfile):
139 slavecert = certs.retrieve_cert_from_file(certfile)
140 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
141 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
142 if with_triggers:
143 self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/request/post/*')
144 return True, cert_buf, cacert_buf
145
146 # if we don't have a cert then:
147 # if we're autosign then sign it, write out the cert and return True, etc, etc
148 # else write out the csr
149
150 if self.cfg.autosign:
151 cert_fn = self.sign_this_csr(csrreq)
152 cert = certs.retrieve_cert_from_file(cert_fn)
153 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
154 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
155 self.logger.info("cert for %s was autosigned" % (requesting_host))
156 if with_triggers:
157 self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*')
158 return True, cert_buf, cacert_buf
159
160 else:
161 # write the csr out to a file to be dealt with by the admin
162 destfo = open(csrfile, 'w')
163 destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
164 destfo.close()
165 del destfo
166 self.logger.info("cert for %s created and ready to be signed" % (requesting_host))
167 if with_triggers:
168 self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*')
169 return False, '', ''
170
171 return False, '', ''
172
173 def get_csrs_waiting(self):
174 hosts = []
175 csrglob = '%s/*.csr' % self.cfg.csrroot
176 csr_list = glob.glob(csrglob)
177 for f in csr_list:
178 hn = os.path.basename(f)
179 hn = hn[:-4]
180 hosts.append(hn)
181 return hosts
182
183 def remove_this_cert(self, hn, with_triggers=True):
184 """ removes cert for hostname using unlink """
185 cm = self
186 csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
187 csrs = glob.glob(csrglob)
188 certglob = '%s/%s.cert' % (cm.cfg.certroot, hn)
189 certs = glob.glob(certglob)
190 if not csrs and not certs:
191 # FIXME: should be an exception?
192 print 'No match for %s to clean up' % hn
193 return
194 if with_triggers:
195 self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/pre/*')
196 for fn in csrs + certs:
197 print 'Cleaning out %s for host matching %s' % (fn, hn)
198 self.logger.info('Cleaning out %s for host matching %s' % (fn, hn))
199 os.unlink(fn)
200 if with_triggers:
201 self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/post/*')
202
203 def sign_this_csr(self, csr, with_triggers=True):
204 """returns the path to the signed cert file"""
205 csr_unlink_file = None
206
207 if type(csr) is type(''):
208 if csr.startswith('/') and os.path.exists(csr): # we have a full path to the file
209 csrfo = open(csr)
210 csr_buf = csrfo.read()
211 csr_unlink_file = csr
212
213 elif os.path.exists('%s/%s' % (self.cfg.csrroot, csr)): # we have a partial path?
214 csrfo = open('%s/%s' % (self.cfg.csrroot, csr))
215 csr_buf = csrfo.read()
216 csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr)
217
218 # we have a string of some kind
219 else:
220 csr_buf = csr
221
222 try:
223 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_buf)
224 except crypto.Error, e:
225 self.logger.info("Unable to sign %s: Bad CSR" % (csr))
226 raise exceptions.Exception("Bad CSR: %s" % csr)
227
228 else: # assume we got a bare csr req
229 csrreq = csr
230
231
232 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
233 if with_triggers:
234 self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/pre/*')
235
236
237 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
238 self.logger.info("Signing for csr %s requested" % certfile)
239 thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
240
241 destfo = open(certfile, 'w')
242 destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
243 destfo.close()
244 del destfo
245
246
247 self.logger.info("csr %s signed" % (certfile))
248 if with_triggers:
249 self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/post/*')
250
251
252 if csr_unlink_file and os.path.exists(csr_unlink_file):
253 os.unlink(csr_unlink_file)
254
255 return certfile
256
257 # return a list of already signed certs
258 def get_signed_certs(self, hostglobs=None):
259 certglob = "%s/*.cert" % (self.cfg.certroot)
260
261 certs = []
262 globs = "*"
263 if hostglobs:
264 globs = hostglobs
265
266 for hostglob in globs:
267 certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob)
268 certs = certs + glob.glob(certglob)
269
270 signed_certs = []
271 for cert in certs:
272 # just want the hostname, so strip off path and ext
273 signed_certs.append(os.path.basename(cert).split(".cert", 1)[0])
274
275 return signed_certs
276
277 # return a list of the cert hash string we use to identify systems
278 def get_cert_hashes(self, hostglobs=None):
279 certglob = "%s/*.cert" % (self.cfg.certroot)
280
281 certfiles = []
282 globs = "*"
283 if hostglobs:
284 globs = hostglobs
285
286 for hostglob in globs:
287 certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob)
288 certfiles = certfiles + glob.glob(certglob)
289
290 cert_hashes = []
291 for certfile in certfiles:
292 cert = certs.retrieve_cert_from_file(certfile)
293 cert_hashes.append("%s-%s" % (cert.get_subject().CN, cert.subject_name_hash()))
294
295 return cert_hashes
296
297 def _run_triggers(self, ref, globber):
298 return utils.run_triggers(ref, globber)
299
300
301 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
302 def __init__(self, addr):
303 self.allow_reuse_address = True
304 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr)
305
306
307 def serve(xmlrpcinstance):
308
309 """
310 Code for starting the XMLRPC service.
311 """
312
313
314 config = read_config(CERTMASTER_CONFIG, CMConfig)
315 listen_addr = config.listen_addr
316 listen_port = config.listen_port
317 if listen_port == '':
318 listen_port = CERTMASTER_LISTEN_PORT
319 server = CertmasterXMLRPCServer((listen_addr,listen_port))
320 server.logRequests = 0 # don't print stuff to console
321 server.register_instance(xmlrpcinstance)
322 xmlrpcinstance.logger.info("certmaster started")
323 xmlrpcinstance.audit_logger.logger.info("certmaster started")
324 server.serve_forever()
325
326
327 def main(argv):
328
329 cm = CertMaster('/etc/certmaster/certmaster.conf')
330
331 if "daemon" in argv or "--daemon" in argv:
332 utils.daemonize("/var/run/certmaster.pid")
333 else:
334 print "serving...\n"
335
336
337 # just let exceptions bubble up for now
338 serve(cm)
339
340
341 if __name__ == "__main__":
342 #textdomain(I18N_DOMAIN)
343 main(sys.argv)