summaryrefslogtreecommitdiffstats
path: root/func/minion/server.py
diff options
context:
space:
mode:
Diffstat (limited to 'func/minion/server.py')
-rwxr-xr-xfunc/minion/server.py285
1 files changed, 285 insertions, 0 deletions
diff --git a/func/minion/server.py b/func/minion/server.py
new file mode 100755
index 0000000..f1b827f
--- /dev/null
+++ b/func/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)