certmaster logging cleanups
[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 def _dispatch(self, method, params):
77 if method == 'trait_names' or method == '_getAttributeNames':
78 return self.handlers.keys()
79
80 # ip = self._this_request
81 # print ip
82
83 # self.audit_logger.log_call(ip, method, params)
84
85 if method in self.handlers.keys():
86 return self.handlers[method](*params)
87 else:
88 self.logger.info("Unhandled method call for method: %s " % method)
89 raise codes.InvalidMethodException
90
91 def _sanitize_cn(self, commonname):
92 commonname = commonname.replace('/', '')
93 commonname = commonname.replace('\\', '')
94 return commonname
95
96 def wait_for_cert(self, csrbuf):
97 """
98 takes csr as a string
99 returns True, caller_cert, ca_cert
100 returns False, '', ''
101 """
102
103 try:
104 csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
105 except crypto.Error, e:
106 #XXX need to raise a fault here and document it - but false is just as good
107 return False, '', ''
108
109 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
110
111
112 self.logger.info("%s requested signing of cert %s" % (requesting_host,csrreq.get_subject().CN))
113 # get rid of dodgy characters in the filename we're about to make
114
115 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
116 csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
117
118 # check for old csr on disk
119 # if we have it - compare the two - if they are not the same - raise a fault
120 if os.path.exists(csrfile):
121 oldfo = open(csrfile)
122 oldcsrbuf = oldfo.read()
123 oldsha = sha.new()
124 oldsha.update(oldcsrbuf)
125 olddig = oldsha.hexdigest()
126 newsha = sha.new()
127 newsha.update(csrbuf)
128 newdig = newsha.hexdigest()
129 if not newdig == olddig:
130 self.logger.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host))
131 # XXX raise a proper fault
132 return False, '', ''
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 requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
219
220 certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
221 thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
222 destfo = open(certfile, 'w')
223 destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
224 destfo.close()
225 del destfo
226 if csr_unlink_file and os.path.exists(csr_unlink_file):
227 os.unlink(csr_unlink_file)
228
229 return certfile
230
231
232 # not used yet, trying to figure out a way to get the client ip addr to log -akl
233 class CertmasterXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
234 def do_POST(self):
235 self.server._this_request = (self.request, self.client_address)
236 try:
237 SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
238 except socket.timeout:
239 pass
240 except (socket.error, OpenSSL.SSL.SysCallError), e:
241 print "Error (%s): socket error - '%s'" % (self.client_address, e)
242
243
244 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
245 def __init__(self, addr):
246 self.allow_reuse_address = True
247 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr)
248
249
250 def serve(xmlrpcinstance):
251
252 """
253 Code for starting the XMLRPC service.
254 """
255
256
257 server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
258 server.logRequests = 0 # don't print stuff to console
259 server.register_instance(xmlrpcinstance)
260 xmlrpcinstance.logger.info("certmaster started")
261 xmlrpcinstance.audit_logger.logger.info("certmaster started")
262 server.serve_forever()
263
264
265 def main(argv):
266
267 cm = CertMaster('/etc/certmaster/certmaster.conf')
268
269 if "daemon" in argv or "--daemon" in argv:
270 utils.daemonize("/var/run/certmaster.pid")
271 else:
272 print "serving...\n"
273
274
275 # just let exceptions bubble up for now
276 serve(cm)
277
278
279 if __name__ == "__main__":
280 #textdomain(I18N_DOMAIN)
281 main(sys.argv)