certmaster-0.25/0000775000076400007640000000000011213774723013110 5ustar adrianadriancertmaster-0.25/AUTHORS0000664000076400007640000000063511064771520014160 0ustar adrianadrian certmaster is written and maintained by (alphabetically) ... Michael DeHaan Adrian Likins Seth Vidal ... Additional patches and contributions by ... Tanabe Ken-ichi Steve Salesvan Jonathan Barber ... [ send in patches to get your name here ] certmaster-0.25/init-scripts/0000775000076400007640000000000011213774723015540 5ustar adrianadriancertmaster-0.25/init-scripts/certmaster0000775000076400007640000000545311211536530017634 0ustar adrianadrian#!/bin/sh # # certmaster certmaster ################################### # LSB header ### BEGIN INIT INFO # Provides: certmaster # Required-Start: network # Default-Start: 3 4 5 # Default-Stop: 0 1 2 6 # Short-Description: certificate master for Fedora Unified Network Control 'master server only' # Description: certificate master to sign/manage ca/cert infrastructure ### END INIT INFO # chkconfig header # chkconfig: - 98 99 # description: certificate master to sign/manage ca/cert infrastructure # # processname: /usr/bin/certmaster # Sanity checks. [ -x /usr/bin/certmaster ] || exit 0 SERVICE=certmaster PROCESS=certmaster DAEMON=/usr/bin/certmaster CONFIG_ARGS="--daemon" CAStatus() { ps wt? | grep "$DAEMON" 2>&1 > /dev/null if [ "x$?" = "x0" ]; then RVAL=0 echo "certmaster is running" else RVAL=3 echo "certmaster is not running" fi return $RVAL } if [ -f /lib/lsb/init-functions ]; then . /lib/lsb/init-functions alias START_DAEMON=start_daemon alias STATUS=CAStatus alias LOG_SUCCESS=log_success_msg alias LOG_FAILURE=log_failure_msg alias LOG_WARNING=log_warning_msg elif [ -f /etc/init.d/functions ]; then . /etc/init.d/functions alias START_DAEMON=daemon alias STATUS=status alias LOG_SUCCESS=success alias LOG_FAILURE=failure alias LOG_WARNING=passed else echo "Error: your platform is not supported by $0" > /dev/stderr exit 1 fi RETVAL=0 start() { if [ -f /etc/debian_version ]; then log_begin_msg "Starting certmaster daemon: " start-stop-daemon --exec $DAEMON --start --quiet -- $CONFIG_ARGS > /dev/null RETVAL=$? log_end_msg $RETVAL return $RETVAL else echo -n $"Starting certmaster daemon: " START_DAEMON $PROCESS $CONFIG_ARGS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE return $RETVAL fi } stop() { if [ -f /etc/debian_version ]; then log_daemon_msg "Stopping certmaster daemon" "certmaster" start-stop-daemon --stop --quiet --pidfile /var/run/certmaster.pid --name certmaster RETVAL=$? log_end_msg $RETVAL rm -f /var/run/certmaster.pid else echo -n $"Stopping certmaster daemon: " killproc $PROCESS RETVAL=$? echo if [ $RETVAL -eq 0 ]; then rm -f /var/lock/subsys/$SERVICE rm -f /var/run/$SERVICE.pid fi fi } restart() { stop start } # See how we were called. case "$1" in start|stop|restart) $1 ;; status) STATUS $PROCESS RETVAL=$? ;; condrestart) [ -f /var/lock/subsys/$SERVICE ] && restart || : ;; reload) echo "can't reload configuration, you have to restart it" RETVAL=$? ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" exit 1 ;; esac exit $RETVAL certmaster-0.25/README0000664000076400007640000000024111175632103013755 0ustar adrianadriancertmaster -- it hands out SSL certs! read more at: https://fedorahosted.org/certmaster/ Source: http://git.fedorahosted.org/git/?p=certmaster.git;a=summary certmaster-0.25/certmaster/0000775000076400007640000000000011213774723015261 5ustar adrianadriancertmaster-0.25/certmaster/requester.py0000664000076400007640000000126211175632107017647 0ustar adrianadrian""" certmaster requester -- asks for certs any python app that wants to use certmaster can use this module Copyright 2008, Red Hat, Inc Michael DeHaan 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. """ import utils def request_cert(hostname=None): # 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) certmaster-0.25/certmaster/utils.py0000664000076400007640000001651011175632107016772 0ustar adrianadrian""" Copyright 2007-2008, 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. """ import os import string import sys import traceback import xmlrpclib import socket import time import glob import codes import certs from config import read_config from commonconfig import MinionConfig import logger import sub_process # FIXME: module needs better pydoc # FIXME: can remove this constant? REMOTE_ERROR = "REMOTE_ERROR" # The standard I/O file descriptors are redirected to /dev/null by default. if (hasattr(os, "devnull")): REDIRECT_TO = os.devnull else: REDIRECT_TO = "/dev/null" def trace_me(): x = traceback.extract_stack() bar = string.join(traceback.format_list(x)) return bar def daemonize(pidfile=None): """ Daemonize this process with the UNIX double-fork trick. Writes the new PID to the provided file name if not None. """ pid = os.fork() if pid > 0: sys.exit(0) os.chdir("/") os.setsid() os.umask(077) pid = os.fork() os.close(0) os.close(1) os.close(2) # based on http://code.activestate.com/recipes/278731/ os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) os.dup2(0, 1) # standard output (1) os.dup2(0, 2) # standard error (2) if pid > 0: if pidfile is not None: open(pidfile, "w").write(str(pid)) sys.exit(0) def nice_exception(etype, evalue, etb): etype = str(etype) try: lefti = etype.index("'") + 1 righti = etype.rindex("'") nicetype = etype[lefti:righti] except: nicetype = etype nicestack = string.join(traceback.format_list(traceback.extract_tb(etb))) return [ REMOTE_ERROR, nicetype, str(evalue), nicestack ] def is_error(result): # FIXME: I believe we can remove this function if type(result) != list: return False if len(result) == 0: return False if result[0] == REMOTE_ERROR: return True return False def get_hostname(talk_to_certmaster=True): """ "localhost" is a lame hostname to use for a key, so try to get a more meaningful hostname. We do this by connecting to the certmaster and seeing what interface/ip it uses to make that connection, and looking up the hostname for that. """ # FIXME: this code ignores http proxies (which granted, we don't # support elsewhere either. hostname = None hostname = socket.gethostname() # print "DEBUG: HOSTNAME TRY1: %s" % hostname try: ip = socket.gethostbyname(hostname) except: return hostname if ip != "127.0.0.1": return hostname # 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): 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 master_uri = 'http://%s:%s/' % (config.certmaster, config.certmaster_port) hn = hostname if hn is None: hn = get_hostname() if hn is None: raise codes.CMException("Could not determine a hostname other than localhost") key_file = '%s/%s.pem' % (cert_dir, hn) csr_file = '%s/%s.csr' % (cert_dir, hn) 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 keypair = None try: if not os.path.exists(cert_dir): os.makedirs(cert_dir) if not os.path.exists(key_file): keypair = certs.make_keypair(dest=key_file) if not os.path.exists(csr_file): if not keypair: keypair = certs.retrieve_key_from_file(key_file) csr = certs.make_csr(keypair, dest=csr_file, hostname=hn) except Exception, e: traceback.print_exc() raise codes.CMException, "Could not create local keypair or csr for session" result = False while not result: 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) except socket.gaierror, e: raise codes.CMException, "Could not locate certmaster at %s" % master_uri # logging here would be nice if not result: # print "DEBUG: no response from certmaster, sleeping 10 seconds" log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri) time.sleep(10) if result: # print "DEBUG: recieved certificate from certmaster" log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file)) if not keypair: keypair = certs.retrieve_key_from_file(key_file) valid = certs.check_cert_key_match(cert_string, keypair) if not valid: log.info("certificate does not match key (run certmaster-ca --clean first?)") sys.stderr.write("certificate does not match key (run certmaster-ca --clean first?)\n") return cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644) os.write(cert_fd, cert_string) os.close(cert_fd) ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644) os.write(ca_cert_fd, ca_cert_string) os.close(ca_cert_fd) def run_triggers(ref, globber): """ Runs all the trigger scripts in a given directory. ref can be a certmaster object, if not None, the name will be passed to the script. If ref is None, the script will be called with no argumenets. Globber is a wildcard expression indicating which triggers to run. Example: "/var/lib/certmaster/triggers/blah/*" """ log = logger.Logger().logger triggers = glob.glob(globber) triggers.sort() for file in triggers: log.debug("Executing trigger: %s" % file) try: if file.find(".rpm") != -1: # skip .rpmnew files that may have been installed # in the triggers directory continue if ref: rc = sub_process.call([file, ref], shell=False) else: rc = sub_process.call([file], shell=False) except: log.warning("Warning: failed to execute trigger: %s" % file) continue if rc != 0: raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc } def submit_csr_to_master(csr_file, master_uri): """" gets us our cert back from the certmaster.wait_for_cert() method takes csr_file as path location and master_uri returns Bool, str(cert), str(ca_cert) """ fo = open(csr_file) csr = fo.read() s = xmlrpclib.ServerProxy(master_uri) # print "DEBUG: waiting for cert" return s.wait_for_cert(csr) certmaster-0.25/certmaster/CommonErrors.py0000664000076400007640000000431311064771520020255 0ustar adrianadrian# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Copyright 2005 Dan Williams and Red Hat, Inc. from exceptions import Exception def canIgnoreSSLError(e): """ Identify common network errors that mean we cannot connect to the server """ # This is a bit complicated by the fact that different versions of # M2Crypto & OpenSSL seem to return different error codes for the # same type of error s = "%s" % e if e[0] == 104: # Connection refused return True elif e[0] == 111: # Connection reset by peer return True elif e[0] == 61: # Connection refused return True elif e[0] == 54: # Connection reset by peer return True elif s == "no certificate returned": return True elif s == "wrong version number": return True elif s == "unexpected eof": return True return False def canIgnoreSocketError(e): """ Identify common network errors that mean we cannot connect to the server """ try: if e[0] == 111: # Connection refused return True elif e[0] == 104: # Connection reset by peer return True elif e[0] == 61: # Connection refused return True except IndexError: return True return False # FIXME: is anything using this? remove underscores class CertMaster_Client_Exception(Exception): def __init__(self, value=None): Exception.__init__(self) self.value = value def __str__(self): return "%s" %(self.value,) certmaster-0.25/certmaster/codes.py0000664000076400007640000000107411175632103016722 0ustar adrianadrian""" Certmaster Copyright 2007-2008, 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. """ import exceptions class CertMasterException(exceptions.Exception): pass class CMException(CertMasterException): pass class InvalidMethodException(CertMasterException): pass # FIXME: more sub-exceptions maybe certmaster-0.25/certmaster/__init__.py0000664000076400007640000000000011064771520017354 0ustar adrianadriancertmaster-0.25/certmaster/SSLConnection.py0000664000076400007640000001234111064771520020311 0ustar adrianadrian# Higher-level SSL objects used by rpclib # # Copyright (c) 2002 Red Hat, Inc. # # Author: Mihai Ibanescu # Modifications by Dan Williams from OpenSSL import SSL import time, socket, select from CommonErrors import canIgnoreSSLError class SSLConnection: """ This whole class exists just to filter out a parameter passed in to the shutdown() method in SimpleXMLRPC.doPOST() """ DEFAULT_TIMEOUT = 20 def __init__(self, conn): """ Connection is not yet a new-style class, so I'm making a proxy instead of subclassing. """ self.__dict__["conn"] = conn self.__dict__["close_refcount"] = 0 self.__dict__["closed"] = False self.__dict__["timeout"] = self.DEFAULT_TIMEOUT def __del__(self): self.__dict__["conn"].close() def __getattr__(self,name): return getattr(self.__dict__["conn"], name) def __setattr__(self,name, value): setattr(self.__dict__["conn"], name, value) def settimeout(self, timeout): if timeout == None: self.__dict__["timeout"] = self.DEFAULT_TIMEOUT else: self.__dict__["timeout"] = timeout self.__dict__["conn"].settimeout(timeout) def shutdown(self, how=1): """ SimpleXMLRpcServer.doPOST calls shutdown(1), and Connection.shutdown() doesn't take an argument. So we just discard the argument. """ self.__dict__["conn"].shutdown() def accept(self): """ This is the other part of the shutdown() workaround. Since servers create new sockets, we have to infect them with our magic. :) """ c, a = self.__dict__["conn"].accept() return (SSLConnection(c), a) def makefile(self, mode, bufsize): """ We need to use socket._fileobject Because SSL.Connection doesn't have a 'dup'. Not exactly sure WHY this is, but this is backed up by comments in socket.py and SSL/connection.c Since httplib.HTTPSResponse/HTTPConnection depend on the socket being duplicated when they close it, we refcount the socket object and don't actually close until its count is 0. """ self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1 return PlgFileObject(self, mode, bufsize) def close(self): if self.__dict__["closed"]: return self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1 if self.__dict__["close_refcount"] == 0: self.shutdown() self.__dict__["conn"].close() self.__dict__["closed"] = True def sendall(self, data, flags=0): """ - Use select() to simulate a socket timeout without setting the socket to non-blocking mode. - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead or WantWrite, consuming 100% CPU, and never times out. """ timeout = self.__dict__["timeout"] con = self.__dict__["conn"] (read, write, excpt) = select.select([], [con], [], timeout) if not con in write: raise socket.timeout((110, "Operation timed out.")) starttime = time.time() origlen = len(data) sent = -1 while len(data): curtime = time.time() if curtime - starttime > timeout: raise socket.timeout((110, "Operation timed out.")) try: sent = con.send(data, flags) except SSL.SysCallError, e: if e[0] == 32: # Broken Pipe self.close() sent = 0 else: raise socket.error(e) except (SSL.WantWriteError, SSL.WantReadError): time.sleep(0.2) continue data = data[sent:] return origlen - len(data) def recv(self, bufsize, flags=0): """ Use select() to simulate a socket timeout without setting the socket to non-blocking mode """ timeout = self.__dict__["timeout"] con = self.__dict__["conn"] (read, write, excpt) = select.select([con], [], [], timeout) if not con in read: raise socket.timeout((110, "Operation timed out.")) starttime = time.time() while True: curtime = time.time() if curtime - starttime > timeout: raise socket.timeout((110, "Operation timed out.")) try: return con.recv(bufsize, flags) except SSL.ZeroReturnError: return None except SSL.WantReadError: time.sleep(0.2) except Exception, e: if canIgnoreSSLError(e): return None else: raise e return None class PlgFileObject(socket._fileobject): def close(self): """ socket._fileobject doesn't actually _close_ the socket, which we want it to do, so we have to override. """ try: if self._sock: self.flush() self._sock.close() finally: self._sock = None certmaster-0.25/certmaster/config.py0000664000076400007640000003547611156522367017120 0ustar adrianadrian# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2002 Duke University # filched from yum - menno smits wrote this - he rocks import os import sys import warnings import copy import urlparse from ConfigParser import NoSectionError, NoOptionError, ConfigParser from ConfigParser import ParsingError import exceptions CONFIG_FILE = "/etc/certmaster/certmaster.conf" class ConfigError(exceptions.Exception): def __init__(self, value=None): exceptions.Exception.__init__(self) self.value = value def __str__(self): return "%s" %(self.value,) class Option(object): ''' This class handles a single Yum configuration file option. Create subclasses for each type of supported configuration option. Python descriptor foo (__get__ and __set__) is used to make option definition easy and consise. ''' def __init__(self, default=None): self._setattrname() self.inherit = False self.default = default def _setattrname(self): '''Calculate the internal attribute name used to store option state in configuration instances. ''' self._attrname = '__opt%d' % id(self) def __get__(self, obj, objtype): '''Called when the option is read (via the descriptor protocol). @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. ''' if obj is None: return self return getattr(obj, self._attrname, None) def __set__(self, obj, value): '''Called when the option is set (via the descriptor protocol). @param obj: The configuration instance to modify. @param value: The value to set the option to. @return: Nothing. ''' # Only try to parse if its a string if isinstance(value, basestring): try: value = self.parse(value) except ValueError, e: # Add the field name onto the error raise ValueError('Error parsing %r: %s' % (value, str(e))) setattr(obj, self._attrname, value) def setup(self, obj, name): '''Initialise the option for a config instance. This must be called before the option can be set or retrieved. @param obj: BaseConfig (or subclass) instance. @param name: Name of the option. ''' setattr(obj, self._attrname, copy.copy(self.default)) def clone(self): '''Return a safe copy of this Option instance ''' new = copy.copy(self) new._setattrname() return new def parse(self, s): '''Parse the string value to the Option's native value. @param s: Raw string value to parse. @return: Validated native value. Will raise ValueError if there was a problem parsing the string. Subclasses should override this. ''' return s def tostring(self, value): '''Convert the Option's native value to a string value. @param value: Native option value. @return: String representation of input. This does the opposite of the parse() method above. Subclasses should override this. ''' return str(value) def Inherit(option_obj): '''Clone an Option instance for the purposes of inheritance. The returned instance has all the same properties as the input Option and shares items such as the default value. Use this to avoid redefinition of reused options. @param option_obj: Option instance to inherit. @return: New Option instance inherited from the input. ''' new_option = option_obj.clone() new_option.inherit = True return new_option class ListOption(Option): def __init__(self, default=None): if default is None: default = [] super(ListOption, self).__init__(default) def parse(self, s): """Converts a string from the config file to a workable list Commas and spaces are used as separators for the list """ # we need to allow for the '\n[whitespace]' continuation - easier # to sub the \n with a space and then read the lines s = s.replace('\n', ' ') s = s.replace(',', ' ') return s.split() def tostring(self, value): return '\n '.join(value) class UrlOption(Option): ''' This option handles lists of URLs with validation of the URL scheme. ''' def __init__(self, default=None, schemes=('http', 'ftp', 'file', 'https'), allow_none=False): super(UrlOption, self).__init__(default) self.schemes = schemes self.allow_none = allow_none def parse(self, url): url = url.strip() # Handle the "_none_" special case if url.lower() == '_none_': if self.allow_none: return None else: raise ValueError('"_none_" is not a valid value') # Check that scheme is valid (s,b,p,q,f,o) = urlparse.urlparse(url) if s not in self.schemes: raise ValueError('URL must be %s not "%s"' % (self._schemelist(), s)) return url def _schemelist(self): '''Return a user friendly list of the allowed schemes ''' if len(self.schemes) < 1: return 'empty' elif len(self.schemes) == 1: return self.schemes[0] else: return '%s or %s' % (', '.join(self.schemes[:-1]), self.schemes[-1]) class UrlListOption(ListOption): ''' Option for handling lists of URLs with validation of the URL scheme. ''' def __init__(self, default=None, schemes=('http', 'ftp', 'file', 'https')): super(UrlListOption, self).__init__(default) # Hold a UrlOption instance to assist with parsing self._urloption = UrlOption(schemes=schemes) def parse(self, s): out = [] for url in super(UrlListOption, self).parse(s): out.append(self._urloption.parse(url)) return out class IntOption(Option): def parse(self, s): try: return int(s) except (ValueError, TypeError), e: raise ValueError('invalid integer value') class BoolOption(Option): def parse(self, s): s = s.lower() if s in ('0', 'no', 'false'): return False elif s in ('1', 'yes', 'true'): return True else: raise ValueError('invalid boolean value') def tostring(self, value): if value: return "1" else: return "0" class FloatOption(Option): def parse(self, s): try: return float(s.strip()) except (ValueError, TypeError): raise ValueError('invalid float value') class SelectionOption(Option): '''Handles string values where only specific values are allowed ''' def __init__(self, default=None, allowed=()): super(SelectionOption, self).__init__(default) self._allowed = allowed def parse(self, s): if s not in self._allowed: raise ValueError('"%s" is not an allowed value' % s) return s class BytesOption(Option): # Multipliers for unit symbols MULTS = { 'k': 1024, 'm': 1024*1024, 'g': 1024*1024*1024, } def parse(self, s): """Parse a friendly bandwidth option to bytes The input should be a string containing a (possibly floating point) number followed by an optional single character unit. Valid units are 'k', 'M', 'G'. Case is ignored. Valid inputs: 100, 123M, 45.6k, 12.4G, 100K, 786.3, 0 Invalid inputs: -10, -0.1, 45.6L, 123Mb Return value will always be an integer 1k = 1024 bytes. ValueError will be raised if the option couldn't be parsed. """ if len(s) < 1: raise ValueError("no value specified") if s[-1].isalpha(): n = s[:-1] unit = s[-1].lower() mult = self.MULTS.get(unit, None) if not mult: raise ValueError("unknown unit '%s'" % unit) else: n = s mult = 1 try: n = float(n) except ValueError: raise ValueError("couldn't convert '%s' to number" % n) if n < 0: raise ValueError("bytes value may not be negative") return int(n * mult) class ThrottleOption(BytesOption): def parse(self, s): """Get a throttle option. Input may either be a percentage or a "friendly bandwidth value" as accepted by the BytesOption. Valid inputs: 100, 50%, 80.5%, 123M, 45.6k, 12.4G, 100K, 786.0, 0 Invalid inputs: 100.1%, -4%, -500 Return value will be a int if a bandwidth value was specified or a float if a percentage was given. ValueError will be raised if input couldn't be parsed. """ if len(s) < 1: raise ValueError("no value specified") if s[-1] == '%': n = s[:-1] try: n = float(n) except ValueError: raise ValueError("couldn't convert '%s' to number" % n) if n < 0 or n > 100: raise ValueError("percentage is out of range") return n / 100.0 else: return BytesOption.parse(self, s) class BaseConfig(object): ''' Base class for storing configuration definitions. Subclass when creating your own definitons. ''' def __init__(self): self._section = None for name in self.iterkeys(): option = self.optionobj(name) option.setup(self, name) def __str__(self): out = [] out.append('[%s]' % self._section) for name, value in self.iteritems(): out.append('%s: %r' % (name, value)) return '\n'.join(out) def populate(self, parser, section, parent=None): '''Set option values from a INI file section. @param parser: ConfParser instance (or subclass) @param section: INI file section to read use. @param parent: Optional parent BaseConfig (or subclass) instance to use when doing option value inheritance. ''' self.cfg = parser self._section = section for name in self.iterkeys(): option = self.optionobj(name) value = None try: value = parser.get(section, name) except (NoSectionError, NoOptionError): # No matching option in this section, try inheriting if parent and option.inherit: value = getattr(parent, name) if value is not None: setattr(self, name, value) def optionobj(cls, name): '''Return the Option instance for the given name ''' obj = getattr(cls, name, None) if isinstance(obj, Option): return obj else: raise KeyError optionobj = classmethod(optionobj) def isoption(cls, name): '''Return True if the given name refers to a defined option ''' try: cls.optionobj(name) return True except KeyError: return False isoption = classmethod(isoption) def iterkeys(self): '''Yield the names of all defined options in the instance. ''' for name, item in self.iteritems(): yield name def iteritems(self): '''Yield (name, value) pairs for every option in the instance. The value returned is the parsed, validated option value. ''' # Use dir() so that we see inherited options too for name in dir(self): if self.isoption(name): yield (name, getattr(self, name)) def write(self, fileobj, section=None, always=()): '''Write out the configuration to a file-like object @param fileobj: File-like object to write to @param section: Section name to use. If not-specified the section name used during parsing will be used. @param always: A sequence of option names to always write out. Options not listed here will only be written out if they are at non-default values. Set to None to dump out all options. ''' # Write section heading if section is None: if self._section is None: raise ValueError("not populated, don't know section") section = self._section # Updated the ConfigParser with the changed values cfgOptions = self.cfg.options(section) for name,value in self.iteritems(): option = self.optionobj(name) if always is None or name in always or option.default != value or name in cfgOptions : self.cfg.set(section,name, option.tostring(value)) # write the updated ConfigParser to the fileobj. self.cfg.write(fileobj) def getConfigOption(self, option, default=None): warnings.warn('getConfigOption() will go away in a future version of Yum.\n' 'Please access option values as attributes or using getattr().', DeprecationWarning) if hasattr(self, option): return getattr(self, option) return default def setConfigOption(self, option, value): warnings.warn('setConfigOption() will go away in a future version of Yum.\n' 'Please set option values as attributes or using setattr().', DeprecationWarning) if hasattr(self, option): setattr(self, option, value) else: raise ConfigError, 'No such option %s' % option def read_config(config_file, BaseConfigDerived): confparser = ConfigParser() opts = BaseConfigDerived() if os.path.exists(config_file): try: confparser.read(config_file) except ParsingError, e: print >> sys.stderr, "Error reading config file: %s" % e sys.exit(1) opts.populate(confparser, 'main') return opts certmaster-0.25/certmaster/sub_process.py0000664000076400007640000012303711064771520020164 0ustar adrianadrian# subprocess - Subprocesses with accessible I/O streams # # For more information about this module, see PEP 324. # # This module should remain compatible with Python 2.2, see PEP 291. # # Copyright (c) 2003-2005 by Peter Astrand # # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/2.4/license for licensing details. r"""subprocess - Subprocesses with accessible I/O streams This module allows you to spawn processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several other, older modules and functions, like: os.system os.spawn* os.popen* popen2.* commands.* Information about how the subprocess module can be used to replace these modules and functions can be found below. Using the subprocess module =========================== This module defines one class called Popen: class Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): Arguments are: args should be a string, or a sequence of program arguments. The program to execute is normally the first item in the args sequence or string, but can be explicitly set by using the executable argument. On UNIX, with shell=False (default): In this case, the Popen class uses os.execvp() to execute the child program. args should normally be a sequence. A string will be treated as a sequence with the string as the only item (the program to execute). On UNIX, with shell=True: If args is a string, it specifies the command string to execute through the shell. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional shell arguments. On Windows: the Popen class uses CreateProcess() to execute the child program, which operates on strings. If args is a sequence, it will be converted to a string using the list2cmdline method. Please note that not all MS Windows applications interpret the command line the same way: The list2cmdline is designed for applications using the same rules as the MS C runtime. bufsize, if given, has the same meaning as the corresponding argument to the built-in open() function: 0 means unbuffered, 1 means line buffered, any other positive value means use a buffer of (approximately) that size. A negative bufsize means to use the system default, which usually means fully buffered. The default value for bufsize is 0 (unbuffered). stdin, stdout and stderr specify the executed programs' standard input, standard output and standard error file handles, respectively. Valid values are PIPE, an existing file descriptor (a positive integer), an existing file object, and None. PIPE indicates that a new pipe to the child should be created. With None, no redirection will occur; the child's file handles will be inherited from the parent. Additionally, stderr can be STDOUT, which indicates that the stderr data from the applications should be captured into the same file handle as for stdout. If preexec_fn is set to a callable object, this object will be called in the child process just before the child is executed. If close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. if shell is true, the specified command will be executed through the shell. If cwd is not None, the current directory will be changed to cwd before the child is executed. If env is not None, it defines the environment variables for the new process. If universal_newlines is true, the file objects stdout and stderr are opened as a text files, but lines may be terminated by any of '\n', the Unix end-of-line convention, '\r', the Macintosh convention or '\r\n', the Windows convention. All of these external representations are seen as '\n' by the Python program. Note: This feature is only available if Python is built with universal newline support (the default). Also, the newlines attribute of the file objects stdout, stdin and stderr are not updated by the communicate() method. The startupinfo and creationflags, if given, will be passed to the underlying CreateProcess() function. They can specify things such as appearance of the main window and priority for the new process. (Windows only) This module also defines two shortcut functions: call(*popenargs, **kwargs): Run command with arguments. Wait for command to complete, then return the returncode attribute. The arguments are the same as for the Popen constructor. Example: retcode = call(["ls", "-l"]) check_call(*popenargs, **kwargs): Run command with arguments. Wait for command to complete. If the exit code was zero then return, otherwise raise CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute. The arguments are the same as for the Popen constructor. Example: check_call(["ls", "-l"]) Exceptions ---------- Exceptions raised in the child process, before the new program has started to execute, will be re-raised in the parent. Additionally, the exception object will have one extra attribute called 'child_traceback', which is a string containing traceback information from the childs point of view. The most common exception raised is OSError. This occurs, for example, when trying to execute a non-existent file. Applications should prepare for OSErrors. A ValueError will be raised if Popen is called with invalid arguments. check_call() will raise CalledProcessError, if the called process returns a non-zero return code. Security -------- Unlike some other popen functions, this implementation will never call /bin/sh implicitly. This means that all characters, including shell metacharacters, can safely be passed to child processes. Popen objects ============= Instances of the Popen class have the following methods: poll() Check if child process has terminated. Returns returncode attribute. wait() Wait for child process to terminate. Returns returncode attribute. communicate(input=None) Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional stdin argument should be a string to be sent to the child process, or None, if no data should be sent to the child. communicate() returns a tuple (stdout, stderr). Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited. The following attributes are also available: stdin If the stdin argument is PIPE, this attribute is a file object that provides input to the child process. Otherwise, it is None. stdout If the stdout argument is PIPE, this attribute is a file object that provides output from the child process. Otherwise, it is None. stderr If the stderr argument is PIPE, this attribute is file object that provides error output from the child process. Otherwise, it is None. pid The process ID of the child process. returncode The child return code. A None value indicates that the process hasn't terminated yet. A negative value -N indicates that the child was terminated by signal N (UNIX only). Replacing older functions with the subprocess module ==================================================== In this section, "a ==> b" means that b can be used as a replacement for a. Note: All functions in this section fail (more or less) silently if the executed program cannot be found; this module raises an OSError exception. In the following examples, we assume that the subprocess module is imported with "from subprocess import *". Replacing /bin/sh shell backquote --------------------------------- output=`mycmd myarg` ==> output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] Replacing shell pipe line ------------------------- output=`dmesg | grep hda` ==> p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) output = p2.communicate()[0] Replacing os.system() --------------------- sts = os.system("mycmd" + " myarg") ==> p = Popen("mycmd" + " myarg", shell=True) pid, sts = os.waitpid(p.pid, 0) Note: * Calling the program through the shell is usually not required. * It's easier to look at the returncode attribute than the exitstatus. A more real-world example would look like this: try: retcode = call("mycmd" + " myarg", shell=True) if retcode < 0: print >>sys.stderr, "Child was terminated by signal", -retcode else: print >>sys.stderr, "Child returned", retcode except OSError, e: print >>sys.stderr, "Execution failed:", e Replacing os.spawn* ------------------- P_NOWAIT example: pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") ==> pid = Popen(["/bin/mycmd", "myarg"]).pid P_WAIT example: retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") ==> retcode = call(["/bin/mycmd", "myarg"]) Vector example: os.spawnvp(os.P_NOWAIT, path, args) ==> Popen([path] + args[1:]) Environment example: os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) ==> Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) Replacing os.popen* ------------------- pipe = os.popen(cmd, mode='r', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout pipe = os.popen(cmd, mode='w', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin (child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdin, child_stdout) = (p.stdin, p.stdout) (child_stdin, child_stdout, child_stderr) = os.popen3(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) (child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p.stderr) (child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) Replacing popen2.* ------------------ Note: If the cmd argument to popen2 functions is a string, the command is executed through /bin/sh. If it is a list, the command is directly executed. (child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) ==> p = Popen(["somestring"], shell=True, bufsize=bufsize stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin) (child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) ==> p = Popen(["mycmd", "myarg"], bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin) The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, except that: * subprocess.Popen raises an exception if the execution fails * the capturestderr argument is replaced with the stderr argument. * stdin=PIPE and stdout=PIPE must be specified. * popen2 closes all filedescriptors by default, but you have to specify close_fds=True with subprocess.Popen. """ import sys mswindows = (sys.platform == "win32") import os import types import traceback # Exception classes used by this module. class CalledProcessError(Exception): """This exception is raised when a process run by check_call() returns a non-zero exit status. The exit status will be stored in the returncode attribute.""" def __init__(self, returncode, cmd): self.returncode = returncode self.cmd = cmd def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) if mswindows: import threading import msvcrt if 0: # <-- change this to use pywin32 instead of the _subprocess driver import pywintypes from win32api import GetStdHandle, STD_INPUT_HANDLE, \ STD_OUTPUT_HANDLE, STD_ERROR_HANDLE from win32api import GetCurrentProcess, DuplicateHandle, \ GetModuleFileName, GetVersion from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE from win32pipe import CreatePipe from win32process import CreateProcess, STARTUPINFO, \ GetExitCodeProcess, STARTF_USESTDHANDLES, \ STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 else: from _subprocess import * class STARTUPINFO: dwFlags = 0 hStdInput = None hStdOutput = None hStdError = None wShowWindow = 0 class pywintypes: error = IOError else: import select import errno import fcntl import pickle __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 # True/False does not exist on 2.2.0 try: False except NameError: False = 0 True = 1 _active = [] def _cleanup(): for inst in _active[:]: if inst.poll(_deadstate=sys.maxint) >= 0: try: _active.remove(inst) except ValueError: # This can happen if two threads create a new Popen instance. # It's harmless that it was already removed, so ignore. pass PIPE = -1 STDOUT = -2 def call(*popenargs, **kwargs): """Run command with arguments. Wait for command to complete, then return the returncode attribute. The arguments are the same as for the Popen constructor. Example: retcode = call(["ls", "-l"]) """ return Popen(*popenargs, **kwargs).wait() def check_call(*popenargs, **kwargs): """Run command with arguments. Wait for command to complete. If the exit code was zero then return, otherwise raise CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute. The arguments are the same as for the Popen constructor. Example: check_call(["ls", "-l"]) """ retcode = call(*popenargs, **kwargs) cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] if retcode: raise CalledProcessError(retcode, cmd) return retcode def list2cmdline(seq): """ Translate a sequence of arguments into a command line string, using the same rules as the MS C runtime: 1) Arguments are delimited by white space, which is either a space or a tab. 2) A string surrounded by double quotation marks is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument. 3) A double quotation mark preceded by a backslash is interpreted as a literal double quotation mark. 4) Backslashes are interpreted literally, unless they immediately precede a double quotation mark. 5) If backslashes immediately precede a double quotation mark, every pair of backslashes is interpreted as a literal backslash. If the number of backslashes is odd, the last backslash escapes the next double quotation mark as described in rule 3. """ # See # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp result = [] needquote = False for arg in seq: bs_buf = [] # Add a space to separate this argument from the others if result: result.append(' ') needquote = (" " in arg) or ("\t" in arg) if needquote: result.append('"') for c in arg: if c == '\\': # Don't know if we need to double yet. bs_buf.append(c) elif c == '"': # Double backspaces. result.append('\\' * len(bs_buf)*2) bs_buf = [] result.append('\\"') else: # Normal char if bs_buf: result.extend(bs_buf) bs_buf = [] result.append(c) # Add remaining backspaces, if any. if bs_buf: result.extend(bs_buf) if needquote: result.extend(bs_buf) result.append('"') return ''.join(result) class Popen(object): def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): """Create new Popen instance.""" _cleanup() self._child_created = False if not isinstance(bufsize, (int, long)): raise TypeError("bufsize must be an integer") if mswindows: if preexec_fn is not None: raise ValueError("preexec_fn is not supported on Windows " "platforms") if close_fds: raise ValueError("close_fds is not supported on Windows " "platforms") else: # POSIX if startupinfo is not None: raise ValueError("startupinfo is only supported on Windows " "platforms") if creationflags != 0: raise ValueError("creationflags is only supported on Windows " "platforms") self.stdin = None self.stdout = None self.stderr = None self.pid = None self.returncode = None self.universal_newlines = universal_newlines # Input and output objects. The general principle is like # this: # # Parent Child # ------ ----- # p2cwrite ---stdin---> p2cread # c2pread <--stdout--- c2pwrite # errread <--stderr--- errwrite # # On POSIX, the child objects are file descriptors. On # Windows, these are Windows file handles. The parent objects # are file descriptors on both platforms. The parent objects # are None when not using PIPEs. The child objects are None # when not redirecting. (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) = self._get_handles(stdin, stdout, stderr) self._execute_child(args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) if p2cwrite: self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) if c2pread: if universal_newlines: self.stdout = os.fdopen(c2pread, 'rU', bufsize) else: self.stdout = os.fdopen(c2pread, 'rb', bufsize) if errread: if universal_newlines: self.stderr = os.fdopen(errread, 'rU', bufsize) else: self.stderr = os.fdopen(errread, 'rb', bufsize) def _translate_newlines(self, data): data = data.replace("\r\n", "\n") data = data.replace("\r", "\n") return data def __del__(self): if not self._child_created: # We didn't get to successfully create a child process. return # In case the child hasn't been waited on, check if it's done. self.poll(_deadstate=sys.maxint) if self.returncode is None and _active is not None: # Child is still running, keep us alive until we can wait on it. _active.append(self) def communicate(self, input=None): """Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child. communicate() returns a tuple (stdout, stderr).""" # Optimization: If we are only using one pipe, or no pipe at # all, using select() or threads is unnecessary. if [self.stdin, self.stdout, self.stderr].count(None) >= 2: stdout = None stderr = None if self.stdin: if input: self.stdin.write(input) self.stdin.close() elif self.stdout: stdout = self.stdout.read() elif self.stderr: stderr = self.stderr.read() self.wait() return (stdout, stderr) return self._communicate(input) if mswindows: # # Windows methods # def _get_handles(self, stdin, stdout, stderr): """Construct and return tupel with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ if stdin is None and stdout is None and stderr is None: return (None, None, None, None, None, None) p2cread, p2cwrite = None, None c2pread, c2pwrite = None, None errread, errwrite = None, None if stdin is None: p2cread = GetStdHandle(STD_INPUT_HANDLE) elif stdin == PIPE: p2cread, p2cwrite = CreatePipe(None, 0) # Detach and turn into fd p2cwrite = p2cwrite.Detach() p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) elif isinstance(stdin, int): p2cread = msvcrt.get_osfhandle(stdin) else: # Assuming file-like object p2cread = msvcrt.get_osfhandle(stdin.fileno()) p2cread = self._make_inheritable(p2cread) if stdout is None: c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) elif stdout == PIPE: c2pread, c2pwrite = CreatePipe(None, 0) # Detach and turn into fd c2pread = c2pread.Detach() c2pread = msvcrt.open_osfhandle(c2pread, 0) elif isinstance(stdout, int): c2pwrite = msvcrt.get_osfhandle(stdout) else: # Assuming file-like object c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) c2pwrite = self._make_inheritable(c2pwrite) if stderr is None: errwrite = GetStdHandle(STD_ERROR_HANDLE) elif stderr == PIPE: errread, errwrite = CreatePipe(None, 0) # Detach and turn into fd errread = errread.Detach() errread = msvcrt.open_osfhandle(errread, 0) elif stderr == STDOUT: errwrite = c2pwrite elif isinstance(stderr, int): errwrite = msvcrt.get_osfhandle(stderr) else: # Assuming file-like object errwrite = msvcrt.get_osfhandle(stderr.fileno()) errwrite = self._make_inheritable(errwrite) return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) def _make_inheritable(self, handle): """Return a duplicate of handle, which is inheritable""" return DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), 0, 1, DUPLICATE_SAME_ACCESS) def _find_w9xpopen(self): """Find and return absolut path to w9xpopen.exe""" w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), "w9xpopen.exe") if not os.path.exists(w9xpopen): # Eeek - file-not-found - possibly an embedding # situation - see if we can locate it in sys.exec_prefix w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), "w9xpopen.exe") if not os.path.exists(w9xpopen): raise RuntimeError("Cannot locate w9xpopen.exe, which is " "needed for Popen to work with your " "shell or platform.") return w9xpopen def _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): """Execute program (MS Windows version)""" if not isinstance(args, types.StringTypes): args = list2cmdline(args) # Process startup details if startupinfo is None: startupinfo = STARTUPINFO() if None not in (p2cread, c2pwrite, errwrite): startupinfo.dwFlags |= STARTF_USESTDHANDLES startupinfo.hStdInput = p2cread startupinfo.hStdOutput = c2pwrite startupinfo.hStdError = errwrite if shell: startupinfo.dwFlags |= STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE comspec = os.environ.get("COMSPEC", "cmd.exe") args = comspec + " /c " + args if (GetVersion() >= 0x80000000L or os.path.basename(comspec).lower() == "command.com"): # Win9x, or using command.com on NT. We need to # use the w9xpopen intermediate program. For more # information, see KB Q150956 # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) w9xpopen = self._find_w9xpopen() args = '"%s" %s' % (w9xpopen, args) # Not passing CREATE_NEW_CONSOLE has been known to # cause random failures on win9x. Specifically a # dialog: "Your program accessed mem currently in # use at xxx" and a hopeful warning about the # stability of your system. Cost is Ctrl+C wont # kill children. creationflags |= CREATE_NEW_CONSOLE # Start the process try: hp, ht, pid, tid = CreateProcess(executable, args, # no special security None, None, # must inherit handles to pass std # handles 1, creationflags, env, cwd, startupinfo) except pywintypes.error, e: # Translate pywintypes.error to WindowsError, which is # a subclass of OSError. FIXME: We should really # translate errno using _sys_errlist (or simliar), but # how can this be done from Python? raise WindowsError(*e.args) # Retain the process handle, but close the thread handle self._child_created = True self._handle = hp self.pid = pid ht.Close() # Child is launched. Close the parent's copy of those pipe # handles that only the child should have open. You need # to make sure that no handles to the write end of the # output pipe are maintained in this process or else the # pipe will not close when the child process exits and the # ReadFile will hang. if p2cread is not None: p2cread.Close() if c2pwrite is not None: c2pwrite.Close() if errwrite is not None: errwrite.Close() def poll(self, _deadstate=None): """Check if child process has terminated. Returns returncode attribute.""" if self.returncode is None: if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: self.returncode = GetExitCodeProcess(self._handle) return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" if self.returncode is None: obj = WaitForSingleObject(self._handle, INFINITE) self.returncode = GetExitCodeProcess(self._handle) return self.returncode def _readerthread(self, fh, buffer): buffer.append(fh.read()) def _communicate(self, input): stdout = None # Return stderr = None # Return if self.stdout: stdout = [] stdout_thread = threading.Thread(target=self._readerthread, args=(self.stdout, stdout)) stdout_thread.setDaemon(True) stdout_thread.start() if self.stderr: stderr = [] stderr_thread = threading.Thread(target=self._readerthread, args=(self.stderr, stderr)) stderr_thread.setDaemon(True) stderr_thread.start() if self.stdin: if input is not None: self.stdin.write(input) self.stdin.close() if self.stdout: stdout_thread.join() if self.stderr: stderr_thread.join() # All data exchanged. Translate lists into strings. if stdout is not None: stdout = stdout[0] if stderr is not None: stderr = stderr[0] # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) else: # # POSIX methods # def _get_handles(self, stdin, stdout, stderr): """Construct and return tupel with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ p2cread, p2cwrite = None, None c2pread, c2pwrite = None, None errread, errwrite = None, None if stdin is None: pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() elif isinstance(stdin, int): p2cread = stdin else: # Assuming file-like object p2cread = stdin.fileno() if stdout is None: pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() elif isinstance(stdout, int): c2pwrite = stdout else: # Assuming file-like object c2pwrite = stdout.fileno() if stderr is None: pass elif stderr == PIPE: errread, errwrite = os.pipe() elif stderr == STDOUT: errwrite = c2pwrite elif isinstance(stderr, int): errwrite = stderr else: # Assuming file-like object errwrite = stderr.fileno() return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) def _set_cloexec_flag(self, fd): try: cloexec_flag = fcntl.FD_CLOEXEC except AttributeError: cloexec_flag = 1 old = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) def _close_fds(self, but): for i in xrange(3, MAXFD): if i == but: continue try: os.close(i) except: pass def _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): """Execute program (POSIX version)""" if isinstance(args, types.StringTypes): args = [args] if shell: args = ["/bin/sh", "-c"] + args if executable is None: executable = args[0] # For transferring possible exec failure from child to parent # The first char specifies the exception type: 0 means # OSError, 1 means some other error. errpipe_read, errpipe_write = os.pipe() self._set_cloexec_flag(errpipe_write) self.pid = os.fork() self._child_created = True if self.pid == 0: # Child try: # Close parent's pipe ends if p2cwrite: os.close(p2cwrite) if c2pread: os.close(c2pread) if errread: os.close(errread) os.close(errpipe_read) # Dup fds for child if p2cread: os.dup2(p2cread, 0) if c2pwrite: os.dup2(c2pwrite, 1) if errwrite: os.dup2(errwrite, 2) # Close pipe fds. Make sure we don't close the same # fd more than once, or standard fds. if p2cread: os.close(p2cread) if c2pwrite and c2pwrite not in (p2cread,): os.close(c2pwrite) if errwrite and errwrite not in (p2cread, c2pwrite): os.close(errwrite) # Close all other fds, if asked for if close_fds: self._close_fds(but=errpipe_write) if cwd is not None: os.chdir(cwd) if preexec_fn: apply(preexec_fn) if env is None: os.execvp(executable, args) else: os.execvpe(executable, args, env) except: exc_type, exc_value, tb = sys.exc_info() # Save the traceback and attach it to the exception object exc_lines = traceback.format_exception(exc_type, exc_value, tb) exc_value.child_traceback = ''.join(exc_lines) os.write(errpipe_write, pickle.dumps(exc_value)) # This exitcode won't be reported to applications, so it # really doesn't matter what we return. os._exit(255) # Parent os.close(errpipe_write) if p2cread and p2cwrite: os.close(p2cread) if c2pwrite and c2pread: os.close(c2pwrite) if errwrite and errread: os.close(errwrite) # Wait for exec to fail or succeed; possibly raising exception data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB os.close(errpipe_read) if data != "": os.waitpid(self.pid, 0) child_exception = pickle.loads(data) raise child_exception def _handle_exitstatus(self, sts): if os.WIFSIGNALED(sts): self.returncode = -os.WTERMSIG(sts) elif os.WIFEXITED(sts): self.returncode = os.WEXITSTATUS(sts) else: # Should never happen raise RuntimeError("Unknown child exit status!") def poll(self, _deadstate=None): """Check if child process has terminated. Returns returncode attribute.""" if self.returncode is None: try: pid, sts = os.waitpid(self.pid, os.WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) except os.error: if _deadstate is not None: self.returncode = _deadstate return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" if self.returncode is None: pid, sts = os.waitpid(self.pid, 0) self._handle_exitstatus(sts) return self.returncode def _communicate(self, input): read_set = [] write_set = [] stdout = None # Return stderr = None # Return if self.stdin: # Flush stdio buffer. This might block, if the user has # been writing to .stdin in an uncontrolled fashion. self.stdin.flush() if input: write_set.append(self.stdin) else: self.stdin.close() if self.stdout: read_set.append(self.stdout) stdout = [] if self.stderr: read_set.append(self.stderr) stderr = [] while read_set or write_set: rlist, wlist, xlist = select.select(read_set, write_set, []) if self.stdin in wlist: # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 bytes_written = os.write(self.stdin.fileno(), input[:512]) input = input[bytes_written:] if not input: self.stdin.close() write_set.remove(self.stdin) if self.stdout in rlist: data = os.read(self.stdout.fileno(), 1024) if data == "": self.stdout.close() read_set.remove(self.stdout) stdout.append(data) if self.stderr in rlist: data = os.read(self.stderr.fileno(), 1024) if data == "": self.stderr.close() read_set.remove(self.stderr) stderr.append(data) # All data exchanged. Translate lists into strings. if stdout is not None: stdout = ''.join(stdout) if stderr is not None: stderr = ''.join(stderr) # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) def _demo_posix(): # # Example 1: Simple redirection: Get process list # plist = Popen(["ps"], stdout=PIPE).communicate()[0] print "Process list:" print plist # # Example 2: Change uid before executing child # if os.getuid() == 0: p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) p.wait() # # Example 3: Connecting several subprocesses # print "Looking for 'hda'..." p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) print repr(p2.communicate()[0]) # # Example 4: Catch execution error # print print "Trying a weird file..." try: print Popen(["/this/path/does/not/exist"]).communicate() except OSError, e: if e.errno == errno.ENOENT: print "The file didn't exist. I thought so..." print "Child traceback:" print e.child_traceback else: print "Error", e.errno else: print >>sys.stderr, "Gosh. No error." def _demo_windows(): # # Example 1: Connecting several subprocesses # print "Looking for 'PROMPT' in set output..." p1 = Popen("set", stdout=PIPE, shell=True) p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) print repr(p2.communicate()[0]) # # Example 2: Simple execution of program # print "Executing calc..." p = Popen("calc") p.wait() if __name__ == "__main__": if mswindows: _demo_windows() else: _demo_posix() certmaster-0.25/certmaster/commonconfig.py0000664000076400007640000000224311175632103020302 0ustar adrianadrian""" Default configuration values for certmaster items when not specified in config file. Copyright 2008, 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. """ from config import BaseConfig, BoolOption, IntOption, Option class CMConfig(BaseConfig): log_level = Option('INFO') listen_addr = Option('') listen_port = IntOption(51235) cadir = Option('/etc/pki/certmaster/ca') cert_dir = Option('/etc/pki/certmaster') certroot = Option('/var/lib/certmaster/certmaster/certs') csrroot = Option('/var/lib/certmaster/certmaster/csrs') cert_extension = Option('cert') autosign = BoolOption(False) sync_certs = BoolOption(False) peering = BoolOption(True) peerroot = Option('/var/lib/certmaster/peers') class MinionConfig(BaseConfig): log_level = Option('INFO') certmaster = Option('certmaster') certmaster_port = IntOption(51235) cert_dir = Option('/etc/pki/certmaster') certmaster-0.25/certmaster/SSLCommon.py0000664000076400007640000001006211064771520017440 0ustar adrianadrian# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Copyright 2005 Dan Williams and Red Hat, Inc. import os, sys from OpenSSL import SSL import SSLConnection import httplib import socket import SocketServer def our_verify(connection, x509, errNum, errDepth, preverifyOK): # print "Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum, errDepth, preverifyOK) # preverifyOK should tell us whether or not the client's certificate # correctly authenticates against the CA chain return preverifyOK def CreateSSLContext(pkey, cert, ca_cert): for f in pkey, cert, ca_cert: if f and not os.access(f, os.R_OK): print "%s does not exist or is not readable." % f os._exit(1) ctx = SSL.Context(SSL.SSLv3_METHOD) # SSLv3 only ctx.use_certificate_file(cert) ctx.use_privatekey_file(pkey) ctx.load_client_ca(ca_cert) ctx.load_verify_locations(ca_cert) verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT ctx.set_verify(verify, our_verify) ctx.set_verify_depth(10) ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_TLSv1) return ctx class BaseServer(SocketServer.TCPServer): allow_reuse_address = 1 def __init__(self, server_addr, req_handler): self._quit = False self.allow_reuse_address = 1 SocketServer.TCPServer.__init__(self, server_addr, req_handler) def stop(self): self._quit = True def serve_forever(self): while not self._quit: self.handle_request() self.server_close() class BaseSSLServer(BaseServer): """ SSL-enabled variant """ def __init__(self, server_address, req_handler, pkey, cert, ca_cert, timeout=None): self._timeout = timeout self.ssl_ctx = CreateSSLContext(pkey, cert, ca_cert) BaseServer.__init__(self, server_address, req_handler) sock = socket.socket(self.address_family, self.socket_type) con = SSL.Connection(self.ssl_ctx, sock) self.socket = SSLConnection.SSLConnection(con) if sys.version_info[:3] >= (2, 3, 0): self.socket.settimeout(self._timeout) self.server_bind() self.server_activate() host, port = self.socket.getsockname()[:2] self.server_name = socket.getfqdn(host) self.server_port = port class HTTPSConnection(httplib.HTTPConnection): "This class allows communication via SSL." response_class = httplib.HTTPResponse def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None): httplib.HTTPConnection.__init__(self, host, port, strict) self.ssl_ctx = ssl_context self._timeout = timeout def connect(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) con = SSL.Connection(self.ssl_ctx, sock) self.sock = SSLConnection.SSLConnection(con) if sys.version_info[:3] >= (2, 3, 0): self.sock.settimeout(self._timeout) self.sock.connect((self.host, self.port)) class HTTPS(httplib.HTTP): """Compatibility with 1.5 httplib interface Python 1.5.2 did not have an HTTPS class, but it defined an interface for sending http requests that is also useful for https. """ _connection_class = HTTPSConnection def __init__(self, host='', port=None, ssl_context=None, strict=None, timeout=None): self._setup(self._connection_class(host, port, ssl_context, strict, timeout)) certmaster-0.25/certmaster/logger.py0000664000076400007640000000522411175632103017105 0ustar adrianadrian## Certmaster ## ## 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. ## ## import logging from config import read_config from commonconfig import CMConfig # from the comments in http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531 class Singleton(object): def __new__(type, *args, **kwargs): if not '_the_instance' in type.__dict__: type._the_instance = object.__new__(type, *args, **kwargs) return type._the_instance # logging is weird, we don't want to setup multiple handlers # so make sure we do that mess only once class Logger(Singleton): _no_handlers = True def __init__(self, logfilepath ="/var/log/certmaster/certmaster.log"): config_file = '/etc/certmaster/minion.conf' self.config = read_config(config_file, CMConfig) self.loglevel = logging._levelNames[self.config.log_level] self._setup_logging() if self._no_handlers: self._setup_handlers(logfilepath=logfilepath) def _setup_logging(self): self.logger = logging.getLogger("certmaster") def _setup_handlers(self, logfilepath="/var/log/certmaster/certmaster.log"): handler = logging.FileHandler(logfilepath, "a") self.logger.setLevel(self.loglevel) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) self.logger.addHandler(handler) self._no_handlers = False class AuditLogger(Singleton): _no_handlers = True def __init__(self, logfilepath = "/var/log/certmaster/audit.log"): self.logfilepath = logfilepath self.loglevel = logging.INFO self._setup_logging() if self._no_handlers: self._setup_handlers(logfilepath=logfilepath) def log_call(self, ip, method, params): # square away a good parseable format at some point -akl self.logger.info("%s called %s with %s" % (ip, method, params)) def _setup_logging(self): self.logger = logging.getLogger("certmaster-audit") def _setup_handlers(self, logfilepath="/var/log/certmaster/audit.log"): handler = logging.FileHandler(logfilepath, "a") self.logger.setLevel(self.loglevel) formatter = logging.Formatter("%(asctime)s - %(message)s") handler.setFormatter(formatter) self.logger.addHandler(handler) self._no_handlers = False certmaster-0.25/certmaster/certmaster.py0000664000076400007640000003105111175632103017774 0ustar adrianadrian# 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 string import sys import traceback import os import os.path from OpenSSL import crypto import sha import glob import socket import exceptions import certs import codes import utils import logger from config import read_config from commonconfig import CMConfig CERTMASTER_LISTEN_PORT = 51235 CERTMASTER_CONFIG = "/etc/certmaster/certmaster.conf" class CertMaster(object): def __init__(self, conf_file=CERTMASTER_CONFIG): self.cfg = read_config(conf_file, CMConfig) 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() 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: self.logger.info("Unhandled method call for method: %s " % method) raise codes.InvalidMethodException def _sanitize_cn(self, commonname): commonname = commonname.replace('/', '') commonname = commonname.replace('\\', '') return commonname def wait_for_cert(self, csrbuf, with_triggers=True): """ 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) if with_triggers: self._run_triggers(requesting_host, '/var/lib/certmaster/triggers/request/pre/*') 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) # check for old csr on disk # if we have it - compare the two - if they are not the same - raise a fault self.logger.debug("csrfile: %s certfile: %s" % (csrfile, certfile)) 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: self.logger.info("A cert for %s already exists and does not match the requesting cert" % (requesting_host)) # 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) if with_triggers: self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/request/post/*') 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) self.logger.info("cert for %s was autosigned" % (requesting_host)) if with_triggers: self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*') 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 self.logger.info("cert for %s created and ready to be signed" % (requesting_host)) if with_triggers: self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*') 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, with_triggers=True): """ 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 if with_triggers: self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/pre/*') for fn in csrs + certs: print 'Cleaning out %s for host matching %s' % (fn, hn) self.logger.info('Cleaning out %s for host matching %s' % (fn, hn)) os.unlink(fn) if with_triggers: self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/post/*') def sign_this_csr(self, csr, with_triggers=True): """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: self.logger.info("Unable to sign %s: Bad CSR" % (csr)) 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) if with_triggers: self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/pre/*') certfile = '%s/%s.cert' % (self.cfg.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) destfo = open(certfile, 'w') destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, thiscert)) destfo.close() del destfo self.logger.info("csr %s signed" % (certfile)) if with_triggers: self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/post/*') if csr_unlink_file and os.path.exists(csr_unlink_file): os.unlink(csr_unlink_file) return certfile # return a list of already signed certs def get_signed_certs(self, hostglobs=None): certglob = "%s/*.cert" % (self.cfg.certroot) certs = [] globs = "*" if hostglobs: globs = hostglobs for hostglob in globs: certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob) certs = certs + glob.glob(certglob) signed_certs = [] for cert in certs: # just want the hostname, so strip off path and ext signed_certs.append(os.path.basename(cert).split(".cert", 1)[0]) return signed_certs def get_peer_certs(self): """ Returns a list of all certs under peerroot """ myglob = os.path.join(self.cfg.peerroot, '*.%s' % self.cfg.cert_extension) 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) certfiles = [] globs = "*" if hostglobs: globs = hostglobs for hostglob in globs: certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob) certfiles = certfiles + glob.glob(certglob) cert_hashes = [] for certfile in certfiles: cert = certs.retrieve_cert_from_file(certfile) cert_hashes.append("%s-%s" % (cert.get_subject().CN, cert.subject_name_hash())) return cert_hashes def _run_triggers(self, ref, globber): return utils.run_triggers(ref, globber) class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self, addr): self.allow_reuse_address = True SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, addr) def serve(xmlrpcinstance): """ Code for starting the XMLRPC service. """ config = read_config(CERTMASTER_CONFIG, CMConfig) listen_addr = config.listen_addr listen_port = config.listen_port if listen_port == '': listen_port = CERTMASTER_LISTEN_PORT server = CertmasterXMLRPCServer((listen_addr,listen_port)) server.logRequests = 0 # don't print stuff to console server.register_instance(xmlrpcinstance) xmlrpcinstance.logger.info("certmaster started") xmlrpcinstance.audit_logger.logger.info("certmaster started") server.serve_forever() def excepthook(exctype, value, tracebackobj): exctype_blurb = "Exception occured: %s" % exctype excvalue_blurb = "Exception value: %s" % value exctb_blurb = "Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tracebackobj))) print exctype_blurb print excvalue_blurb print exctb_blurb log = logger.Logger().logger log.info(exctype_blurb) log.info(excvalue_blurb) log.info(exctb_blurb) def main(argv): sys.excepthook = excepthook cm = CertMaster('/etc/certmaster/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) certmaster-0.25/certmaster/certs.py0000664000076400007640000001126211175632107016751 0ustar adrianadrian# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright (c) 2007 Red Hat, inc #- Written by Seth Vidal skvidal @ fedoraproject.org from OpenSSL import crypto import socket import os import utils def_country = 'UN' def_state = 'FC' def_local = 'Certmaster-town' def_org = 'certmaster' def_ou = 'slave-key' def make_keypair(dest=None): pkey = crypto.PKey() pkey.generate_key(crypto.TYPE_RSA, 2048) if dest: destfd = os.open(dest, os.O_RDWR|os.O_CREAT, 0600) os.write(destfd, (crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))) os.close(destfd) return pkey def make_csr(pkey, dest=None, cn=None, hostname=None): req = crypto.X509Req() req.get_subject() subj = req.get_subject() subj.C = def_country subj.ST = def_state subj.L = def_local subj.O = def_org subj.OU = def_ou if cn: subj.CN = cn elif hostname: subj.CN = hostname else: subj.CN = utils.gethostname() subj.emailAddress = 'root@%s' % subj.CN req.set_pubkey(pkey) req.sign(pkey, 'md5') if dest: destfd = os.open(dest, os.O_RDWR|os.O_CREAT, 0644) os.write(destfd, crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) os.close(destfd) return req def retrieve_key_from_file(keyfile): fo = open(keyfile, 'r') buf = fo.read() keypair = crypto.load_privatekey(crypto.FILETYPE_PEM, buf) return keypair def retrieve_csr_from_file(csrfile): fo = open(csrfile, 'r') buf = fo.read() csrreq = crypto.load_certificate_request(crypto.FILETYPE_PEM, buf) return csrreq def retrieve_cert_from_file(certfile): fo = open(certfile, 'r') buf = fo.read() cert = crypto.load_certificate(crypto.FILETYPE_PEM, buf) return cert def create_ca(CN="Certmaster Certificate Authority", ca_key_file=None, ca_cert_file=None): cakey = make_keypair(dest=ca_key_file) careq = make_csr(cakey, cn=CN) cacert = crypto.X509() cacert.set_serial_number(0) cacert.gmtime_adj_notBefore(0) cacert.gmtime_adj_notAfter(60*60*24*365*10) # 10 yrs - hard to beat this kind of cert! cacert.set_issuer(careq.get_subject()) cacert.set_subject(careq.get_subject()) cacert.set_pubkey(careq.get_pubkey()) cacert.sign(cakey, 'md5') if ca_cert_file: destfo = open(ca_cert_file, 'w') destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cacert)) destfo.close() def _get_serial_number(cadir): serial = '%s/serial.txt' % cadir i = 1 if os.path.exists(serial): f = open(serial, 'r').read() f = f.replace('\n','') try: i = int(f) i+=1 except ValueError, e: i = 1 _set_serial_number(cadir, i) return i def _set_serial_number(cadir, last): serial = '%s/serial.txt' % cadir f = open(serial, 'w') f.write(str(last) + '\n') f.close() def create_slave_certificate(csr, cakey, cacert, cadir, slave_cert_file=None): cert = crypto.X509() cert.set_serial_number(_get_serial_number(cadir)) cert.gmtime_adj_notBefore(0) cert.gmtime_adj_notAfter(60*60*24*365*10) # 10 yrs - hard to beat this kind of cert! cert.set_issuer(cacert.get_subject()) cert.set_subject(csr.get_subject()) cert.set_pubkey(csr.get_pubkey()) cert.sign(cakey, 'md5') if slave_cert_file: destfo = open(slave_cert_file, 'w') destfo.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) destfo.close() return cert def check_cert_key_match(cert, key): if not isinstance(cert, crypto.X509Type): cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) if not isinstance(key, crypto.PKeyType): key = crypto.load_privatekey(crypto.FILETYPE_PEM, key) from OpenSSL import SSL context = SSL.Context(SSL.SSLv3_METHOD) try: context.use_certificate(cert) context.use_privatekey(key) return True except: return False certmaster-0.25/LICENSE0000664000076400007640000004310411064771520014113 0ustar adrianadrian GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. certmaster-0.25/setup.py0000664000076400007640000000546211213765563014633 0ustar adrianadrian#!/usr/bin/python from distutils.core import setup #from setuptools import setup,find_packages NAME = "certmaster" VERSION = "0.25" SHORT_DESC = "%s remote configuration and management api" % NAME LONG_DESC = """ A small pluggable xml-rpc daemon used by %s to implement various web services hooks """ % NAME if __name__ == "__main__": manpath = "share/man/man1/" etcpath = "/etc/%s" % NAME initpath = "/etc/init.d/" logpath = "/var/log/%s/" % NAME certdir = "/var/lib/%s/" % NAME certmaster_cert_dir = "/var/lib/%s/%s" % (NAME,NAME) certmaster_cert_certs_dir = "/var/lib/%s/%s/certs" % (NAME, NAME) certmaster_cert_csrs_dir = "/var/lib/%s/%s/csrs" % (NAME, NAME) trigpath = "/var/lib/%s/triggers/"% NAME pkipath = "/etc/pki/%s" % NAME rotpath = "/etc/logrotate.d" aclpath = "%s/minion-acl.d" % etcpath setup( name="%s" % NAME, version = VERSION, author = "Lots", author_email = "func-list@redhat.com", url = "https://fedorahosted.org/certmaster/", license = "GPL", scripts = [ "scripts/certmaster", "scripts/certmaster-ca", "scripts/certmaster-request", "scripts/certmaster-sync", ], # package_data = { '' : ['*.*'] }, package_dir = {"%s" % NAME: "%s" % NAME }, packages = ["%s" % NAME, ], data_files = [(initpath, ["init-scripts/certmaster"]), (etcpath, ["etc/minion.conf"]), (etcpath, ["etc/certmaster.conf"]), (manpath, ["docs/certmaster.1.gz"]), (manpath, ["docs/certmaster-request.1.gz"]), (manpath, ["docs/certmaster-ca.1.gz"]), (manpath, ["docs/certmaster-sync.1.gz"]), (rotpath, ['etc/certmaster_rotate']), (logpath, []), (certdir, []), (certmaster_cert_dir, []), (certmaster_cert_certs_dir, []), (certmaster_cert_csrs_dir, []), (etcpath, []), (pkipath, []), (aclpath, []), ("%s/peers" % certdir, []), ("%s/sign/pre/" % trigpath, []), ("%s/sign/post/" % trigpath, []), ("%s/remove/pre/" % trigpath, []), ("%s/remove/post/" % trigpath, []), ("%s/request/pre/" % trigpath, []), ("%s/request/post/" % trigpath, []), ], description = SHORT_DESC, long_description = LONG_DESC ) certmaster-0.25/docs/0000775000076400007640000000000011213774723014040 5ustar adrianadriancertmaster-0.25/docs/certmaster-ca.pod0000664000076400007640000000173711175632103017276 0ustar adrianadrian=head1 NAME certmaster-ca -- signs certificate requests gathered by certmaster. =head1 SYNOPSIS certmaster-ca --list certmaster-ca --sign machine.example.org =head1 DESCRIPTION "certmaster-ca --list" The list command prints all certificates that have been requested from certmaster by a remote application (such as funcd or certmaster-request) but are not yet signed. "certmaster-ca --sign [hostname]" This command is used to sign a certificate and send it back to the requester. =head1 AUTO-SIGNING The certmaster can be configured to make this command unneccessary; all incoming requests can be signed automatically by certmaster. To configure this, edit /etc/certmaster/certmaster.conf. =head1 ADDITONAL RESOURCES See https://fedorahosted.org/certmaster. It's a Wiki. See also https://fedorahosted.org/func See also the manpages for "certmaster" and "certmaster-request". =head1 AUTHOR Various. See https://fedorahosted.org/certmaster and https://fedorahosted.org/func certmaster-0.25/docs/certmaster-sync.1.gz0000664000076400007640000000450611213774723017671 0ustar adrianadrian/JkSHEsle`IjlCe4HeFg$[*~GۄAxsrrlW?>~RO$JþgaMIRI2Y2~7aI*f{y$R9tްgaG`Or8lWW 7xp`A6l2bsåT<eSXrXjP(HҮت ÅR*^73ϣ\Ћ kްP@+Dz8$iiD2&(^!˹\TŌ<N.y2KtܝV(5y E:1L{x5҈)޻fmÃ\3EA_ =-2QhP!H#M"ƐeQAs "EQ!\9!<RPfwpKC ׌d{k-"4T!vs V2Czq% Ba.,,7:Nf-׻qSc q0 G-A~r}\!|U_w(N/! ˮcvGۚ]0M54ނu"ȚI =mVwYVŬlAcNQ( R$0Z 1֤.*vb//B*Z(3(n^H*HaW6ʾ*+ARR|Z{NTBBFYYUB&P_!\]3/D!JfȰ.82)<YY"ϊ;K g)5(b.Qʃg,0hy#XwunDmpR ϡg3T@#9)0<4X !F~i=k'f}GG^;&oM=Lq 92 AAvoKw\#}Psrn,ERg܀}l3k'pg~FxBVضZNiäޱ%-y-i/0JcvkUy[~{uwE156wYu^[bn~ÓYC[<24V]ˇ4N8O=-m=|ٰƛ&i[OPtŒ?ݖt뀵|U!@ GRvQ]Z<iymķeo@$yhFtHx$y]"ϭ7n_9g5SSZҺJFr WXcHlkvҁJ7$>'nGZĪߧ FMROծ3169-8~wM#mcvD/ U5%â$0>ـà:N )k*~z2ŽuUi,44`۳UFb[YSN8fSћ>T%zs9^Rk@[1t9OͶΑr;\)56e<;6;`̩)^PsƎM;8v袋u4@sx:L䷋!8x:=Gz/;NBh⌟'FZϪbOK@BQ2 ; DO SW$!0yFWW+ nH?%G4fQH=۰BG\^MFs=w8> ¤gt2ˋm>[Ym4UOS4t%EČXz z Be(̜(+V-B'+ GW]kt Ad4S1Mr:e1jQ<5O`gQˢ6B,& @F\B!AkaW8'1R{F ÆJ#˭`'IVۑԁסq0$ kWĄރu^7@M:>"zeHh^̀ QF]{ݍjK8?Vk[a%VG#BBmOrRD _ Wk5**1WNM`4 7Ja,c"։)c8cmhzy1xx]O"LL,60Iw, M88ڽ5ۭK|&|g"z> HSSa3?L5{!jv{q͞Ljnup`I60\s=1Cc 9x,4KX}|zAXo2LF1?(~@cmٱ$4J'D Ԍ*GK2iv``E@FB L0eR ܃F042 y2L8kFÔzm @~`~ŪU<IGkWdXt6&3AIs!L=*=hur нq4R+P[4FW_^ST&Wg̫4&s#ǥ$4T,WąQ@/Ŝ% sQvزdx4 gAJ wG[}nivc&wrtN-ANUgkk*DZ}# oYJPvXeipb~z+3@ܕ-gc|0z8~I΃y![}Va*RV-50>ހàlFۂR!kNq#ٙ߻>Y] v(ƃnGhWViϩe =ʄT%qy n\NoO@-~cE*re]#j] HE;sv[<SfF3vdlkG]0tn0+Pz/;N@dbF˫#\0}Dk&Ç'ohȇsJ+bhG[%i^S˗Ô{ /FLp;n'rDKbJSh8m1oy)Ry>k0+f]^Mf㙉Fpfe1a Xgl4NǓryoc9ip?l*NŲӬÝ!a  'zeXA cJ'#[<,= 8xq˲WOXI.sa\y+61 [fWу4=A{cmhk9v_T4X[riuxweP==pjb횉0ͥ i?'W) k`certmaster-0.25/docs/.gitignore0000664000076400007640000000004311064771520016021 0ustar adrianadrian# ignore compressed man pages *.gz certmaster-0.25/docs/certmaster-sync.pod0000664000076400007640000000243711175632103017665 0ustar adrianadrian=head1 NAME certmaster-sync -- syncronize client certificates with Func. =head1 SYNOPSIS certmaster-sync [-f|--force] =head1 DESCRIPTION certmaster-sync syncronizes client certificates amongst certmaster clients via Func. It is assumed that the hosts who have requested certificates are reachable via Func for syncronization operations. certmaster-sync by default is called as a post-sign and post-clean trigger. In order to enable syncronization you must set B to B, see B below. The syncronization occurs by querying remote Func methods in B on the minion hosts. This will gather information, copy any new certificates, and remove any certificates that have been cleaned. =head1 OPTIONS =over =item -f, --force Override the configuration value for B in F =back =head1 CONFIGURATION VALUES =over =item sync_certs B determines whether or not the script will actually syncronize or if it will exit with no operation. You can use -f|--force to override this configuration value. (Default: False) =back =head1 ADDITONAL RESOURCES See https://fedorahosted.org/certmaster. It's a Wiki. See also https://fedorahosted.org/func =head1 AUTHOR John Eckersberg certmaster-0.25/docs/certmaster-ca.1.gz0000664000076400007640000000415611213774723017301 0ustar adrianadrian/JksH;\"0@sMj kU-t+ w9nI l'UIgn, \'Ё+p Lh| Wwr^з_4Pga9MS-bOd<'|okXHR>WAR / k5 4mXuup' ZV&%62cA•Z-e + $튭J:c(&u='^o"NfsbZ~ 0!D4'HPS?`D2U&)$B"L*ur' Ɛ)CLVA2Ep'x!Z՘ 4wڙPyIN_D}ȓ],X,fаP!Mv:1i1"hB@( 9=[I/e&$J(vs{'pK7C䷔d{c-gNq>:| Ȗsa% }q8&R0t Ӳ givj8wzx]B<0L%Ñru0oh>~%cܨO!/.Ȯsv ~a4hA1Ϝ5,>w%#{ CZtsSd+R #XPV zQgI &`5Eю,9i~|E=WRpE% 2Te_E< dꠂƔdQ/Z켽=NfN TGK$xv^YL5X<~Ƿ Okmef?Lj?=>;~ّۑbG؋BVi*u7u:_~׹=bsWq p;ޜͺUAcԵ|Hc]r Dm :]-z'6qa dZ~N~&u H(B|_^V D*p1 3gNB oG; =iiwҴb$tw_ge\FɨwZ51]NM8tCӊض#v; `GFV($Vu1vaC!FiBZ0xNDs=2*^I%dC)h+ 9% $ nI7ɲwe=IHS6 ج :lՅQ0Y᫬0ԲEp3Y1Tp~ N\N@nA |cFq٘O1Hy\o)cnx'﮶'`̨(,nd(4vZm74}#R17Á%x `t3t1x^0Z3dм5T1u[`MtK/B{*8Nuqy5O W? Qs^S_hˉ7AdBh:fˋ]i; ^Ꞗ+ K'XQT,=GÚ(s.Ź͹ z穭n2qDerw G8(g5"d׆9ΜW;\`0BzV$et,"nNe N CIBÍRFS~eh٥9~1}7ؚW\qQuyVA9Bj:q,]E~Ab=6yX*<bǜI>HRY>HJvȵO+sOOdzˋ;&уa*%,NIK/I\0ָ P%?٢Q|mXYz/O;S:=< )],Hre_4A2n Жcertmaster-0.25/docs/certmaster-request.1.gz0000664000076400007640000000423511213774722020403 0ustar adrianadrian/JXmsHί"lK 9&u2U*ʞt442a7~- uUR~yezz& r"W2҃5LzQJ8l7,I`Eԃ$" eM ˓0[Y2@Ű EúKVk4X54v)UIH͵LcJh2WĆ<"JmJz/A 2(FWݰ| ßπFH(A~D$ d*B\K Wt*, 0rOA)t,ge U-{̐w3Ȗ83cY[J_"O$%WZyd2bw8 @ǀ \tiC3.ePܒ]gMz)STJQPB-V@ȯ L[*?`MK j^T˵ ?ü&2C0t Ӳ givb8/۵],!%44 LG[ˉ[ۣeGo{VrmR_E]]6zuu(elnaSQ]ްd8ߜo nn0*ee eH$d2Je੡^ SkdpLlGg  CWcjN!>yJRU :e_EH 2F8X=TkE=:č5?kp)3"M%.{]H6*#H?#⛟QT&'K\ RÑʕYF*$ąܡO*/ERV?s֪\ _;vH'N%to )y*v< \fgbP_(mN|cQWpdE="E3m;Ⱓ,vdQo&l`>vZql{af [փy#pDA2FKUF6meA 佁A764!:s~{S/F™?8euACv#[uaʙ s~0xZ6n$-f|"oiԛKlC(o("i])2U21ʇv5L79cG&o~t:9M$l>ѿF90?vvߘ#wLc!4qבL\fO+〧&<Ӂ4>Wbml#N@xkC>_kp饻)7YՋN`); 4/fc שc:&D rPLTz*4`^R&C3 CZ..'{էQn[򫠫rUI9 J`#QRXT ('p:Ǘhv6WԆN7>o`y%>K_Ʒt50ENml)LzU"XiTbMYŗ(4) t]Ts! kÔ*zJ o~3TO&cIMͺeF7gQl*6)(A'}Ē x~y1j:=, #:EҳTlˏ'X^b )a8 D,dqm sF$l~TʑrZ1Meȿ Y1%certmaster-0.25/docs/certmaster-request.pod0000664000076400007640000000155211175632103020376 0ustar adrianadrian=head1 NAME certmaster-request -- requests SSL certs from a certmasster Fedora Unified Network Controller. =head1 SYNOPSIS certmaster-request [--server certmaster.example.com] [--port port] [ --wait infinite/seconds ] =head1 DESCRIPTION FIXME: To be added later once we split this out from func. =head1 API Note: Many applications will want to use the XMLRPC API (see source) or import the Python code to request certs. For those that don't want to do that, this command line tool is available. Explore the other options if they make more sense for your application. =head1 EXIT_STATUS non-zero upon failure. =head1 ADDITONAL RESOURCES See https://fedorahosted.org/certmaster for more information See also the manpages for "certmaster", and "certmaster-ca". =head1 AUTHOR Various. See https://fedorahosted.org/func and https://fedorahosted.org/certmaster certmaster-0.25/etc/0000775000076400007640000000000011213774723013663 5ustar adrianadriancertmaster-0.25/etc/minion.conf0000664000076400007640000000020611116005011015774 0ustar adrianadrian# configuration for minions [main] certmaster = certmaster certmaster_port = 51235 log_level = DEBUG cert_dir = /etc/pki/certmaster certmaster-0.25/etc/.minion.conf.swp0000644000076400007640000003000011106605620016666 0ustar adrianadrianb0VIM 7.1 I>adrianlocalhost.localdomain~adrian/src/certmaster/etc/minion.conf 3210#"! UtpadFz{zycert_dir = /etc/pki/certmasterlog_level = DEBUGcertmaster_port = 51235certmaster = certmaster[main]# configuration for minionscertmaster-0.25/etc/certmaster_rotate0000664000076400007640000000046111064771520017332 0ustar adrianadrian/var/log/certmaster/audit.log { missingok notifempty rotate 4 weekly postrotate if [ -f /var/lock/subsys/certmasterd ]; then /etc/init.d/certmasterd condrestart fi endscript } /var/log/certmaster/certmaster.log { missingok notifempty rotate 4 weekly } certmaster-0.25/etc/certmaster.conf0000664000076400007640000000046011175632103016673 0ustar adrianadrian# configuration for certmasterd and certmaster-ca [main] autosign = no listen_addr = listen_port = 51235 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 certmaster-0.25/po/0000775000076400007640000000000011213774723013526 5ustar adrianadriancertmaster-0.25/po/messages.pot0000644000076400007640000000000011213774723016045 0ustar adrianadriancertmaster-0.25/scripts/0000775000076400007640000000000011213774723014577 5ustar adrianadriancertmaster-0.25/scripts/certmaster0000775000076400007640000000020211064771520016664 0ustar adrianadrian#!/usr/bin/python from certmaster import certmaster import sys if __name__ == "__main__": certmaster.main(sys.argv) certmaster-0.25/scripts/certmaster-request0000775000076400007640000000112111064771520020353 0ustar adrianadrian#!/usr/bin/python """ Application to request a cert from a certmaster. Takes no arguments, uses /etc/certmaster/minion.conf Copyright 2008, Red Hat, Inc Michael DeHaan 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. """ import distutils.sysconfig from certmaster import requester if __name__ == "__main__": requester.request_cert() certmaster-0.25/scripts/certmaster-sync0000664000076400007640000001014711175632103017640 0ustar adrianadrian#!/usr/bin/python -tt # Syncs the valid CA-signed certificates from certmaster to all known # hosts via func. To be called during the post-sign hook to copy new # certificates and from the post-clean hook in order to purge stale # certificates. Requires 'sync_certs' to be set in certmaster.conf. import os import sys import sha import xmlrpclib from glob import glob from time import sleep from certmaster import certmaster as certmaster from func.overlord.client import Client from func.CommonErrors import Func_Client_Exception import func.jobthing as jobthing def syncable(cert_list): """ Calls out to known hosts to find out who is configured for peering. Returns a list of hostnames who support peering. """ try: fc = Client('*', async=True, nforks=len(cert_list)) except Func_Client_Exception: # we are either: # - signing the first minion # - cleaning the only minion # so there's nothing to hit. This shouldn't happen # when we get called from the 'post-fetch' trigger # (future work) return None # Only wait for a few seconds. Assume anything that doesn't get # back by then is a lost cause. Don't want this trigger to spin # too long. ticks = 0 return_code = jobthing.JOB_ID_RUNNING results = None job_id = fc.certmastermod.peering_enabled() while return_code != jobthing.JOB_ID_FINISHED and ticks < 3: sleep(1) (return_code, results) = fc.job_status(job_id) ticks += 1 hosts = [] for host, result in results.iteritems(): if result == True: hosts.append(host) return hosts def remote_peers(hosts): """ Calls out to hosts to collect peer information """ fc = Client(';'.join(hosts)) return fc.certmastermod.known_peers() def local_certs(): """ Returns (hostname, sha1) hash of local certs """ globby = '*.%s' % cm.cfg.cert_extension globby = os.path.join(cm.cfg.certroot, globby) files = glob(globby) results = [] for f in files: hostname = os.path.basename(f).replace('.' + cm.cfg.cert_extension, '') digest = checksum(f) results.append([hostname, digest]) return results def checksum(f): thissum = sha.new() if os.path.exists(f): fo = open(f, 'r') data = fo.read() fo.close() thissum.update(data) return thissum.hexdigest() def remove_stale_certs(local, remote): """ For each cert on each remote host, make sure it exists locally. If not then it has been cleaned locally and needs unlinked remotely. """ local = [foo[0] for foo in local] # don't care about checksums for host, peers in remote.iteritems(): fc = Client(host) die = [] for peer in peers: if peer[0] not in local: die.append(peer[0]) if die != []: fc.certmastermod.remove_peer_certs(die) def copy_updated_certs(local, remote): """ For each local cert, make sure it exists on the remote with the correct hash. If not, copy it over! """ for host, peers in remote.iteritems(): fc = Client(host) for cert in local: if cert not in peers: cert_name = '%s.%s' % (cert[0], cm.cfg.cert_extension) full_path = os.path.join(cm.cfg.certroot, cert_name) fd = open(full_path) certblob = fd.read() fd.close() fc.certmastermod.copy_peer_cert(cert[0], xmlrpclib.Binary(certblob)) def main(): forced = False try: if sys.argv[1] in ['-f', '--force']: forced = True except IndexError: pass if not cm.cfg.sync_certs and not forced: sys.exit(0) certs = glob(os.path.join(cm.cfg.certroot, '*.%s' % cm.cfg.cert_extension)) hosts = syncable(certs) if not hosts: return 0 remote = remote_peers(hosts) local = local_certs() remove_stale_certs(local, remote) copy_updated_certs(local, remote) if __name__ == "__main__": cm = certmaster.CertMaster() main() certmaster-0.25/scripts/certmaster-ca0000775000076400007640000000613211064771520017255 0ustar adrianadrian#!/usr/bin/python -tt # sign/list keys # --sign hostname hostname hostname # --list # lists all csrs needing to be signed # --list-all ? # --clean? not sure what it will do import sys import glob import os import certmaster import certmaster.certs import certmaster.certmaster from optparse import OptionParser def errorprint(stuff): print >> sys.stderr, stuff def parseargs(args): usage = 'certmaster-ca