From 196b67a03a5ff8b550f2dfe8efc95893c408b483 Mon Sep 17 00:00:00 2001 From: Graham Hagger Date: Wed, 27 Oct 2010 17:52:19 -0400 Subject: starting to establish the way sslca will truly work --- src/lib/Server/Plugins/SSLCA.py | 623 ++++++++++++++++++++++++++++++---------- 1 file changed, 471 insertions(+), 152 deletions(-) (limited to 'src/lib/Server/Plugins/SSLCA.py') diff --git a/src/lib/Server/Plugins/SSLCA.py b/src/lib/Server/Plugins/SSLCA.py index 734049417..f18c18944 100644 --- a/src/lib/Server/Plugins/SSLCA.py +++ b/src/lib/Server/Plugins/SSLCA.py @@ -1,166 +1,485 @@ import Bcfg2.Server.Plugin -import Bcfg2.Options -import os -from ConfigParser import ConfigParser, NoSectionError, NoOptionError -from M2Crypto import RSA, EVP, X509, m2 - -class SSLCA(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Generator, - Bcfg2.Server.Plugin.DirectoryBacked): - """ - The sslca generator manages ssl certificates - and keys - """ +from subprocess import Popen, PIPE +import lxml.etree +import posixpath +import logging +import pdb - name = 'SSLbase' - __version__ = '0.00000000001' - __author__ = 'ghagger@wgen.net' +class SSLCA(Bcfg2.Server.Plugin.GroupSpool): + #Bcfg2.Server.Plugin.Plugin, + #Bcfg2.Server.Plugin.Generator, + #Bcfg2.Server.Plugin.DirectoryBacked): - hostkey = 'localhost.key' - hostcert = 'localhost.crt' + """ + The SSLCA generator handles the creation and + management of ssl certificates and their keys. + """ + name = 'SSLCA' + __version__ = '$Id:$' + __author__ = 'g.hagger@gmail.com' + __child__ = Bcfg2.Server.Plugin.FileBacked def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Generator.__init__(self) - try: - Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, - self.core.fam) - except OSError, ioerr: - self.logger.error("Failed to load SSLbase repository from %s" \ - % (self.data)) - self.logger.error(ioerr) - raise Bcfg2.Server.Plugin.PluginInitError - self.Entries = {'Path': - {'/etc/pki/tls/private/localhost.key': self.get_key, - '/etc/pki/tls/certs/localhost.crt': self.get_cert}} - # grab essential sslca configuration from bcfg2.conf - cp = ConfigParser() - cp.read(Bcfg2.Options.CFILE.value) - try: - ca_cert_filename = cp.get('sslca', 'ca_cert') - ca_key_filename = cp.get('sslca', 'ca_key') - self.ca_key_passphrase = cp.get('sslca', 'ca_key_passphrase') - self.cert_subject = cp.get('sslca', 'cert_subject') - self.cert_days = cp.get('sslca', 'cert_days') - self.pkey_bits = cp.get('sslca', 'pkey_bits') - except: - raise NoOptionError - self.ca_cert = X509.load_cert(ca_cert_filename) - self.ca_key = EVP.load_key(ca_key_filename, lambda x: self.ca_key_passphrase) - self._newkey = False + Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore) +# Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) +# Bcfg2.Server.Plugin.Generator.__init__(self) +# try: +# Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, +# self.core.fam) +# except OSError, ioerr: +# self.logger.error("Failed to load SSHbase repository from %s" \ +# % (self.data)) +# self.logger.error(ioerr) +# raise Bcfg2.Server.Plugin.PluginInitError +# + def HandleEvent(self, event=None): + action = event.code2str() + if event.filename[0] == '/': + return + epath = "".join([self.data, self.handles[event.requestID], + event.filename]) + if posixpath.isdir(epath): + ident = self.handles[event.requestID] + event.filename + else: + ident = self.handles[event.requestID][:-1] + + self.logger.error('ACTION: %s, IDENT %s, FILENAME %s' % (action, ident, event.filename)) + + if action in ['exists', 'created']: + if posixpath.isdir(epath): + self.AddDirectoryMonitor(epath[len(self.data):]) + if ident not in self.entries and posixpath.isfile(epath): + if event.filename.endswith('key.xml'): + self.Entries['Path'][ident] = self.get_key + elif event.filename.endswith('cert.xml'): + pass +# self.Entries['Path'][ident] = self.get_cert + else: + fname = "".join([ident, '/', event.filename]) + self.entries[fname] = self.__child__(epath) + self.entries[fname].HandleEvent(event) + if action == 'changed': + self.entries[ident].HandleEvent(event) + elif action == 'deleted': + fbase = self.handles[event.requestID] + event.filename + if fbase in self.entries: + # a directory was deleted + del self.entries[fbase] + del self.Entries['Path'][fbase] + else: + self.entries[ident].HandleEvent(event) def get_key(self, entry, metadata): - filename = self.hostkey+".H_%s" % metadata.hostname - if filename in self.entries.keys(): - entry.text = self.entries[filename].data - self.pkey = EVP.load_key_string(entry.text) + path = entry.get('name') + permdata = {'owner':'root', + 'group':'root', + 'type':'file', + 'perms':'644'} + [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] + filename = "".join([path, '/', path.rsplit('/', 1)[1], '.H_', metadata.hostname]) + if filename not in self.entries.keys(): + key = self.build_key(filename, metadata) + open(self.data + filename, 'w').write(key) + entry.text = key else: - (self.pkey, entry.text) = self.build_key(filename) - keyfile = open(self.data + '/' +filename, 'w') - keyfile.write(entry.text) - keyfile.close() - self._newkey = True - - def build_key(self, filename): - """Generate new private key for client.""" - rsa_key = RSA.gen_key(int(self.pkey_bits), m2.RSA_F4) - pkey = EVP.PKey() - pkey.assign_rsa(rsa_key) - keyfile = open(self.data + '/' +filename, 'w') - keyfile.write(pkey.as_pem(cipher=None)) - keyfile.close() - self._newkey = True - return pkey, pkey.as_pem(cipher=None) + entry.text = self.entries[filename].data + + def build_key(self, filename, metadata): + # TODO read params + type = 'rsa' + bits = 2048 + if type == 'rsa': + cmd = "openssl genrsa %s " % bits + elif type == 'dsa': + cmd = "openssl dsaparam -noout -genkey %s" % bits + key = Popen(cmd, shell=True, stdout=PIPE).stdout.read() + return key def get_cert(self, entry, metadata): - filename = self.hostcert + ".H_%s" % metadata.hostname - # load prexisting cert, if any - if filename in self.entries.keys() and self._newkey == False: - cert = X509.load_cert_string(self.entries[filename].data) - # check cert subjectAltNames match current aliases - cert_aliases = cert.get_ext('subjectAltName') - if cert_aliases: - if metadata.aliases != [alias.lstrip('DNS:') for alias in cert_aliases.get_value().split(', ')]: - entry.text = self.build_cert(filename, metadata) - return - entry.text = cert.as_text()+cert.as_string() + path = entry.get('name') + permdata = {'owner':'root', + 'group':'root', + 'type':'file', + 'perms':'644'} + [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] + filename = "".join([path, '/', path.rsplit('/', 1)[1], '.H_', metadata.hostname]) + if filename in self.entries.keys() and self.verify_cert(filename) : + entry.text = self.entries[filename].data else: - entry.text = self.build_cert(filename, metadata) - - def get_serial(self): - serialpath = self.data + '/serial' - serial = 0 - if os.path.isfile(serialpath): - serialfile = open(serialpath, 'r') - serial = int(serialfile.read()) - serialfile.close() - serialfile = open(serialpath, 'w') - serial += 1 - serialfile.write(str(serial)) - serialfile.close() - return serial - - def build_cert(self, filename, metadata): - req = self.make_request(self.pkey, metadata) - serial = self.get_serial() - cert = self.make_cert(req, serial, metadata.aliases) - cert_out = cert.as_text()+cert.as_pem() - certfile = open(self.data + '/' +filename, 'w') - certfile.write(cert_out) - certfile.close() - cert_store = self.data + '/certstore' - if not os.path.isdir(cert_store): - os.mkdir(cert_store) - storefile = open(cert_store + '/' + str(serial) + '.pem', 'w') - storefile.write(cert_out) - storefile.close() - return cert_out - - def make_request(self, key, metadata): - req = X509.Request() - req.set_version(2) - req.set_pubkey(key) - name = X509.X509_Name() - parts = [a.split('=') for a in self.cert_subject.split(',')] - [setattr(name, k, v) for k,v in parts] - name.CN = metadata.hostname - req.set_subject_name(name) - req.sign(key, 'sha1') - return req - - def make_cert(self, req, serial, aliases): - pkey = req.get_pubkey() - if not req.verify(pkey): - raise ValueError, 'Error verifying request' - sub = req.get_subject() - cert = X509.X509() - cert.set_serial_number(serial) - cert.set_version(2) - cert.set_subject(sub) - cert.set_issuer(self.ca_cert) - cert.set_pubkey(pkey) - notBefore = m2.x509_get_not_before(cert.x509) - notAfter = m2.x509_get_not_after(cert.x509) - m2.x509_gmtime_adj(notBefore, 0) - m2.x509_gmtime_adj(notAfter, 60*60*24*long(self.cert_days)) - exts = [ - ('basicConstraints','CA:FALSE'), - ('subjectKeyIdentifier','hash'), - ('authorityKeyIdentifier','keyid,issuer:always'), - ('nsCertType','SSL Server'), - ] - if aliases: - exts.append(('subjectAltName', ','.join(['DNS:'+alias for alias in aliases]))) - for ext in exts: - cert.add_ext(X509.new_extension(ext[0],ext[1])) - cert.sign(self.ca_key, 'sha1') - return cert + cert = self.build_cert(filename, metadata) + open(self.data + filename, 'w').write(cert) + entry.text = cert + + def verify_cert(self): + # TODO + # check cert matches key + # check expiry + pass + + def build_req(self): + pass + + def build_cert(self): + pass + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#class SSLCAFile: +# +# def __init__(self, datastore, name, specific, encoding): +# self.data = datastore +# self.name = name +# self.specific = specific +# self.encoding = encoding +# if name.endswith('.xml'): +# self.xml = lxml.etree.parse(name) +# +# def handle_event(self, event=None): +# """Handle all fs events for this file.""" +# if event and event.code2str() == 'deleted': +# return +# +# def bind_entry(self, entry, metadata): +# pdb.set_trace() +# +# +#class SSLCAKeyFile(SSLCAFile): +# +# def __init__(self, datastore, name, specific, encoding): +# SSLCAFile.__init__(self, datastore, name, specific, encoding) +# key_attrs = self.xml.find('Key') +# self.bits = key_attrs.get('bits') +# self.type = key_attrs.get('type') +# +# def bind_entry(self, entry, metadata): +# """Build literal file information.""" +# if entry.tag == 'Path': +# entry.set('type', 'file') +# entry.text = self.get_key(entry, metadata) +# +# def get_key(self, entry, metadata): +# fname = +dir '.H_' + metadata.hostname +# # TODO add logic to get+verify key if hostfile exists & save if not +# pdb.set_trace() +# return self.build_key() +# +# def build_key(self): +# if self.type == 'rsa': +# cmd = "openssl genrsa %s " % self.bits +# elif self.type == 'dsa': +# cmd = "openssl dsaparam -noout -genkey %s" % self.bits +# key = Popen(cmd, shell=True, stdout=PIPE).stdout.read() +# return key +# +# +#class SSLCACertFile(SSLCAFile): +# +# def __init__(self, datastore, name, specific, encoding): +# SSLCAFile.__init__(self, datastore, name, specific, encoding) +# cert_attrs = self.xml.find('Cert') +# #self.format = cert_attrs.get('format') +# #self.key = cert_attrs.get('key') +# #self.ca = cert_attrs.get('ca') +# +# def bind_entry(self, entry, metadata): +# """Build literal file information.""" +# fname = entry.get('realname', entry.get('name')) +# if entry.tag == 'Path': +# entry.set('type', 'file') +# entry.text = 'booya cert' +# +# +#class SSLCAEntrySet(Bcfg2.Server.Plugin.EntrySet): +# """ +# Handles host and group specific entries +# """ +# def __init__(self, datastore, basename, path, entry_type, encoding): +# self.data = datastore +# Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, entry_type, encoding) +# +# def entry_init(self, event): +# """Handle template and info file creation.""" +# logger = logging.getLogger('Bcfg2.Plugins.SSLCA') +# if event.filename in self.entries: +# logger.warn("Got duplicate add for %s" % event.filename) +# else: +# fpath = "%s/%s" % (self.path, event.filename) +# try: +# spec = self.specificity_from_filename(event.filename) +# except Bcfg2.Server.Plugin.SpecificityError: +# if not self.ignore.match(event.filename): +# logger.error("Could not process filename %s; ignoring" % fpath) +# return +# self.entries[event.filename] = self.entry_type(self.data, fpath, +# spec, self.encoding) +# self.entries[event.filename].handle_event(event) +# +# +#class SSLCA(Bcfg2.Server.Plugin.GroupSpool): +# """ +# The SSLCA generator handles the creation and +# management of ssl certificates and their keys. +# """ +# name = 'SSLCA' +# __version__ = '$Id:$' +# __author__ = 'g.hagger@gmail.com' +# filename_pattern = '(key|cert)\.xml' +# es_cls = SSLCAEntrySet +# +# def __init__(self, core, datastore): +# Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore) +# +# def HandleEvent(self, event): +# action = event.code2str() +# if event.filename[0] == '/': +# return +# epath = "".join([self.data, self.handles[event.requestID], +# event.filename]) +# if posixpath.isdir(epath): +# ident = self.handles[event.requestID] + event.filename +# else: +# ident = self.handles[event.requestID][:-1] +# +# if action in ['exists', 'created']: +# if posixpath.isdir(epath): +# self.AddDirectoryMonitor(epath[len(self.data):]) +# if ident not in self.entries and posixpath.isfile(epath): +# if event.filename.endswith('key.xml'): +# es_child_cls = SSLCAKeyFile +# elif event.filename.endswith('cert.xml'): +# es_child_cls = SSLCACertFile +# else: +# return +# dirpath = "".join([self.data, ident]) +# self.entries[ident] = self.es_cls(self.data, +# self.filename_pattern, +# dirpath, +# es_child_cls, +# self.encoding) +# self.Entries['Path'][ident] = self.entries[ident].bind_entry +# if not posixpath.isdir(epath): +# # do not pass through directory events +# self.entries[ident].handle_event(event) +# if action == 'changed': +# self.entries[ident].handle_event(event) +# elif action == 'deleted': +# fbase = self.handles[event.requestID] + event.filename +# if fbase in self.entries: +# # a directory was deleted +# del self.entries[fbase] +# del self.Entries['Path'][fbase] +# else: +# self.entries[ident].handle_event(event) + + + + + + + + - def HandleEvent(self, event=None): - """Local event handler that does something....""" - Bcfg2.Server.Plugin.DirectoryBacked.HandleEvent(self, event) - def HandlesEntry(self, entry, _): - """Handle entries dynamically.""" - return entry.tag == 'Path' and (entry.get('name').endswith(self.hostkey) or entry.get('name').endswith(self.hostcert)) +#import Bcfg2.Options +#import os +#from ConfigParser import ConfigParser, NoSectionError, NoOptionError +#from M2Crypto import RSA, EVP, X509, m2 +#class SSLCA(Bcfg2.Server.Plugin.Plugin, +# Bcfg2.Server.Plugin.Generator, +# Bcfg2.Server.Plugin.DirectoryBacked): +# """ +# The sslca generator manages ssl certificates +# and keys +# """ +# +# name = 'SSLbase' +# __version__ = '0.00000000001' +# __author__ = 'ghagger@wgen.net' +# +# hostkey = 'localhost.key' +# hostcert = 'localhost.crt' +# +# def __init__(self, core, datastore): +# Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) +# Bcfg2.Server.Plugin.Generator.__init__(self) +# try: +# Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, +# self.core.fam) +# except OSError, ioerr: +# self.logger.error("Failed to load SSLbase repository from %s" \ +# % (self.data)) +# self.logger.error(ioerr) +# raise Bcfg2.Server.Plugin.PluginInitError +# self.Entries = {'Path': +# {'/etc/pki/tls/private/localhost.key': self.get_key, +# '/etc/pki/tls/certs/localhost.crt': self.get_cert}} +# # grab essential sslca configuration from bcfg2.conf +# cp = ConfigParser() +# cp.read(Bcfg2.Options.CFILE.value) +# try: +# ca_cert_filename = cp.get('sslca', 'ca_cert') +# ca_key_filename = cp.get('sslca', 'ca_key') +# self.ca_key_passphrase = cp.get('sslca', 'ca_key_passphrase') +# self.cert_subject = cp.get('sslca', 'cert_subject') +# self.cert_days = cp.get('sslca', 'cert_days') +# self.pkey_bits = cp.get('sslca', 'pkey_bits') +# except: +# raise NoOptionError +# self.ca_cert = X509.load_cert(ca_cert_filename) +# self.ca_key = EVP.load_key(ca_key_filename, lambda x: self.ca_key_passphrase) +# self._newkey = False +# +# def get_key(self, entry, metadata): +# filename = self.hostkey+".H_%s" % metadata.hostname +# if filename in self.entries.keys(): +# entry.text = self.entries[filename].data +# self.pkey = EVP.load_key_string(entry.text) +# else: +# (self.pkey, entry.text) = self.build_key(filename) +# keyfile = open(self.data + '/' +filename, 'w') +# keyfile.write(entry.text) +# keyfile.close() +# self._newkey = True +# +# def build_key(self, filename): +# """Generate new private key for client.""" +# rsa_key = RSA.gen_key(int(self.pkey_bits), m2.RSA_F4) +# pkey = EVP.PKey() +# pkey.assign_rsa(rsa_key) +# keyfile = open(self.data + '/' +filename, 'w') +# keyfile.write(pkey.as_pem(cipher=None)) +# keyfile.close() +# self._newkey = True +# return pkey, pkey.as_pem(cipher=None) +# +# def get_cert(self, entry, metadata): +# filename = self.hostcert + ".H_%s" % metadata.hostname +# # load prexisting cert, if any +# if filename in self.entries.keys() and self._newkey == False: +# cert = X509.load_cert_string(self.entries[filename].data) +# # check cert subjectAltNames match current aliases +# cert_aliases = cert.get_ext('subjectAltName') +# if cert_aliases: +# if metadata.aliases != [alias.lstrip('DNS:') for alias in cert_aliases.get_value().split(', ')]: +# entry.text = self.build_cert(filename, metadata) +# return +# entry.text = cert.as_text()+cert.as_string() +# else: +# entry.text = self.build_cert(filename, metadata) +# +# def get_serial(self): +# serialpath = self.data + '/serial' +# serial = 0 +# if os.path.isfile(serialpath): +# serialfile = open(serialpath, 'r') +# serial = int(serialfile.read()) +# serialfile.close() +# serialfile = open(serialpath, 'w') +# serial += 1 +# serialfile.write(str(serial)) +# serialfile.close() +# return serial +# +# def build_cert(self, filename, metadata): +# req = self.make_request(self.pkey, metadata) +# serial = self.get_serial() +# cert = self.make_cert(req, serial, metadata.aliases) +# cert_out = cert.as_text()+cert.as_pem() +# certfile = open(self.data + '/' +filename, 'w') +# certfile.write(cert_out) +# certfile.close() +# cert_store = self.data + '/certstore' +# if not os.path.isdir(cert_store): +# os.mkdir(cert_store) +# storefile = open(cert_store + '/' + str(serial) + '.pem', 'w') +# storefile.write(cert_out) +# storefile.close() +# return cert_out +# +# def make_request(self, key, metadata): +# req = X509.Request() +# req.set_version(2) +# req.set_pubkey(key) +# name = X509.X509_Name() +# parts = [a.split('=') for a in self.cert_subject.split(',')] +# [setattr(name, k, v) for k,v in parts] +# name.CN = metadata.hostname +# req.set_subject_name(name) +# req.sign(key, 'sha1') +# return req +# +# def make_cert(self, req, serial, aliases): +# pkey = req.get_pubkey() +# if not req.verify(pkey): +# raise ValueError, 'Error verifying request' +# sub = req.get_subject() +# cert = X509.X509() +# cert.set_serial_number(serial) +# cert.set_version(2) +# cert.set_subject(sub) +# cert.set_issuer(self.ca_cert) +# cert.set_pubkey(pkey) +# notBefore = m2.x509_get_not_before(cert.x509) +# notAfter = m2.x509_get_not_after(cert.x509) +# m2.x509_gmtime_adj(notBefore, 0) +# m2.x509_gmtime_adj(notAfter, 60*60*24*long(self.cert_days)) +# exts = [ +# ('basicConstraints','CA:FALSE'), +# ('subjectKeyIdentifier','hash'), +# ('authorityKeyIdentifier','keyid,issuer:always'), +# ('nsCertType','SSL Server'), +# ] +# if aliases: +# exts.append(('subjectAltName', ','.join(['DNS:'+alias for alias in aliases]))) +# for ext in exts: +# cert.add_ext(X509.new_extension(ext[0],ext[1])) +# cert.sign(self.ca_key, 'sha1') +# return cert +# +# def HandleEvent(self, event=None): +# """Local event handler that does something....""" +# Bcfg2.Server.Plugin.DirectoryBacked.HandleEvent(self, event) +# +# def HandlesEntry(self, entry, _): +# """Handle entries dynamically.""" +# return entry.tag == 'Path' and (entry.get('name').endswith(self.hostkey) or entry.get('name').endswith(self.hostcert)) +# -- cgit v1.2.3-1-g7c22