add some basic logging output to certmaster
[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().logger
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.client_address
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 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
233 def __init__(self, args):
234 self.allow_reuse_address = True
235 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args)
236
237
238 def serve(xmlrpcinstance):
239
240 """
241 Code for starting the XMLRPC service.
242 """
243
244 server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
245 server.logRequests = 0 # don't print stuff to console
246 server.register_instance(xmlrpcinstance)
247 xmlrpcinstance.logger.info("certmaster started")
248 xmlrpcinstance.audit_logger.info("certmaster started")
249 server.serve_forever()
250
251
252 def main(argv):
253
254 cm = CertMaster('/etc/certmaster/certmaster.conf')
255
256 if "daemon" in argv or "--daemon" in argv:
257 utils.daemonize("/var/run/certmaster.pid")
258 else:
259 print "serving...\n"
260
261
262 # just let exceptions bubble up for now
263 serve(cm)
264
265
266 if __name__ == "__main__":
267 #textdomain(I18N_DOMAIN)
268 main(sys.argv)