X-Git-Url: https://pwan.org/git/?a=blobdiff_plain;f=certmaster%2Fminion%2Fserver.py;fp=certmaster%2Fminion%2Fserver.py;h=f1b827fbb15a6d0368bfa1a9335db7e6d088331b;hb=8f2ff4d7c902d534d68ff1a16418b7be492033bf;hp=0000000000000000000000000000000000000000;hpb=5b2601a56907b02efc6567354fa051ef08d97b6f;p=certmaster.git diff --git a/certmaster/minion/server.py b/certmaster/minion/server.py new file mode 100755 index 0000000..f1b827f --- /dev/null +++ b/certmaster/minion/server.py @@ -0,0 +1,285 @@ +""" +func + +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 socket +import fnmatch + +from gettext import textdomain +I18N_DOMAIN = "func" + + +from func.config import read_config +from func.commonconfig import FuncdConfig +from func import logger +from func import certs +import func.jobthing as jobthing +import utils + +# our modules +import AuthedXMLRPCServer +import codes +import module_loader +import func.utils as futils + + + +class XmlRpcInterface(object): + + def __init__(self): + + """ + Constructor. + """ + + config_file = '/etc/func/minion.conf' + self.config = read_config(config_file, FuncdConfig) + self.logger = logger.Logger().logger + self.audit_logger = logger.AuditLogger() + self.__setup_handlers() + + # need a reference so we can log ip's, certs, etc + # self.server = server + + def __setup_handlers(self): + + """ + Add RPC functions from each class to the global list so they can be called. + """ + + self.handlers = {} + for x in self.modules.keys(): + try: + self.modules[x].register_rpc(self.handlers, x) + self.logger.debug("adding %s" % x) + except AttributeError, e: + self.logger.warning("module %s not loaded, missing register_rpc method" % self.modules[x]) + + + # internal methods that we do instead of spreading internal goo + # all over the modules. For now, at lest -akl + + + # system.listMethods os a quasi stanard xmlrpc method, so + # thats why it has a odd looking name + self.handlers["system.listMethods"] = self.list_methods + self.handlers["system.list_methods"] = self.list_methods + self.handlers["system.list_modules"] = self.list_modules + + def list_modules(self): + modules = self.modules.keys() + modules.sort() + return modules + + def list_methods(self): + methods = self.handlers.keys() + methods.sort() + return methods + + def get_dispatch_method(self, method): + + if method in self.handlers: + return FuncApiMethod(self.logger, method, self.handlers[method]) + + else: + self.logger.info("Unhandled method call for method: %s " % method) + raise codes.InvalidMethodException + + +class FuncApiMethod: + + """ + Used to hold a reference to all of the registered functions. + """ + + def __init__(self, logger, name, method): + + self.logger = logger + self.__method = method + self.__name = name + + def __log_exc(self): + + """ + Log an exception. + """ + + (t, v, tb) = sys.exc_info() + self.logger.info("Exception occured: %s" % t ) + self.logger.info("Exception value: %s" % v) + self.logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb)))) + + def __call__(self, *args): + + self.logger.debug("(X) -------------------------------------------") + + try: + rc = self.__method(*args) + except codes.FuncException, e: + self.__log_exc() + (t, v, tb) = sys.exc_info() + rc = futils.nice_exception(t,v,tb) + except: + self.__log_exc() + (t, v, tb) = sys.exc_info() + rc = futils.nice_exception(t,v,tb) + self.logger.debug("Return code for %s: %s" % (self.__name, rc)) + + return rc + + +def serve(): + + """ + Code for starting the XMLRPC service. + """ + server =FuncSSLXMLRPCServer(('', 51234)) + server.logRequests = 0 # don't print stuff to console + server.serve_forever() + + + +class FuncXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, XmlRpcInterface): + + def __init__(self, args): + + self.allow_reuse_address = True + + self.modules = module_loader.load_modules() + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args) + XmlRpcInterface.__init__(self) + + +class FuncSSLXMLRPCServer(AuthedXMLRPCServer.AuthedSSLXMLRPCServer, + XmlRpcInterface): + def __init__(self, args): + self.allow_reuse_address = True + self.modules = module_loader.load_modules() + + XmlRpcInterface.__init__(self) + hn = utils.get_hostname() + self.key = "%s/%s.pem" % (self.config.cert_dir, hn) + self.cert = "%s/%s.cert" % (self.config.cert_dir, hn) + self.ca = "%s/ca.cert" % self.config.cert_dir + + self._our_ca = certs.retrieve_cert_from_file(self.ca) + + AuthedXMLRPCServer.AuthedSSLXMLRPCServer.__init__(self, ("", 51234), + self.key, self.cert, + self.ca) + + def _dispatch(self, method, params): + + """ + the SimpleXMLRPCServer class will call _dispatch if it doesn't + find a handler method + """ + # take _this_request and hand it off to check out the acls of the method + # being called vs the requesting host + + if not hasattr(self, '_this_request'): + raise codes.InvalidMethodException + + r,a = self._this_request + peer_cert = r.get_peer_certificate() + ip = a[0] + + + # generally calling conventions are: hardware.info + # async convention is async.hardware.info + # here we parse out the async to decide how to invoke it. + # see the async docs on the Wiki for further info. + async_dispatch = False + if method.startswith("async."): + async_dispatch = True + method = method.replace("async.","",1) + + if not self._check_acl(peer_cert, ip, method, params): + raise codes.AccessToMethodDenied + + # Recognize ipython's tab completion calls + if method == 'trait_names' or method == '_getAttributeNames': + return self.handlers.keys() + + cn = peer_cert.get_subject().CN + sub_hash = peer_cert.subject_name_hash() + self.audit_logger.log_call(ip, cn, sub_hash, method, params) + + try: + if not async_dispatch: + return self.get_dispatch_method(method)(*params) + else: + return jobthing.minion_async_run(self.get_dispatch_method, method, params) + except: + (t, v, tb) = sys.exc_info() + rc = futils.nice_exception(t, v, tb) + return rc + + def auth_cb(self, request, client_address): + peer_cert = request.get_peer_certificate() + return peer_cert.get_subject().CN + + def _check_acl(self, cert, ip, method, params): + acls = utils.get_acls_from_config(acldir=self.config.acl_dir) + + # certmaster always gets to run things + ca_cn = self._our_ca.get_subject().CN + ca_hash = self._our_ca.subject_name_hash() + ca_key = '%s-%s' % (ca_cn, ca_hash) + acls[ca_key] = ['*'] + + cn = cert.get_subject().CN + sub_hash = cert.subject_name_hash() + if acls: + allow_list = [] + hostkey = '%s-%s' % (cn, sub_hash) + # search all the keys, match to 'cn-subhash' + for hostmatch in acls.keys(): + if fnmatch.fnmatch(hostkey, hostmatch): + allow_list.extend(acls[hostmatch]) + # go through the allow_list and make sure this method is in there + for methodmatch in allow_list: + if fnmatch.fnmatch(method, methodmatch): + return True + + return False + + +def main(argv): + + """ + Start things up. + """ + + if "daemon" in sys.argv or "--daemon" in sys.argv: + futils.daemonize("/var/run/funcd.pid") + else: + print "serving...\n" + + try: + utils.create_minion_keys() + serve() + except codes.FuncException, e: + print >> sys.stderr, 'error: %s' % e + sys.exit(1) + + +# ====================================================================================== +if __name__ == "__main__": + textdomain(I18N_DOMAIN) + main(sys.argv)