594736206ce31d99123bd0c19c4d3bb4df8df2ca
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
25 from OpenSSL
import crypto
30 # Python-2.4.z ... gah! (or even 2.3!)
36 # TODO: jude: was warnings even available in 2.4 ?
37 warnings
.warn("sha1 is deprecated", DeprecationWarning)
39 raise ValueError, "Bad checksum type"
51 from config
import read_config
52 from commonconfig
import CMConfig
54 CERTMASTER_LISTEN_PORT
= 51235
55 CERTMASTER_CONFIG
= "/etc/certmaster/certmaster.conf"
57 class CertMaster(object):
58 def __init__(self
, conf_file
=CERTMASTER_CONFIG
):
59 self
.cfg
= read_config(conf_file
, CMConfig
)
61 usename
= utils
.get_hostname(talk_to_certmaster
=False)
63 self
.logger
= logger
.Logger().logger
64 self
.audit_logger
= logger
.AuditLogger()
69 for (s_caname
,a_ca
) in self
.cfg
.ca
.iteritems():
73 mycn
= '%s-CA-KEY' % usename
75 mycn
= '%s-%s-CA-KEY' % (s_caname
.upper(),usename
)
77 s_ca_key_file
= '%s/certmaster.key' % s_cadir
78 s_ca_cert_file
= '%s/certmaster.crt' % s_cadir
80 # if ca_key_file exists and ca_cert_file is missing == minion only setup
81 if os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
85 if not os
.path
.exists(s_cadir
):
87 if not os
.path
.exists(s_ca_key_file
) and not os
.path
.exists(s_ca_cert_file
):
88 certs
.create_ca(CN
=mycn
, ca_key_file
=s_ca_key_file
, ca_cert_file
=s_ca_cert_file
, hash_function
=a_ca
.hash_function
)
89 except (IOError, OSError), e
:
90 print 'Cannot make certmaster certificate authority keys/certs for CA %s, aborting: %s' % (s_caname
, e
)
93 # open up the cakey and cacert so we have them available
94 a_ca
.cakey
= certs
.retrieve_key_from_file(s_ca_key_file
)
95 a_ca
.cacert
= certs
.retrieve_cert_from_file(s_ca_cert_file
)
97 for dirpath
in [a_ca
.cadir
, a_ca
.certroot
, a_ca
.csrroot
, a_ca
.csrroot
]:
98 if not os
.path
.exists(dirpath
):
103 'wait_for_cert': self
.wait_for_cert
,
107 def _dispatch(self
, method
, params
):
108 if method
== 'trait_names' or method
== '_getAttributeNames':
109 return self
.handlers
.keys()
112 if method
in self
.handlers
.keys():
113 return self
.handlers
[method
](*params
)
115 self
.logger
.info("Unhandled method call for method: %s " % method
)
116 raise codes
.InvalidMethodException
118 def _sanitize_cn(self
, commonname
):
119 commonname
= commonname
.replace('/', '')
120 commonname
= commonname
.replace('\\', '')
123 def wait_for_cert(self
, csrbuf
, ca_name
, with_triggers
=True):
125 takes csr as a string
126 returns True, caller_cert, ca_cert
127 returns False, '', ''
131 certauth
= self
.cfg
.ca
[ca_name
]
133 raise codes
.CMException("Unknown cert authority: %s" % ca_name
)
136 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csrbuf
)
137 except crypto
.Error
, e
:
138 #XXX need to raise a fault here and document it - but false is just as good
141 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
144 self
._run
_triggers
(requesting_host
, '/var/lib/certmaster/triggers/request/pre/*')
146 self
.logger
.info("%s requested signing of cert %s" % (requesting_host
,csrreq
.get_subject().CN
))
147 # get rid of dodgy characters in the filename we're about to make
149 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
150 csrfile
= '%s/%s.csr' % (certauth
.csrroot
, requesting_host
)
152 # check for old csr on disk
153 # if we have it - compare the two - if they are not the same - raise a fault
154 self
.logger
.debug("csrfile: %s certfile: %s" % (csrfile
, certfile
))
156 if os
.path
.exists(csrfile
):
157 oldfo
= open(csrfile
)
158 oldcsrbuf
= oldfo
.read()
159 oldsha
= hashlib
.new(certauth
.hash_function
)
160 oldsha
.update(oldcsrbuf
)
161 olddig
= oldsha
.hexdigest()
162 newsha
= hashlib
.new(certauth
.hash_function
)
163 newsha
.update(csrbuf
)
164 newdig
= newsha
.hexdigest()
165 if not newdig
== olddig
:
166 self
.logger
.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host
))
167 # XXX raise a proper fault
172 # if we have it, then return True, etc, etc
173 if os
.path
.exists(certfile
):
174 slavecert
= certs
.retrieve_cert_from_file(certfile
)
175 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, slavecert
)
176 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
178 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/request/post/*')
179 return True, cert_buf
, cacert_buf
181 # if we don't have a cert then:
182 # if we're autosign then sign it, write out the cert and return True, etc, etc
183 # else write out the csr
185 if certauth
.autosign
:
186 cert_fn
= self
.sign_this_csr(csrreq
,certauth
)
187 cert
= certs
.retrieve_cert_from_file(cert_fn
)
188 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
189 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, certauth
.cacert
)
190 self
.logger
.info("cert for %s for ca %s was autosigned" % (requesting_host
,ca_name
))
192 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
193 return True, cert_buf
, cacert_buf
196 # write the csr out to a file to be dealt with by the admin
197 destfo
= open(csrfile
, 'w')
198 destfo
.write(crypto
.dump_certificate_request(crypto
.FILETYPE_PEM
, csrreq
))
201 self
.logger
.info("cert for %s for CA %s created and ready to be signed" % (requesting_host
, ca_name
))
203 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
208 def get_csrs_waiting(self
, certauth
):
210 csrglob
= '%s/*.csr' % certauth
.csrroot
211 csr_list
= glob
.glob(csrglob
)
213 hn
= os
.path
.basename(f
)
218 def remove_this_cert(self
, hn
, certauth
, with_triggers
=True):
219 """ removes cert for hostname using unlink """
221 csrglob
= '%s/%s.csr' % (certauth
.csrroot
, hn
)
222 csrs
= glob
.glob(csrglob
)
223 certglob
= '%s/%s.cert' % (certauth
.certroot
, hn
)
224 certs
= glob
.glob(certglob
)
225 if not csrs
and not certs
:
226 # FIXME: should be an exception?
227 print 'No match for %s to clean up' % hn
230 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/pre/*')
231 for fn
in csrs
+ certs
:
232 print 'Cleaning out %s for host matching %s' % (fn
, hn
)
233 self
.logger
.info('Cleaning out %s for host matching %s' % (fn
, hn
))
236 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/post/*')
238 def sign_this_csr(self
, csr
, certauth
,with_triggers
=True):
239 """returns the path to the signed cert file"""
240 csr_unlink_file
= None
242 if type(csr
) is type(''):
243 if csr
.startswith('/') and os
.path
.exists(csr
): # we have a full path to the file
245 csr_buf
= csrfo
.read()
246 csr_unlink_file
= csr
248 elif os
.path
.exists('%s/%s' % (certauth
.csrroot
, csr
)): # we have a partial path?
249 csrfo
= open('%s/%s' % (certauth
.csrroot
, csr
))
250 csr_buf
= csrfo
.read()
251 csr_unlink_file
= '%s/%s' % (certauth
.csrroot
, csr
)
253 # we have a string of some kind
258 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csr_buf
)
259 except crypto
.Error
, e
:
260 self
.logger
.info("Unable to sign %s: Bad CSR" % (csr
))
261 raise exceptions
.Exception("Bad CSR: %s" % csr
)
263 else: # assume we got a bare csr req
267 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
269 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/pre/*')
272 certfile
= '%s/%s.cert' % (certauth
.certroot
, requesting_host
)
273 self
.logger
.info("Signing for csr %s requested" % certfile
)
274 thiscert
= certs
.create_slave_certificate(csrreq
, certauth
.cakey
, certauth
.cacert
, certauth
.cadir
, certauth
.hash_function
)
276 destfo
= open(certfile
, 'w')
277 destfo
.write(crypto
.dump_certificate(crypto
.FILETYPE_PEM
, thiscert
))
282 self
.logger
.info("csr %s signed" % (certfile
))
284 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/post/*')
287 if csr_unlink_file
and os
.path
.exists(csr_unlink_file
):
288 os
.unlink(csr_unlink_file
)
292 # return a list of already signed certs
293 def get_signed_certs(self
, certauth
,hostglobs
=None):
294 certglob
= "%s/*.cert" % (certauth
.certroot
)
301 for hostglob
in globs
:
302 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
303 certs
= certs
+ glob
.glob(certglob
)
307 # just want the hostname, so strip off path and ext
308 signed_certs
.append(os
.path
.basename(cert
).split(".cert", 1)[0])
312 def get_peer_certs(self
):
314 Returns a list of all certs under peerroot
316 myglob
= os
.path
.join(self
.cfg
.peerroot
, '*.%s' % self
.cfg
.cert_extension
)
317 return glob
.glob(myglob
)
319 # return a list of the cert hash string we use to identify systems
320 def get_cert_hashes(self
, certauth
, hostglobs
=None):
321 certglob
= "%s/*.cert" % (certauth
.certroot
)
328 for hostglob
in globs
:
329 certglob
= "%s/%s.cert" % (certauth
.certroot
, hostglob
)
330 certfiles
= certfiles
+ glob
.glob(certglob
)
333 for certfile
in certfiles
:
334 cert
= certs
.retrieve_cert_from_file(certfile
)
335 cert_hashes
.append("%s-%s" % (cert
.get_subject().CN
, cert
.subject_name_hash()))
339 def _run_triggers(self
, ref
, globber
):
340 return utils
.run_triggers(ref
, globber
)
343 class CertmasterXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
):
344 def __init__(self
, addr
):
345 self
.allow_reuse_address
= True
346 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, addr
)
349 def serve(xmlrpcinstance
):
352 Code for starting the XMLRPC service.
356 config
= read_config(CERTMASTER_CONFIG
, CMConfig
)
357 listen_addr
= config
.listen_addr
358 listen_port
= config
.listen_port
359 if listen_port
== '':
360 listen_port
= CERTMASTER_LISTEN_PORT
361 server
= CertmasterXMLRPCServer((listen_addr
,listen_port
))
362 server
.logRequests
= 0 # don't print stuff to console
363 server
.register_instance(xmlrpcinstance
)
364 xmlrpcinstance
.logger
.info("certmaster started")
365 xmlrpcinstance
.audit_logger
.logger
.info("certmaster started")
366 server
.serve_forever()
368 def excepthook(exctype
, value
, tracebackobj
):
369 exctype_blurb
= "Exception occured: %s" % exctype
370 excvalue_blurb
= "Exception value: %s" % value
371 exctb_blurb
= "Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tracebackobj
)))
377 log
= logger
.Logger().logger
378 log
.info(exctype_blurb
)
379 log
.info(excvalue_blurb
)
380 log
.info(exctb_blurb
)
385 sys
.excepthook
= excepthook
386 cm
= CertMaster('/etc/certmaster/certmaster.conf')
388 if "--version" in sys
.argv
or "-v" in sys
.argv
:
389 print >> sys
.stderr
, file("/etc/certmaster/version").read().strip()
392 if "daemon" in argv
or "--daemon" in argv
:
393 utils
.daemonize("/var/run/certmaster.pid")
397 # just let exceptions bubble up for now
401 if __name__
== "__main__":
402 #textdomain(I18N_DOMAIN)