58cb50b5f66a2dcc146550754490d5daa425595d
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
36 from config
import read_config
37 from commonconfig
import CMConfig
39 CERTMASTER_LISTEN_PORT
= 51235
40 CERTMASTER_CONFIG
= "/etc/certmaster/certmaster.conf"
42 class CertMaster(object):
43 def __init__(self
, conf_file
=CERTMASTER_CONFIG
):
44 self
.cfg
= read_config(conf_file
, CMConfig
)
46 usename
= utils
.get_hostname(talk_to_certmaster
=False)
48 mycn
= '%s-CA-KEY' % usename
49 self
.ca_key_file
= '%s/certmaster.key' % self
.cfg
.cadir
50 self
.ca_cert_file
= '%s/certmaster.crt' % self
.cfg
.cadir
52 self
.logger
= logger
.Logger().logger
53 self
.audit_logger
= logger
.AuditLogger()
56 if not os
.path
.exists(self
.cfg
.cadir
):
57 os
.makedirs(self
.cfg
.cadir
)
58 if not os
.path
.exists(self
.ca_key_file
) and not os
.path
.exists(self
.ca_cert_file
):
59 certs
.create_ca(CN
=mycn
, ca_key_file
=self
.ca_key_file
, ca_cert_file
=self
.ca_cert_file
)
60 except (IOError, OSError), e
:
61 print 'Cannot make certmaster certificate authority keys/certs, aborting: %s' % e
65 # open up the cakey and cacert so we have them available
66 self
.cakey
= certs
.retrieve_key_from_file(self
.ca_key_file
)
67 self
.cacert
= certs
.retrieve_cert_from_file(self
.ca_cert_file
)
69 for dirpath
in [self
.cfg
.cadir
, self
.cfg
.certroot
, self
.cfg
.csrroot
]:
70 if not os
.path
.exists(dirpath
):
75 'wait_for_cert': self
.wait_for_cert
,
79 def _dispatch(self
, method
, params
):
80 if method
== 'trait_names' or method
== '_getAttributeNames':
81 return self
.handlers
.keys()
84 if method
in self
.handlers
.keys():
85 return self
.handlers
[method
](*params
)
87 self
.logger
.info("Unhandled method call for method: %s " % method
)
88 raise codes
.InvalidMethodException
90 def _sanitize_cn(self
, commonname
):
91 commonname
= commonname
.replace('/', '')
92 commonname
= commonname
.replace('\\', '')
95 def wait_for_cert(self
, csrbuf
, with_triggers
=True):
98 returns True, caller_cert, ca_cert
103 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csrbuf
)
104 except crypto
.Error
, e
:
105 #XXX need to raise a fault here and document it - but false is just as good
108 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
111 self
._run
_triggers
(requesting_host
, '/var/lib/certmaster/triggers/request/pre/*')
113 self
.logger
.info("%s requested signing of cert %s" % (requesting_host
,csrreq
.get_subject().CN
))
114 # get rid of dodgy characters in the filename we're about to make
116 certfile
= '%s/%s.cert' % (self
.cfg
.certroot
, requesting_host
)
117 csrfile
= '%s/%s.csr' % (self
.cfg
.csrroot
, requesting_host
)
119 # check for old csr on disk
120 # if we have it - compare the two - if they are not the same - raise a fault
121 self
.logger
.debug("csrfile: %s certfile: %s" % (csrfile
, certfile
))
123 if os
.path
.exists(csrfile
):
124 oldfo
= open(csrfile
)
125 oldcsrbuf
= oldfo
.read()
127 oldsha
.update(oldcsrbuf
)
128 olddig
= oldsha
.hexdigest()
130 newsha
.update(csrbuf
)
131 newdig
= newsha
.hexdigest()
132 if not newdig
== olddig
:
133 self
.logger
.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host
))
134 # XXX raise a proper fault
139 # if we have it, then return True, etc, etc
140 if os
.path
.exists(certfile
):
141 slavecert
= certs
.retrieve_cert_from_file(certfile
)
142 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, slavecert
)
143 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, self
.cacert
)
145 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/request/post/*')
146 return True, cert_buf
, cacert_buf
148 # if we don't have a cert then:
149 # if we're autosign then sign it, write out the cert and return True, etc, etc
150 # else write out the csr
152 if self
.cfg
.autosign
:
153 cert_fn
= self
.sign_this_csr(csrreq
)
154 cert
= certs
.retrieve_cert_from_file(cert_fn
)
155 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
156 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, self
.cacert
)
157 self
.logger
.info("cert for %s was autosigned" % (requesting_host
))
159 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
160 return True, cert_buf
, cacert_buf
163 # write the csr out to a file to be dealt with by the admin
164 destfo
= open(csrfile
, 'w')
165 destfo
.write(crypto
.dump_certificate_request(crypto
.FILETYPE_PEM
, csrreq
))
168 self
.logger
.info("cert for %s created and ready to be signed" % (requesting_host
))
170 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
175 def get_csrs_waiting(self
):
177 csrglob
= '%s/*.csr' % self
.cfg
.csrroot
178 csr_list
= glob
.glob(csrglob
)
180 hn
= os
.path
.basename(f
)
185 def remove_this_cert(self
, hn
, with_triggers
=True):
186 """ removes cert for hostname using unlink """
188 csrglob
= '%s/%s.csr' % (cm
.cfg
.csrroot
, hn
)
189 csrs
= glob
.glob(csrglob
)
190 certglob
= '%s/%s.cert' % (cm
.cfg
.certroot
, hn
)
191 certs
= glob
.glob(certglob
)
192 if not csrs
and not certs
:
193 # FIXME: should be an exception?
194 print 'No match for %s to clean up' % hn
197 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/pre/*')
198 for fn
in csrs
+ certs
:
199 print 'Cleaning out %s for host matching %s' % (fn
, hn
)
200 self
.logger
.info('Cleaning out %s for host matching %s' % (fn
, hn
))
203 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/post/*')
205 def sign_this_csr(self
, csr
, with_triggers
=True):
206 """returns the path to the signed cert file"""
207 csr_unlink_file
= None
209 if type(csr
) is type(''):
210 if csr
.startswith('/') and os
.path
.exists(csr
): # we have a full path to the file
212 csr_buf
= csrfo
.read()
213 csr_unlink_file
= csr
215 elif os
.path
.exists('%s/%s' % (self
.cfg
.csrroot
, csr
)): # we have a partial path?
216 csrfo
= open('%s/%s' % (self
.cfg
.csrroot
, csr
))
217 csr_buf
= csrfo
.read()
218 csr_unlink_file
= '%s/%s' % (self
.cfg
.csrroot
, csr
)
220 # we have a string of some kind
225 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csr_buf
)
226 except crypto
.Error
, e
:
227 self
.logger
.info("Unable to sign %s: Bad CSR" % (csr
))
228 raise exceptions
.Exception("Bad CSR: %s" % csr
)
230 else: # assume we got a bare csr req
234 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
236 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/pre/*')
239 certfile
= '%s/%s.cert' % (self
.cfg
.certroot
, requesting_host
)
240 self
.logger
.info("Signing for csr %s requested" % certfile
)
241 thiscert
= certs
.create_slave_certificate(csrreq
, self
.cakey
, self
.cacert
, self
.cfg
.cadir
)
243 destfo
= open(certfile
, 'w')
244 destfo
.write(crypto
.dump_certificate(crypto
.FILETYPE_PEM
, thiscert
))
249 self
.logger
.info("csr %s signed" % (certfile
))
251 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/post/*')
254 if csr_unlink_file
and os
.path
.exists(csr_unlink_file
):
255 os
.unlink(csr_unlink_file
)
259 # return a list of already signed certs
260 def get_signed_certs(self
, hostglobs
=None):
261 certglob
= "%s/*.cert" % (self
.cfg
.certroot
)
268 for hostglob
in globs
:
269 certglob
= "%s/%s.cert" % (self
.cfg
.certroot
, hostglob
)
270 certs
= certs
+ glob
.glob(certglob
)
274 # just want the hostname, so strip off path and ext
275 signed_certs
.append(os
.path
.basename(cert
).split(".cert", 1)[0])
279 def get_peer_certs(self
):
281 Returns a list of all certs under peerroot
283 myglob
= os
.path
.join(self
.cfg
.peerroot
, '*.%s' % self
.cfg
.cert_extension
)
284 return glob
.glob(myglob
)
286 # return a list of the cert hash string we use to identify systems
287 def get_cert_hashes(self
, hostglobs
=None):
288 certglob
= "%s/*.cert" % (self
.cfg
.certroot
)
295 for hostglob
in globs
:
296 certglob
= "%s/%s.cert" % (self
.cfg
.certroot
, hostglob
)
297 certfiles
= certfiles
+ glob
.glob(certglob
)
300 for certfile
in certfiles
:
301 cert
= certs
.retrieve_cert_from_file(certfile
)
302 cert_hashes
.append("%s-%s" % (cert
.get_subject().CN
, cert
.subject_name_hash()))
306 def _run_triggers(self
, ref
, globber
):
307 return utils
.run_triggers(ref
, globber
)
310 class CertmasterXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
):
311 def __init__(self
, addr
):
312 self
.allow_reuse_address
= True
313 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, addr
)
316 def serve(xmlrpcinstance
):
319 Code for starting the XMLRPC service.
323 config
= read_config(CERTMASTER_CONFIG
, CMConfig
)
324 listen_addr
= config
.listen_addr
325 listen_port
= config
.listen_port
326 if listen_port
== '':
327 listen_port
= CERTMASTER_LISTEN_PORT
328 server
= CertmasterXMLRPCServer((listen_addr
,listen_port
))
329 server
.logRequests
= 0 # don't print stuff to console
330 server
.register_instance(xmlrpcinstance
)
331 xmlrpcinstance
.logger
.info("certmaster started")
332 xmlrpcinstance
.audit_logger
.logger
.info("certmaster started")
333 server
.serve_forever()
335 def excepthook(exctype
, value
, tracebackobj
):
336 exctype_blurb
= "Exception occured: %s" % exctype
337 excvalue_blurb
= "Exception value: %s" % value
338 exctb_blurb
= "Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tracebackobj
)))
344 log
= logger
.Logger().logger
345 log
.info(exctype_blurb
)
346 log
.info(excvalue_blurb
)
347 log
.info(exctb_blurb
)
352 sys
.excepthook
= excepthook
353 cm
= CertMaster('/etc/certmaster/certmaster.conf')
355 if "daemon" in argv
or "--daemon" in argv
:
356 utils
.daemonize("/var/run/certmaster.pid")
361 # just let exceptions bubble up for now
365 if __name__
== "__main__":
366 #textdomain(I18N_DOMAIN)