1 # FIXME: more intelligent fault raises
6 Copyright 2007, Red Hat, Inc
9 This software may be freely redistributed under the terms of the GNU
10 general public license.
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.
18 import SimpleXMLRPCServer
24 from OpenSSL
import crypto
29 # Python-2.4.z ... gah! (or even 2.3!)
36 raise ValueError, "Bad checksum type"
48 from config
import read_config
49 from commonconfig
import CMConfig
51 CERTMASTER_LISTEN_PORT
= 51235
52 CERTMASTER_CONFIG
= "/etc/certmaster/certmaster.conf"
54 class CertMaster(object):
55 def __init__(self
, conf_file
=CERTMASTER_CONFIG
):
56 self
.cfg
= read_config(conf_file
, CMConfig
)
58 usename
= utils
.get_hostname(talk_to_certmaster
=False)
60 self
.logger
= logger
.Logger().logger
61 self
.audit_logger
= logger
.AuditLogger()
66 for (s_caname
,a_ca
) in self
.cfg
.ca
.iteritems():
70 mycn
= '%s-CA-KEY' % usename
72 mycn
= '%s-%s-CA-KEY' % (s_caname
.upper(),usename
)
74 s_ca_key_file
= '%s/certmaster.key' % s_cadir
75 s_ca_cert_file
= '%s/certmaster.crt' % s_cadir
77 # if ca_key_file exists and ca_cert_file is missing == minion only setup
78 if os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
82 if not os
.path
.exists(s_cadir
):
84 if not os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
85 certs
.create_ca(CN
=mycn
, ca_key_file
=s_ca_key_file
, ca_cert_file
=s_ca_cert_file
)
86 except (IOError, OSError), e
:
87 print 'Cannot make certmaster certificate authority keys/certs for CA %s, aborting: %s' % (s_caname
, e
)
90 # open up the cakey and cacert so we have them available
91 a_ca
.cakey
= certs
.retrieve_key_from_file(s_ca_key_file
)
92 a_ca
.cacert
= certs
.retrieve_cert_from_file(s_ca_cert_file
)
94 for dirpath
in [a_ca
.cadir
, a_ca
.certroot
, a_ca
.csrroot
, a_ca
.csrroot
]:
95 if not os
.path
.exists(dirpath
):
100 'wait_for_cert': self
.wait_for_cert
,
104 def _dispatch(self
, method
, params
):
105 if method
== 'trait_names' or method
== '_getAttributeNames':
106 return self
.handlers
.keys()
109 if method
in self
.handlers
.keys():
110 return self
.handlers
[method
](*params
)
112 self
.logger
.info("Unhandled method call for method: %s " % method
)
113 raise codes
.InvalidMethodException
115 def _sanitize_cn(self
, commonname
):
116 commonname
= commonname
.replace('/', '')
117 commonname
= commonname
.replace('\\', '')
120 def wait_for_cert(self
, csrbuf
, ca_name
, with_triggers
=True):
122 takes csr as a string
123 returns True, caller_cert, ca_cert
124 returns False, '', ''
128 certauth
= self
.cfg
.ca
[ca_name
]
130 raise codes
.CMException("Unknown cert authority: %s" % ca_name
)
133 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csrbuf
)
134 except crypto
.Error
, e
:
135 #XXX need to raise a fault here and document it - but false is just as good
138 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
141 self
._run
_triggers
(requesting_host
, '/var/lib/certmaster/triggers/request/pre/*')
143 self
.logger
.info("%s requested signing of cert %s" % (requesting_host
,csrreq
.get_subject().CN
))
144 # get rid of dodgy characters in the filename we're about to make
146 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
147 csrfile
= '%s/%s.csr' % (certauth
.csrroot
, requesting_host
)
149 # check for old csr on disk
150 # if we have it - compare the two - if they are not the same - raise a fault
151 self
.logger
.debug("csrfile: %s certfile: %s" % (csrfile
, certfile
))
153 if os
.path
.exists(csrfile
):
154 oldfo
= open(csrfile
)
155 oldcsrbuf
= oldfo
.read()
156 oldsha
= hashlib
.new('sha1')
157 oldsha
.update(oldcsrbuf
)
158 olddig
= oldsha
.hexdigest()
159 newsha
= hashlib
.new('sha1')
160 newsha
.update(csrbuf
)
161 newdig
= newsha
.hexdigest()
162 if not newdig
== olddig
:
163 self
.logger
.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host
))
164 # XXX raise a proper fault
169 # if we have it, then return True, etc, etc
170 if os
.path
.exists(certfile
):
171 slavecert
= certs
.retrieve_cert_from_file(certfile
)
172 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, slavecert
)
173 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
175 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/request/post/*')
176 return True, cert_buf
, cacert_buf
178 # if we don't have a cert then:
179 # if we're autosign then sign it, write out the cert and return True, etc, etc
180 # else write out the csr
182 if certauth
.autosign
:
183 cert_fn
= self
.sign_this_csr(csrreq
,certauth
)
184 cert
= certs
.retrieve_cert_from_file(cert_fn
)
185 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
186 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
187 self
.logger
.info("cert for %s for ca %s was autosigned" % (requesting_host
,ca_name
))
189 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
190 return True, cert_buf
, cacert_buf
193 # write the csr out to a file to be dealt with by the admin
194 destfo
= open(csrfile
, 'w')
195 destfo
.write(crypto
.dump_certificate_request(crypto
.FILETYPE_PEM
, csrreq
))
198 self
.logger
.info("cert for %s for CA %s created and ready to be signed" % (requesting_host
, ca_name
))
200 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
205 def get_csrs_waiting(self
, certauth
):
207 csrglob
= '%s/*.csr' % certauth
.csrroot
208 csr_list
= glob
.glob(csrglob
)
210 hn
= os
.path
.basename(f
)
215 def remove_this_cert(self
, hn
, certauth
, with_triggers
=True):
216 """ removes cert for hostname using unlink """
218 csrglob
= '%s/%s.csr' % (certauth
.csrroot
, hn
)
219 csrs
= glob
.glob(csrglob
)
220 certglob
= '%s/%s.cert' % (certauth
.certroot
, hn
)
221 certs
= glob
.glob(certglob
)
222 if not csrs
and not certs
:
223 # FIXME: should be an exception?
224 print 'No match for %s to clean up' % hn
227 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/pre/*')
228 for fn
in csrs
+ certs
:
229 print 'Cleaning out %s for host matching %s' % (fn
, hn
)
230 self
.logger
.info('Cleaning out %s for host matching %s' % (fn
, hn
))
233 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/post/*')
235 def sign_this_csr(self
, csr
, certauth
,with_triggers
=True):
236 """returns the path to the signed cert file"""
237 csr_unlink_file
= None
239 if type(csr
) is type(''):
240 if csr
.startswith('/') and os
.path
.exists(csr
): # we have a full path to the file
242 csr_buf
= csrfo
.read()
243 csr_unlink_file
= csr
245 elif os
.path
.exists('%s/%s' % (certauth
.csrroot
, csr
)): # we have a partial path?
246 csrfo
= open('%s/%s' % (certauth
.csrroot
, csr
))
247 csr_buf
= csrfo
.read()
248 csr_unlink_file
= '%s/%s' % (certauth
.csrroot
, csr
)
250 # we have a string of some kind
255 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csr_buf
)
256 except crypto
.Error
, e
:
257 self
.logger
.info("Unable to sign %s: Bad CSR" % (csr
))
258 raise exceptions
.Exception("Bad CSR: %s" % csr
)
260 else: # assume we got a bare csr req
264 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
266 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/pre/*')
269 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
270 self
.logger
.info("Signing for csr %s requested" % certfile
)
271 thiscert
= certs
.create_slave_certificate(csrreq
, certauth
.cakey
, certauth
.cacert
, certauth
.cadir
)
273 destfo
= open(certfile
, 'w')
274 destfo
.write(crypto
.dump_certificate(crypto
.FILETYPE_PEM
, thiscert
))
279 self
.logger
.info("csr %s signed" % (certfile
))
281 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/post/*')
284 if csr_unlink_file
and os
.path
.exists(csr_unlink_file
):
285 os
.unlink(csr_unlink_file
)
289 # return a list of already signed certs
290 def get_signed_certs(self
, certauth
,hostglobs
=None):
291 certglob
= "%s/*.cert" % (certauth
.certroot
)
298 for hostglob
in globs
:
299 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
300 certs
= certs
+ glob
.glob(certglob
)
304 # just want the hostname, so strip off path and ext
305 signed_certs
.append(os
.path
.basename(cert
).split(".cert", 1)[0])
309 def get_peer_certs(self
):
311 Returns a list of all certs under peerroot
313 myglob
= os
.path
.join(self
.cfg
.peerroot
, '*.%s' % self
.cfg
.cert_extension
)
314 return glob
.glob(myglob
)
316 # return a list of the cert hash string we use to identify systems
317 def get_cert_hashes(self
, certauth
, hostglobs
=None):
318 certglob
= "%s/*.cert" % (certauth
.certroot
)
325 for hostglob
in globs
:
326 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
327 certfiles
= certfiles
+ glob
.glob(certglob
)
330 for certfile
in certfiles
:
331 cert
= certs
.retrieve_cert_from_file(certfile
)
332 cert_hashes
.append("%s-%s" % (cert
.get_subject().CN
, cert
.subject_name_hash()))
336 def _run_triggers(self
, ref
, globber
):
337 return utils
.run_triggers(ref
, globber
)
340 class CertmasterXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
):
341 def __init__(self
, addr
):
342 self
.allow_reuse_address
= True
343 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, addr
)
346 def serve(xmlrpcinstance
):
349 Code for starting the XMLRPC service.
353 config
= read_config(CERTMASTER_CONFIG
, CMConfig
)
354 listen_addr
= config
.listen_addr
355 listen_port
= config
.listen_port
356 if listen_port
== '':
357 listen_port
= CERTMASTER_LISTEN_PORT
358 server
= CertmasterXMLRPCServer((listen_addr
,listen_port
))
359 server
.logRequests
= 0 # don't print stuff to console
360 server
.register_instance(xmlrpcinstance
)
361 xmlrpcinstance
.logger
.info("certmaster started")
362 xmlrpcinstance
.audit_logger
.logger
.info("certmaster started")
363 server
.serve_forever()
365 def excepthook(exctype
, value
, tracebackobj
):
366 exctype_blurb
= "Exception occured: %s" % exctype
367 excvalue_blurb
= "Exception value: %s" % value
368 exctb_blurb
= "Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tracebackobj
)))
374 log
= logger
.Logger().logger
375 log
.info(exctype_blurb
)
376 log
.info(excvalue_blurb
)
377 log
.info(exctb_blurb
)
382 sys
.excepthook
= excepthook
383 cm
= CertMaster('/etc/certmaster/certmaster.conf')
385 if "--version" in sys
.argv
or "-v" in sys
.argv
:
386 print >> sys
.stderr
, file("/etc/certmaster/version").read().strip()
389 if "daemon" in argv
or "--daemon" in argv
:
390 utils
.daemonize("/var/run/certmaster.pid")
394 # just let exceptions bubble up for now
398 if __name__
== "__main__":
399 #textdomain(I18N_DOMAIN)