diff options
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/SSLCA.py')
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/SSLCA.py | 111 |
1 files changed, 67 insertions, 44 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py index 0072dc62d..d207c45a2 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSLCA.py +++ b/src/lib/Bcfg2/Server/Plugins/SSLCA.py @@ -3,12 +3,15 @@ import Bcfg2.Options import lxml.etree import posixpath import tempfile -import pipes import os from subprocess import Popen, PIPE, STDOUT # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser +try: + from hashlib import md5 +except ImportError: + from md5 import md5 class SSLCA(Bcfg2.Server.Plugin.GroupSpool): """ @@ -22,6 +25,10 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): cert_specs = {} CAs = {} + def __init__(self, core, datastore): + Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore) + self.infoxml = dict() + def HandleEvent(self, event=None): """ Updates which files this plugin handles based upon filesystem events. @@ -37,19 +44,21 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): else: ident = self.handles[event.requestID][:-1] - fname = "".join([ident, '/', event.filename]) + fname = os.path.join(ident, event.filename) if event.filename.endswith('.xml'): if action in ['exists', 'created', 'changed']: if event.filename.endswith('key.xml'): - key_spec = dict(list(lxml.etree.parse(epath).find('Key').items())) + key_spec = dict(list(lxml.etree.parse(epath, + parser=Bcfg2.Server.XMLParser).find('Key').items())) self.key_specs[ident] = { 'bits': key_spec.get('bits', 2048), 'type': key_spec.get('type', 'rsa') } self.Entries['Path'][ident] = self.get_key elif event.filename.endswith('cert.xml'): - cert_spec = dict(list(lxml.etree.parse(epath).find('Cert').items())) + cert_spec = dict(list(lxml.etree.parse(epath, + parser=Bcfg2.Server.XMLParser).find('Cert').items())) ca = cert_spec.get('ca', 'default') self.cert_specs[ident] = { 'ca': ca, @@ -67,6 +76,10 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): cp.read(self.core.cfile) self.CAs[ca] = dict(cp.items('sslca_' + ca)) self.Entries['Path'][ident] = self.get_cert + elif event.filename.endswith("info.xml"): + self.infoxml[ident] = Bcfg2.Server.Plugin.InfoXML(epath, + noprio=True) + self.infoxml[ident].HandleEvent(event) if action == 'deleted': if ident in self.Entries['Path']: del self.Entries['Path'][ident] @@ -90,28 +103,27 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): either grabs a prexisting key hostfile, or triggers the generation of a new key if one doesn't exist. """ - # set path type and permissions, otherwise bcfg2 won't bind the file - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - # check if we already have a hostfile, or need to generate a new key # TODO: verify key fits the specs path = entry.get('name') - filename = "".join([path, '/', path.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + filename = os.path.join(path, "%s.H_%s" % (os.path.basename(path), + metadata.hostname)) if filename not in list(self.entries.keys()): key = self.build_key(filename, entry, metadata) open(self.data + filename, 'w').write(key) entry.text = key - self.entries[filename] = self.__child__("%s%s" % (self.data, - filename)) + self.entries[filename] = self.__child__(self.data + filename) self.entries[filename].HandleEvent() else: entry.text = self.entries[filename].data + entry.set("type", "file") + if path in self.infoxml: + Bcfg2.Server.Plugin.bind_info(entry, metadata, + infoxml=self.infoxml[path]) + else: + Bcfg2.Server.Plugin.bind_info(entry, metadata) + def build_key(self, filename, entry, metadata): """ generates a new key according the the specification @@ -130,56 +142,61 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): either grabs a prexisting cert hostfile, or triggers the generation of a new cert if one doesn't exist. """ - # set path type and permissions, otherwise bcfg2 won't bind the file - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - path = entry.get('name') - filename = "".join([path, '/', path.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + filename = os.path.join(path, "%s.H_%s" % (os.path.basename(path), + metadata.hostname)) # first - ensure we have a key to work with key = self.cert_specs[entry.get('name')].get('key') - key_filename = "".join([key, '/', key.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + key_filename = os.path.join(key, "%s.H_%s" % (os.path.basename(key), + metadata.hostname)) if key_filename not in self.entries: e = lxml.etree.Element('Path') - e.attrib['name'] = key + e.set('name', key) self.core.Bind(e, metadata) # check if we have a valid hostfile - if filename in list(self.entries.keys()) and self.verify_cert(filename, - key_filename, - entry): + if (filename in list(self.entries.keys()) and + self.verify_cert(filename, key_filename, entry)): entry.text = self.entries[filename].data else: cert = self.build_cert(key_filename, entry, metadata) open(self.data + filename, 'w').write(cert) - self.entries[filename] = self.__child__("%s%s" % (self.data, - filename)) + self.entries[filename] = self.__child__(self.data + filename) self.entries[filename].HandleEvent() entry.text = cert + entry.set("type", "file") + if path in self.infoxml: + Bcfg2.Server.Plugin.bind_info(entry, metadata, + infoxml=self.infoxml[path]) + else: + Bcfg2.Server.Plugin.bind_info(entry, metadata) + def verify_cert(self, filename, key_filename, entry): - if self.verify_cert_against_ca(filename, entry): - if self.verify_cert_against_key(filename, key_filename): - return True - return False + do_verify = self.CAs[self.cert_specs[entry.get('name')]['ca']].get('verify_certs', True) + if do_verify: + return (self.verify_cert_against_ca(filename, entry) and + self.verify_cert_against_key(filename, key_filename)) + return True def verify_cert_against_ca(self, filename, entry): """ check that a certificate validates against the ca cert, and that it has not expired. """ - chaincert = self.CAs[self.cert_specs[entry.get('name')]['ca']].get('chaincert') + chaincert = \ + self.CAs[self.cert_specs[entry.get('name')]['ca']].get('chaincert') cert = self.data + filename - res = Popen(["openssl", "verify", "-CAfile", chaincert, cert], + res = Popen(["openssl", "verify", "-untrusted", chaincert, "-purpose", + "sslserver", cert], stdout=PIPE, stderr=STDOUT).stdout.read() if res == cert + ": OK\n": + self.debug_log("SSLCA: %s verified successfully against CA" % + entry.get("name")) return True + self.logger.warning("SSLCA: %s failed verification against CA: %s" % + (entry.get("name"), res)) return False def verify_cert_against_key(self, filename, key_filename): @@ -188,14 +205,20 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): """ cert = self.data + filename key = self.data + key_filename - cmd = ("openssl x509 -noout -modulus -in %s | openssl md5" % - pipes.quote(cert)) - cert_md5 = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT).stdout.read() - cmd = ("openssl rsa -noout -modulus -in %s | openssl md5" % - pipes.quote(key)) - key_md5 = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT).stdout.read() + cert_md5 = \ + md5(Popen(["openssl", "x509", "-noout", "-modulus", "-in", cert], + stdout=PIPE, + stderr=STDOUT).stdout.read().strip()).hexdigest() + key_md5 = \ + md5(Popen(["openssl", "rsa", "-noout", "-modulus", "-in", key], + stdout=PIPE, + stderr=STDOUT).stdout.read().strip()).hexdigest() if cert_md5 == key_md5: + self.debug_log("SSLCA: %s verified successfully against key %s" % + (filename, key_filename)) return True + self.logger.warning("SSLCA: %s failed verification against key %s" % + (filename, key_filename)) return False def build_cert(self, key_filename, entry, metadata): |