more logging info. log info for sign_this_csr()
[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):
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
109 self.logger.info("%s requested signing of cert %s" % (requesting_host,csrreq.get_subject().CN))
110 # get rid of dodgy characters in the filename we're about to make
111
112 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
113 csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
114
115 # check for old csr on disk
116 # if we have it - compare the two - if they are not the same - raise a fault
117 self.logger.debug("csrfile: %s certfile: %s" % (csrfile, certfile))
118
119 if os.path.exists(csrfile):
120 oldfo = open(csrfile)
121 oldcsrbuf = oldfo.read()
122 oldsha = sha.new()
123 oldsha.update(oldcsrbuf)
124 olddig = oldsha.hexdigest()
125 newsha = sha.new()
126 newsha.update(csrbuf)
127 newdig = newsha.hexdigest()
128 if not newdig == olddig:
129 self.logger.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host))
130 # XXX raise a proper fault
131 return False, '', ''
132
133
134 # look for a cert:
135 # if we have it, then return True, etc, etc
136 if os.path.exists(certfile):
137 slavecert = certs.retrieve_cert_from_file(certfile)
138 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
139 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
140 return True, cert_buf, cacert_buf
141
142 # if we don't have a cert then:
143 # if we're autosign then sign it, write out the cert and return True, etc, etc
144 # else write out the csr
145
146 if self.cfg.autosign:
147 cert_fn = self.sign_this_csr(csrreq)
148 cert = certs.retrieve_cert_from_file(cert_fn)
149 cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
150 cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
151 self.logger.info("cert for %s was autosigned" % (requesting_host))
152 return True, cert_buf, cacert_buf
153
154 else:
155 # write the csr out to a file to be dealt with by the admin
156 destfo = open(csrfile, 'w')
157 destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
158 destfo.close()
159 del destfo
160 self.logger.info("cert for %s created and ready to be signed" % (requesting_host))
161 return False, '', ''
162
163 return False, '', ''
164
165 def get_csrs_waiting(self):
166 hosts = []
167 csrglob = '%s/*.csr' % self.cfg.csrroot
168 csr_list = glob.glob(csrglob)
169 for f in csr_list:
170 hn = os.path.basename(f)
171 hn = hn[:-4]
172 hosts.append(hn)
173 return hosts
174
175 def remove_this_cert(self, hn):
176 """ removes cert for hostname using unlink """
177 cm = self
178 csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
179 csrs = glob.glob(csrglob)
180 certglob = '%s/%s.cert' % (cm.cfg.certroot, hn)
181 certs = glob.glob(certglob)
182 if not csrs and not certs:
183 # FIXME: should be an exception?
184 print 'No match for %s to clean up' % hn
185 return
186 for fn in csrs + certs:
187 print 'Cleaning out %s for host matching %s' % (fn, hn)
188 self.logger.info('Cleaning out %s for host matching %s' % (fn, hn))
189 os.unlink(fn)
190
191 def sign_this_csr(self, csr):
192 """returns the path to the signed cert file"""
193 csr_unlink_file = None
194
195 if type(csr) is type(''):
196 if csr.startswith('/') and os.path.exists(csr): # we have a full path to the file
197 csrfo = open(csr)
198 csr_buf = csrfo.read()
199 csr_unlink_file = csr
200
201 elif os.path.exists('%s/%s' % (self.cfg.csrroot, csr)): # we have a partial path?
202 csrfo = open('%s/%s' % (self.cfg.csrroot, csr))
203 csr_buf = csrfo.read()
204 csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr)
205
206 # we have a string of some kind
207 else:
208 csr_buf = csr
209
210 try:
211 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_buf)
212 except crypto.Error, e:
213 self.logger.info("Unable to sign %s: Bad CSR" % (csr))
214 raise exceptions.Exception("Bad CSR: %s" % csr)
215
216 else: # assume we got a bare csr req
217 csrreq = csr
218
219
220 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
221 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
222 self.logger.info("Signing for csr %s requested" % certfile)
223 thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
224
225 destfo = open(certfile, 'w')
226 destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
227 destfo.close()
228 del destfo
229
230
231 self.logger.info("csr %s signed" % (certfile))
232 if csr_unlink_file and os.path.exists(csr_unlink_file):
233 os.unlink(csr_unlink_file)
234
235 return certfile
236
237
238
239
240 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
241 def __init__(self, addr):
242 self.allow_reuse_address = True
243 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr)
244
245
246 def serve(xmlrpcinstance):
247
248 """
249 Code for starting the XMLRPC service.
250 """
251
252
253 server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
254 server.logRequests = 0 # don't print stuff to console
255 server.register_instance(xmlrpcinstance)
256 xmlrpcinstance.logger.info("certmaster started")
257 xmlrpcinstance.audit_logger.logger.info("certmaster started")
258 server.serve_forever()
259
260
261 def main(argv):
262
263 cm = CertMaster('/etc/certmaster/certmaster.conf')
264
265 if "daemon" in argv or "--daemon" in argv:
266 utils.daemonize("/var/run/certmaster.pid")
267 else:
268 print "serving...\n"
269
270
271 # just let exceptions bubble up for now
272 serve(cm)
273
274
275 if __name__ == "__main__":
276 #textdomain(I18N_DOMAIN)
277 main(sys.argv)