summaryrefslogtreecommitdiffstats
path: root/func/certmaster.py
diff options
context:
space:
mode:
Diffstat (limited to 'func/certmaster.py')
-rwxr-xr-xfunc/certmaster.py247
1 files changed, 247 insertions, 0 deletions
diff --git a/func/certmaster.py b/func/certmaster.py
new file mode 100755
index 0000000..fe5dcbc
--- /dev/null
+++ b/func/certmaster.py
@@ -0,0 +1,247 @@
+# FIXME: more intelligent fault raises
+
+"""
+cert master listener
+
+Copyright 2007, Red Hat, Inc
+see AUTHORS
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+# standard modules
+import SimpleXMLRPCServer
+import sys
+import os
+import os.path
+from OpenSSL import crypto
+import sha
+import glob
+import socket
+import exceptions
+
+#from func.server import codes
+import certs
+import codes
+import utils
+from config import read_config
+from commonconfig import CMConfig
+
+CERTMASTER_LISTEN_PORT = 51235
+CERTMASTER_CONFIG = "/etc/func/certmaster.conf"
+
+class CertMaster(object):
+ def __init__(self, conf_file=CERTMASTER_CONFIG):
+ self.cfg = read_config(conf_file, CMConfig)
+
+ usename = utils.get_hostname()
+
+ mycn = '%s-CA-KEY' % usename
+ self.ca_key_file = '%s/funcmaster.key' % self.cfg.cadir
+ self.ca_cert_file = '%s/funcmaster.crt' % self.cfg.cadir
+ try:
+ if not os.path.exists(self.cfg.cadir):
+ os.makedirs(self.cfg.cadir)
+ if not os.path.exists(self.ca_key_file) and not os.path.exists(self.ca_cert_file):
+ certs.create_ca(CN=mycn, ca_key_file=self.ca_key_file, ca_cert_file=self.ca_cert_file)
+ except (IOError, OSError), e:
+ print 'Cannot make certmaster certificate authority keys/certs, aborting: %s' % e
+ sys.exit(1)
+
+
+ # open up the cakey and cacert so we have them available
+ self.cakey = certs.retrieve_key_from_file(self.ca_key_file)
+ self.cacert = certs.retrieve_cert_from_file(self.ca_cert_file)
+
+ for dirpath in [self.cfg.cadir, self.cfg.certroot, self.cfg.csrroot]:
+ if not os.path.exists(dirpath):
+ os.makedirs(dirpath)
+
+ # setup handlers
+ self.handlers = {
+ 'wait_for_cert': self.wait_for_cert,
+ }
+
+ def _dispatch(self, method, params):
+ if method == 'trait_names' or method == '_getAttributeNames':
+ return self.handlers.keys()
+
+ if method in self.handlers.keys():
+ return self.handlers[method](*params)
+ else:
+ raise codes.InvalidMethodException
+
+ def _sanitize_cn(self, commonname):
+ commonname = commonname.replace('/', '')
+ commonname = commonname.replace('\\', '')
+ return commonname
+
+ def wait_for_cert(self, csrbuf):
+ """
+ takes csr as a string
+ returns True, caller_cert, ca_cert
+ returns False, '', ''
+ """
+
+ try:
+ csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csrbuf)
+ except crypto.Error, e:
+ #XXX need to raise a fault here and document it - but false is just as good
+ return False, '', ''
+
+ requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
+
+ # get rid of dodgy characters in the filename we're about to make
+
+ certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
+ csrfile = '%s/%s.csr' % (self.cfg.csrroot, requesting_host)
+
+ # check for old csr on disk
+ # if we have it - compare the two - if they are not the same - raise a fault
+ if os.path.exists(csrfile):
+ oldfo = open(csrfile)
+ oldcsrbuf = oldfo.read()
+ oldsha = sha.new()
+ oldsha.update(oldcsrbuf)
+ olddig = oldsha.hexdigest()
+ newsha = sha.new()
+ newsha.update(csrbuf)
+ newdig = newsha.hexdigest()
+ if not newdig == olddig:
+ # XXX raise a proper fault
+ return False, '', ''
+
+ # look for a cert:
+ # if we have it, then return True, etc, etc
+ if os.path.exists(certfile):
+ slavecert = certs.retrieve_cert_from_file(certfile)
+ cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
+ cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
+ return True, cert_buf, cacert_buf
+
+ # if we don't have a cert then:
+ # if we're autosign then sign it, write out the cert and return True, etc, etc
+ # else write out the csr
+
+ if self.cfg.autosign:
+ cert_fn = self.sign_this_csr(csrreq)
+ cert = certs.retrieve_cert_from_file(cert_fn)
+ cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+ cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
+ return True, cert_buf, cacert_buf
+
+ else:
+ # write the csr out to a file to be dealt with by the admin
+ destfo = open(csrfile, 'w')
+ destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq))
+ destfo.close()
+ del destfo
+ return False, '', ''
+
+ return False, '', ''
+
+ def get_csrs_waiting(self):
+ hosts = []
+ csrglob = '%s/*.csr' % self.cfg.csrroot
+ csr_list = glob.glob(csrglob)
+ for f in csr_list:
+ hn = os.path.basename(f)
+ hn = hn[:-4]
+ hosts.append(hn)
+ return hosts
+
+ def remove_this_cert(self, hn):
+ """ removes cert for hostname using unlink """
+ cm = self
+ csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
+ csrs = glob.glob(csrglob)
+ certglob = '%s/%s.cert' % (cm.cfg.certroot, hn)
+ certs = glob.glob(certglob)
+ if not csrs and not certs:
+ # FIXME: should be an exception?
+ print 'No match for %s to clean up' % hn
+ return
+ for fn in csrs + certs:
+ print 'Cleaning out %s for host matching %s' % (fn, hn)
+ os.unlink(fn)
+
+ def sign_this_csr(self, csr):
+ """returns the path to the signed cert file"""
+ csr_unlink_file = None
+
+ if type(csr) is type(''):
+ if csr.startswith('/') and os.path.exists(csr): # we have a full path to the file
+ csrfo = open(csr)
+ csr_buf = csrfo.read()
+ csr_unlink_file = csr
+
+ elif os.path.exists('%s/%s' % (self.cfg.csrroot, csr)): # we have a partial path?
+ csrfo = open('%s/%s' % (self.cfg.csrroot, csr))
+ csr_buf = csrfo.read()
+ csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr)
+
+ # we have a string of some kind
+ else:
+ csr_buf = csr
+
+ try:
+ csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_buf)
+ except crypto.Error, e:
+ raise exceptions.Exception("Bad CSR: %s" % csr)
+
+ else: # assume we got a bare csr req
+ csrreq = csr
+ requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
+
+ certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
+ thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
+ destfo = open(certfile, 'w')
+ destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert))
+ destfo.close()
+ del destfo
+ if csr_unlink_file and os.path.exists(csr_unlink_file):
+ os.unlink(csr_unlink_file)
+
+ return certfile
+
+
+class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
+ def __init__(self, args):
+ self.allow_reuse_address = True
+ SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args)
+
+
+def serve(xmlrpcinstance):
+
+ """
+ Code for starting the XMLRPC service.
+ """
+
+ server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
+ server.logRequests = 0 # don't print stuff to console
+ server.register_instance(xmlrpcinstance)
+ server.serve_forever()
+
+
+def main(argv):
+
+ cm = CertMaster('/etc/func/certmaster.conf')
+
+ if "daemon" in argv or "--daemon" in argv:
+ utils.daemonize("/var/run/certmaster.pid")
+ else:
+ print "serving...\n"
+
+
+ # just let exceptions bubble up for now
+ serve(cm)
+
+
+if __name__ == "__main__":
+ #textdomain(I18N_DOMAIN)
+ main(sys.argv)