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():
67 s_cadir
= a_ca
['cadir']
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 self
.cakey
[s_caname
] = certs
.retrieve_key_from_file(s_ca_key_file
)
92 self
.cacert
[s_caname
] = 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
='', with_triggers
=True):
122 takes csr as a string
123 returns True, caller_cert, ca_cert
124 returns False, '', ''
128 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csrbuf
)
129 except crypto
.Error
, e
:
130 #XXX need to raise a fault here and document it - but false is just as good
133 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
136 self
._run
_triggers
(requesting_host
, '/var/lib/certmaster/triggers/request/pre/*')
138 self
.logger
.info("%s requested signing of cert %s" % (requesting_host
,csrreq
.get_subject().CN
))
139 # get rid of dodgy characters in the filename we're about to make
141 certfile
= '%s/%s.cert' % (self
.cfg
.ca
[ca
]['certroot'], requesting_host
)
142 csrfile
= '%s/%s.csr' % (self
.cfg
.ca
[ca
]['csrroot'], requesting_host
)
144 # check for old csr on disk
145 # if we have it - compare the two - if they are not the same - raise a fault
146 self
.logger
.debug("csrfile: %s certfile: %s" % (csrfile
, certfile
))
148 if os
.path
.exists(csrfile
):
149 oldfo
= open(csrfile
)
150 oldcsrbuf
= oldfo
.read()
151 oldsha
= hashlib
.new('sha1')
152 oldsha
.update(oldcsrbuf
)
153 olddig
= oldsha
.hexdigest()
154 newsha
= hashlib
.new('sha1')
155 newsha
.update(csrbuf
)
156 newdig
= newsha
.hexdigest()
157 if not newdig
== olddig
:
158 self
.logger
.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host
))
159 # XXX raise a proper fault
164 # if we have it, then return True, etc, etc
165 if os
.path
.exists(certfile
):
166 slavecert
= certs
.retrieve_cert_from_file(certfile
)
167 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, slavecert
)
168 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, self
.cacert
[ca
])
170 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/request/post/*')
171 return True, cert_buf
, cacert_buf
173 # if we don't have a cert then:
174 # if we're autosign then sign it, write out the cert and return True, etc, etc
175 # else write out the csr
177 if self
.cfg
.ca
[ca
]['autosign']:
178 cert_fn
= self
.sign_this_csr(csrreq
,ca
=ca
)
179 cert
= certs
.retrieve_cert_from_file(cert_fn
)
180 cert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
181 cacert_buf
= crypto
.dump_certificate(crypto
.FILETYPE_PEM
, self
.cacert
[ca
])
182 self
.logger
.info("cert for %s for ca %s was autosigned" % (requesting_host
,ca
))
184 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
185 return True, cert_buf
, cacert_buf
188 # write the csr out to a file to be dealt with by the admin
189 destfo
= open(csrfile
, 'w')
190 destfo
.write(crypto
.dump_certificate_request(crypto
.FILETYPE_PEM
, csrreq
))
193 self
.logger
.info("cert for %s for CA %s created and ready to be signed" % (requesting_host
, ca
))
195 self
._run
_triggers
(None,'/var/lib/certmaster/triggers/request/post/*')
200 def get_csrs_waiting(self
, ca
=''):
202 csrglob
= '%s/*.csr' % self
.cfg
.ca
[ca
]['csrroot']
203 csr_list
= glob
.glob(csrglob
)
205 hn
= os
.path
.basename(f
)
210 def remove_this_cert(self
, hn
, with_triggers
=True, ca
=''):
211 """ removes cert for hostname using unlink """
213 csrglob
= '%s/%s.csr' % (cm
.cfg
.ca
[ca
]['csrroot'], hn
)
214 csrs
= glob
.glob(csrglob
)
215 certglob
= '%s/%s.cert' % (cm
.cfg
.ca
[ca
]['certroot'], hn
)
216 certs
= glob
.glob(certglob
)
217 if not csrs
and not certs
:
218 # FIXME: should be an exception?
219 print 'No match for %s to clean up' % hn
222 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/pre/*')
223 for fn
in csrs
+ certs
:
224 print 'Cleaning out %s for host matching %s' % (fn
, hn
)
225 self
.logger
.info('Cleaning out %s for host matching %s' % (fn
, hn
))
228 self
._run
_triggers
(hn
,'/var/lib/certmaster/triggers/remove/post/*')
230 def sign_this_csr(self
, csr
, with_triggers
=True, ca
=''):
231 """returns the path to the signed cert file"""
232 csr_unlink_file
= None
234 if type(csr
) is type(''):
235 if csr
.startswith('/') and os
.path
.exists(csr
): # we have a full path to the file
237 csr_buf
= csrfo
.read()
238 csr_unlink_file
= csr
240 elif os
.path
.exists('%s/%s' % (self
.cfg
.ca
[ca
]['csrroot'], csr
)): # we have a partial path?
241 csrfo
= open('%s/%s' % (self
.cfg
.ca
[ca
]['csrroot'], csr
))
242 csr_buf
= csrfo
.read()
243 csr_unlink_file
= '%s/%s' % (self
.cfg
.ca
[ca
]['csrroot'], csr
)
245 # we have a string of some kind
250 csrreq
= crypto
.load_certificate_request(crypto
.FILETYPE_PEM
, csr_buf
)
251 except crypto
.Error
, e
:
252 self
.logger
.info("Unable to sign %s: Bad CSR" % (csr
))
253 raise exceptions
.Exception("Bad CSR: %s" % csr
)
255 else: # assume we got a bare csr req
259 requesting_host
= self
._sanitize
_cn
(csrreq
.get_subject().CN
)
261 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/pre/*')
264 certfile
= '%s/%s.cert' % (self
.cfg
.ca
[ca
]['certroot'], requesting_host
)
265 self
.logger
.info("Signing for csr %s requested" % certfile
)
266 thiscert
= certs
.create_slave_certificate(csrreq
, self
.cakey
[ca
], self
.cacert
[ca
], self
.cfg
.ca
[ca
]['cadir'])
268 destfo
= open(certfile
, 'w')
269 destfo
.write(crypto
.dump_certificate(crypto
.FILETYPE_PEM
, thiscert
))
274 self
.logger
.info("csr %s signed" % (certfile
))
276 self
._run
_triggers
(requesting_host
,'/var/lib/certmaster/triggers/sign/post/*')
279 if csr_unlink_file
and os
.path
.exists(csr_unlink_file
):
280 os
.unlink(csr_unlink_file
)
284 # return a list of already signed certs
285 def get_signed_certs(self
, hostglobs
=None, ca
=''):
286 certglob
= "%s/*.cert" % (self
.cfg
.ca
[ca
]['certroot'])
293 for hostglob
in globs
:
294 certglob
= "%s/%s.cert" % (self
.cfg
.ca
[ca
]['certroot'], hostglob
)
295 certs
= certs
+ glob
.glob(certglob
)
299 # just want the hostname, so strip off path and ext
300 signed_certs
.append(os
.path
.basename(cert
).split(".cert", 1)[0])
304 def get_peer_certs(self
):
306 Returns a list of all certs under peerroot
308 myglob
= os
.path
.join(self
.cfg
.peerroot
, '*.%s' % self
.cfg
.cert_extension
)
309 return glob
.glob(myglob
)
311 # return a list of the cert hash string we use to identify systems
312 def get_cert_hashes(self
, hostglobs
=None,ca
=''):
313 certglob
= "%s/*.cert" % (self
.cfg
.ca
[ca
]['certroot'])
320 for hostglob
in globs
:
321 certglob
= "%s/%s.cert" % (self
.cfg
.ca
[ca
]['certroot'], hostglob
)
322 certfiles
= certfiles
+ glob
.glob(certglob
)
325 for certfile
in certfiles
:
326 cert
= certs
.retrieve_cert_from_file(certfile
)
327 cert_hashes
.append("%s-%s" % (cert
.get_subject().CN
, cert
.subject_name_hash()))
331 def _run_triggers(self
, ref
, globber
):
332 return utils
.run_triggers(ref
, globber
)
335 class CertmasterXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
):
336 def __init__(self
, addr
):
337 self
.allow_reuse_address
= True
338 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, addr
)
341 def serve(xmlrpcinstance
):
344 Code for starting the XMLRPC service.
348 config
= read_config(CERTMASTER_CONFIG
, CMConfig
)
349 listen_addr
= config
.listen_addr
350 listen_port
= config
.listen_port
351 if listen_port
== '':
352 listen_port
= CERTMASTER_LISTEN_PORT
353 server
= CertmasterXMLRPCServer((listen_addr
,listen_port
))
354 server
.logRequests
= 0 # don't print stuff to console
355 server
.register_instance(xmlrpcinstance
)
356 xmlrpcinstance
.logger
.info("certmaster started")
357 xmlrpcinstance
.audit_logger
.logger
.info("certmaster started")
358 server
.serve_forever()
360 def excepthook(exctype
, value
, tracebackobj
):
361 exctype_blurb
= "Exception occured: %s" % exctype
362 excvalue_blurb
= "Exception value: %s" % value
363 exctb_blurb
= "Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tracebackobj
)))
369 log
= logger
.Logger().logger
370 log
.info(exctype_blurb
)
371 log
.info(excvalue_blurb
)
372 log
.info(exctb_blurb
)
377 sys
.excepthook
= excepthook
378 cm
= CertMaster('/etc/certmaster/certmaster.conf')
380 if "--version" in sys
.argv
or "-v" in sys
.argv
:
381 print >> sys
.stderr
, file("/etc/certmaster/version").read().strip()
384 if "daemon" in argv
or "--daemon" in argv
:
385 utils
.daemonize("/var/run/certmaster.pid")
389 # just let exceptions bubble up for now
393 if __name__
== "__main__":
394 #textdomain(I18N_DOMAIN)