From 1b1b6f5733d65cbb73f48ac9b4419aba3dc00eee Mon Sep 17 00:00:00 2001 From: Jude Nagurney Date: Sun, 1 Mar 2015 21:41:08 -0500 Subject: [PATCH] (Not working yet, but the changeset was getting too big: The service starts, but certmaster-request still giving an error) --- .gitignore | 3 ++ AUTHORS | 1 + MANIFEST.in | 2 +- Makefile | 3 ++ certmaster/certmaster.py | 97 +++++++++++++++++++++----------------- certmaster/config.py | 24 +++++++++- certmaster/requester.py | 4 +- certmaster/utils.py | 13 ++--- etc/certmaster.conf | 24 ++++++++-- etc/minion.conf | 3 ++ scripts/certmaster-ca | 19 ++++---- scripts/certmaster-request | 6 ++- scripts/certmaster-sync | 5 +- 13 files changed, 136 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 35a6b22..442342c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *~ \#*\# build/ +MANIFEST +dist +rpm-build diff --git a/AUTHORS b/AUTHORS index 5ab8e48..fe269a0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,6 +11,7 @@ Additional patches and contributions by ... Tanabe Ken-ichi Steve Salesvan Jonathan Barber + Jude Nagurney ... [ send in patches to get your name here ] diff --git a/MANIFEST.in b/MANIFEST.in index 782337c..9633326 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,7 @@ recursive-include docs * recursive-include init-scripts * recursive-include po *.po recursive-include po *.pot -recursice-include triggers * +recursive-include triggers * include AUTHORS include LICENSE include README diff --git a/Makefile b/Makefile index dc2ed1b..af1432e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ VERSION = 0.28 RELEASE = 1 PYTHON = /usr/bin/python +DIST = pwan MESSAGESPOT=po/messages.pot @@ -92,7 +93,9 @@ async: install rpms: build manpage sdist mkdir -p rpm-build cp dist/*.gz rpm-build/ + echo ${RELEASE} rpmbuild --define "_topdir %(pwd)/rpm-build" \ + --define "dist ${DIST}" \ --define "_builddir %{_topdir}" \ --define "_rpmdir %{_topdir}" \ --define "_srcrpmdir %{_topdir}" \ diff --git a/certmaster/certmaster.py b/certmaster/certmaster.py index 7b133df..3fcb78f 100644 --- a/certmaster/certmaster.py +++ b/certmaster/certmaster.py @@ -57,34 +57,43 @@ class CertMaster(object): usename = utils.get_hostname(talk_to_certmaster=False) - mycn = '%s-CA-KEY' % usename - self.ca_key_file = '%s/certmaster.key' % self.cfg.cadir - self.ca_cert_file = '%s/certmaster.crt' % self.cfg.cadir - self.logger = logger.Logger().logger self.audit_logger = logger.AuditLogger() - # if ca_key_file exists and ca_cert_file is missing == minion only setup - if os.path.exists(self.ca_key_file) and not os.path.exists(self.ca_cert_file): - return + self.cakey = {} + self.cacert = {} - 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) + for (s_caname,a_ca) in self.cfg.ca.iteritems(): + s_cadir = a_ca['cadir'] + if s_caname == "": + mycn = '%s-CA-KEY' % usename + else: + mycn = '%s-%s-CA-KEY' % (s_caname.upper(),usename) + + s_ca_key_file = '%s/certmaster.key' % s_cadir + s_ca_cert_file = '%s/certmaster.crt' % s_cadir - # 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) + # if ca_key_file exists and ca_cert_file is missing == minion only setup + if os.path.exists(s_ca_key_file) and not os.path.exists(s_ca_cert_file): + continue - for dirpath in [self.cfg.cadir, self.cfg.certroot, self.cfg.csrroot]: - if not os.path.exists(dirpath): - os.makedirs(dirpath) + try: + if not os.path.exists(s_cadir): + os.makedirs(s_cadir) + if not os.path.exists(s_ca_key_file) and not os.path.exists(s_ca_cert_file): + certs.create_ca(CN=mycn, ca_key_file=s_ca_key_file, ca_cert_file=s_ca_cert_file) + except (IOError, OSError), e: + print 'Cannot make certmaster certificate authority keys/certs for CA %s, aborting: %s' % (s_caname, e) + sys.exit(1) + + # open up the cakey and cacert so we have them available + self.cakey[s_caname] = certs.retrieve_key_from_file(s_ca_key_file) + self.cacert[s_caname] = certs.retrieve_cert_from_file(s_ca_cert_file) + + for dirpath in [a_ca['cadir'], a_ca['certroot'], a_ca['csrroot'], a_ca['csrroot']]: + if not os.path.exists(dirpath): + os.makedirs(dirpath) # setup handlers self.handlers = { @@ -108,7 +117,7 @@ class CertMaster(object): commonname = commonname.replace('\\', '') return commonname - def wait_for_cert(self, csrbuf, with_triggers=True): + def wait_for_cert(self, csrbuf, ca='', with_triggers=True): """ takes csr as a string returns True, caller_cert, ca_cert @@ -129,8 +138,8 @@ class CertMaster(object): self.logger.info("%s requested signing of cert %s" % (requesting_host,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) + certfile = '%s/%s.cert' % (self.cfg.ca[ca]['certroot'], requesting_host) + csrfile = '%s/%s.csr' % (self.cfg.ca[ca]['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 @@ -165,12 +174,12 @@ class CertMaster(object): # 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: + if self.cfg.ca[ca]['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) - self.logger.info("cert for %s was autosigned" % (requesting_host)) + self.logger.info("cert for %s for ca %s was autosigned" % (requesting_host,ca)) if with_triggers: self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*') return True, cert_buf, cacert_buf @@ -181,16 +190,16 @@ class CertMaster(object): destfo.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csrreq)) destfo.close() del destfo - self.logger.info("cert for %s created and ready to be signed" % (requesting_host)) + self.logger.info("cert for %s for CA %s created and ready to be signed" % (requesting_host, ca)) if with_triggers: self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*') return False, '', '' return False, '', '' - def get_csrs_waiting(self): + def get_csrs_waiting(self, ca=''): hosts = [] - csrglob = '%s/*.csr' % self.cfg.csrroot + csrglob = '%s/*.csr' % self.cfg.ca[ca]['csrroot'] csr_list = glob.glob(csrglob) for f in csr_list: hn = os.path.basename(f) @@ -198,12 +207,12 @@ class CertMaster(object): hosts.append(hn) return hosts - def remove_this_cert(self, hn, with_triggers=True): + def remove_this_cert(self, hn, with_triggers=True, ca=''): """ removes cert for hostname using unlink """ cm = self - csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn) + csrglob = '%s/%s.csr' % (cm.cfg.ca[ca]['csrroot'], hn) csrs = glob.glob(csrglob) - certglob = '%s/%s.cert' % (cm.cfg.certroot, hn) + certglob = '%s/%s.cert' % (cm.cfg.ca[ca]['certroot'], hn) certs = glob.glob(certglob) if not csrs and not certs: # FIXME: should be an exception? @@ -218,7 +227,7 @@ class CertMaster(object): if with_triggers: self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/post/*') - def sign_this_csr(self, csr, with_triggers=True): + def sign_this_csr(self, csr, with_triggers=True,ca=''): """returns the path to the signed cert file""" csr_unlink_file = None @@ -228,10 +237,10 @@ class CertMaster(object): 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)) + elif os.path.exists('%s/%s' % (self.cfg.ca[ca]['csrroot'], csr)): # we have a partial path? + csrfo = open('%s/%s' % (self.cfg.ca[ca]['csrroot'], csr)) csr_buf = csrfo.read() - csr_unlink_file = '%s/%s' % (self.cfg.csrroot, csr) + csr_unlink_file = '%s/%s' % (self.cfg.ca[ca]['csrroot'], csr) # we have a string of some kind else: @@ -252,9 +261,9 @@ class CertMaster(object): self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/pre/*') - certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host) + certfile = '%s/%s.cert' % (self.cfg.ca[ca]['certroot'], requesting_host) self.logger.info("Signing for csr %s requested" % certfile) - thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir) + thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.ca[ca]['cadir']) destfo = open(certfile, 'w') destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert)) @@ -273,8 +282,8 @@ class CertMaster(object): return certfile # return a list of already signed certs - def get_signed_certs(self, hostglobs=None): - certglob = "%s/*.cert" % (self.cfg.certroot) + def get_signed_certs(self, hostglobs=None, ca=''): + certglob = "%s/*.cert" % (self.cfg.ca[ca]['certroot']) certs = [] globs = "*" @@ -282,7 +291,7 @@ class CertMaster(object): globs = hostglobs for hostglob in globs: - certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob) + certglob = "%s/%s.cert" % (self.cfg.ca[ca]['certroot'], hostglob) certs = certs + glob.glob(certglob) signed_certs = [] @@ -300,8 +309,8 @@ class CertMaster(object): return glob.glob(myglob) # return a list of the cert hash string we use to identify systems - def get_cert_hashes(self, hostglobs=None): - certglob = "%s/*.cert" % (self.cfg.certroot) + def get_cert_hashes(self, hostglobs=None,ca=''): + certglob = "%s/*.cert" % (self.cfg.ca[ca]['certroot']) certfiles = [] globs = "*" @@ -309,7 +318,7 @@ class CertMaster(object): globs = hostglobs for hostglob in globs: - certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob) + certglob = "%s/%s.cert" % (self.cfg.ca[ca]['certroot'], hostglob) certfiles = certfiles + glob.glob(certglob) cert_hashes = [] diff --git a/certmaster/config.py b/certmaster/config.py index 4cf2a7f..cac9394 100644 --- a/certmaster/config.py +++ b/certmaster/config.py @@ -60,7 +60,7 @@ class Option(object): @param obj: The configuration instance to modify. @param objtype: The type of the config instance (not used). @return: The parsed option value or the default value if the value - wasn't set in the configuration file. + was not set in the configuration file. ''' if obj is None: return self @@ -475,4 +475,26 @@ def read_config(config_file, BaseConfigDerived): print >> sys.stderr, "Error reading config file: %s" % e sys.exit(1) opts.populate(confparser, 'main') + + ## build up the cas structure + opts.ca = {} + opts.ca[''] = {} + + ## Add the default items when just using a single ca + main_items = confparser.items('main') + for (key,value) in main_items: + if key in ['autosign','cadir','cert_dir','certroot','csrroot']: + opts.ca[''][key] = value + + ## Add additonal ca sections + sections = confparser.sections() + for a_section in sections: + if a_section.startswith('ca:'): + ca_name = a_section[3:] + items = confparser.items(a_section) + opts.ca[ca_name] = {} + for (key,value) in items: + opts.ca[ca_name][key] = value + print 'opts.ca: %s %s %s' % (ca_name, key, value) + return opts diff --git a/certmaster/requester.py b/certmaster/requester.py index 1fd6826..9a0e94f 100644 --- a/certmaster/requester.py +++ b/certmaster/requester.py @@ -15,8 +15,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import utils -def request_cert(hostname=None): +def request_cert(hostname=None, ca=''): # this should be enough, but do we want to allow parameters # for overriding the server and port from the config file? # maybe not. -- mpd - utils.create_minion_keys(hostname) + utils.create_minion_keys(hostname,ca) diff --git a/certmaster/utils.py b/certmaster/utils.py index b135e7d..d160982 100644 --- a/certmaster/utils.py +++ b/certmaster/utils.py @@ -122,13 +122,15 @@ def get_hostname(talk_to_certmaster=True): # FIXME: move to requestor module and also create a verbose mode # prints to the screen for usage by /usr/bin/certmaster-request -def create_minion_keys(hostname=None): +def create_minion_keys(hostname=None, ca=''): log = logger.Logger().logger # FIXME: paths should not be hard coded here, move to settings universally config_file = '/etc/certmaster/minion.conf' config = read_config(config_file, MinionConfig) - cert_dir = config.cert_dir + + cert_dir = config.ca[ca]['cert_dir'] + master_uri = 'http://%s:%s/' % (config.certmaster, config.certmaster_port) hn = hostname @@ -146,7 +148,6 @@ def create_minion_keys(hostname=None): cert_file = '%s/%s.cert' % (cert_dir, hn) ca_cert_file = '%s/ca.cert' % cert_dir - if os.path.exists(cert_file) and os.path.exists(ca_cert_file): # print "DEBUG: err, no cert_file" return @@ -171,7 +172,7 @@ def create_minion_keys(hostname=None): try: # print "DEBUG: submitting CSR to certmaster: %s" % master_uri log.debug("submitting CSR: %s to certmaster %s" % (csr_file, master_uri)) - result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri) + result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri, ca) except socket.error, e: log.warning("Could not locate certmaster at %s" % master_uri) @@ -231,7 +232,7 @@ def run_triggers(ref, globber): raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc } -def submit_csr_to_master(csr_file, master_uri): +def submit_csr_to_master(csr_file, master_uri, ca=''): """" gets us our cert back from the certmaster.wait_for_cert() method takes csr_file as path location and master_uri @@ -243,4 +244,4 @@ def submit_csr_to_master(csr_file, master_uri): s = xmlrpclib.ServerProxy(master_uri) # print "DEBUG: waiting for cert" - return s.wait_for_cert(csr) + return s.wait_for_cert(csr,ca) diff --git a/etc/certmaster.conf b/etc/certmaster.conf index cfdca9d..e8d6c67 100644 --- a/etc/certmaster.conf +++ b/etc/certmaster.conf @@ -1,12 +1,30 @@ # configuration for certmasterd and certmaster-ca [main] -autosign = no listen_addr = listen_port = 51235 +cert_extension = cert +sync_certs = False + +# Use thse settings if no --ca flag provided +autosign = no cadir = /etc/pki/certmaster/ca cert_dir = /etc/pki/certmaster certroot = /var/lib/certmaster/certmaster/certs csrroot = /var/lib/certmaster/certmaster/csrs -cert_extension = cert -sync_certs = False + +# use these directories if '--ca=ldap' provided in the certmaster-ca commands +# [ca:ldap] +# autosign = yes +# cadir = /etc/pki/certmaster/ldap-ca +# cert_dir = /etc/pki/certmaster/ldap +# certroot = /var/lib/certmaster/ldap/certs +# csrroot = /var/lib/certmaster/ldap/csrs + +# use these directories if '--ca=yourapp' provided in the certmaster-ca commands +# [ca:yourapp] +# autosign = yes +# cadir = /etc/pki/certmaster/yourapp-ca +# cert_dir = /etc/pki/certmaster/yourapp +# certroot = /var/lib/certmaster/yourapp/certs +# csrroot = /var/lib/certmaster/yourapp/csrs diff --git a/etc/minion.conf b/etc/minion.conf index 47c6540..7a25ce2 100644 --- a/etc/minion.conf +++ b/etc/minion.conf @@ -6,3 +6,6 @@ certmaster_port = 51235 log_level = DEBUG cert_dir = /etc/pki/certmaster +# [ca:ldap] +# cert_dir = /etc/pki/certmaster-ldap + diff --git a/scripts/certmaster-ca b/scripts/certmaster-ca index d10c1a2..0e43253 100755 --- a/scripts/certmaster-ca +++ b/scripts/certmaster-ca @@ -1,8 +1,9 @@ #!/usr/bin/python -tt # sign/list keys +# --ca ca sign/list certs for the 'ca' # --sign hostname hostname hostname # --list # lists all csrs needing to be signed -# --list-all ? +# --list-all ca list all certs for a given ca # --clean? not sure what it will do import sys @@ -27,7 +28,9 @@ class CertmasterCAOptionParser(optparse.OptionParser): def parseargs(args): usage = 'certmaster-ca