From 59635c22773a8a7b26c7c27891e999a3f71bbd1a Mon Sep 17 00:00:00 2001 From: Joey Hagedorn Date: Thu, 19 Jul 2007 18:54:02 +0000 Subject: merging back in xmlrpc branch for agent-mode support git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@3505 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Component.py | 57 +++++++++++++++----- src/sbin/bcfg2 | 145 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/sbin/bcfg2-remote | 55 +++++++++++++++++++ 3 files changed, 239 insertions(+), 18 deletions(-) create mode 100755 src/sbin/bcfg2-remote (limited to 'src') diff --git a/src/lib/Component.py b/src/lib/Component.py index 1e905121f..7ebdf8f86 100644 --- a/src/lib/Component.py +++ b/src/lib/Component.py @@ -2,7 +2,8 @@ __revision__ = '$Revision$' import atexit, logging, select, signal, socket, sys, time, urlparse, xmlrpclib, cPickle, ConfigParser -import BaseHTTPServer, SimpleXMLRPCServer, SocketServer +from base64 import decodestring +import BaseHTTPServer, SimpleXMLRPCServer import Bcfg2.tlslite.errors import Bcfg2.tlslite.api @@ -30,7 +31,23 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): try: # get arguments data = self.rfile.read(int(self.headers["content-length"])) - response = self.server._cobalt_marshalled_dispatch(data, self.client_address) + + authenticated = False + #try x509 cert auth (will have been completed, just checking status) + authenticated = self.request.authenticated + #TLSConnection can be accessed by self.request? + + #try httpauth + if not authenticated and "Authorization" in self.headers: + #need more checking here in case its all garbled + namepass = decodestring(self.headers["Authorization"].strip("Basic ")).split(":") + if self.server._authenticate_connection("bogus-method", + namepass[0], + namepass[1], + self.client_address): + authenticated = True + + response = self.server._cobalt_marshalled_dispatch(data, self.client_address, authenticated) except: # This should only happen if the module is buggy # internal error, report as HTTP server error log.error("Unexcepted handler failure in do_POST", exc_info=1) @@ -60,8 +77,10 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): class TLSServer(Bcfg2.tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): '''This class is an tlslite-using SSLServer''' - def __init__(self, address, keyfile, handler, checker=None): + def __init__(self, address, keyfile, handler, checker=None, + reqCert=False): self.sc = Bcfg2.tlslite.api.SessionCache() + self.rc = reqCert x509 = Bcfg2.tlslite.api.X509() s = open(keyfile).read() x509.parse(s) @@ -85,8 +104,14 @@ class TLSServer(Bcfg2.tlslite.api.TLSSocketServerMixIn, tlsConnection.handshakeServer(certChain=self.chain, privateKey=self.key, sessionCache=self.sc, - checker=self.checker) + checker=self.checker, + reqCert=self.rc) tlsConnection.ignoreAbruptClose = True + #Connection authenticated during TLS handshake, no need for passwords + if not self.checker == None: + tlsConnection.authenticated = True + else: + tlsConnection.authenticated = False return True except Bcfg2.tlslite.errors.TLSError, error: return False @@ -160,7 +185,7 @@ class Component(TLSServer, self.assert_location() atexit.register(self.deassert_location) - def _cobalt_marshalled_dispatch(self, data, address): + def _cobalt_marshalled_dispatch(self, data, address, authenticated=False): """Decode and dispatch XMLRPC requests. Overloaded to pass through client address information """ @@ -171,15 +196,19 @@ class Component(TLSServer, % (address[0])) #open('/tmp/badreq', 'w').write(data) return xmlrpclib.dumps(xmlrpclib.Fault(4, "Bad Request")) - if len(rawparams) < 2: - self.logger.error("No authentication included with request from %s" % address[0]) - return xmlrpclib.dumps(xmlrpclib.Fault(2, "No Authentication Info")) - user = rawparams[0] - password = rawparams[1] - params = rawparams[2:] - # check authentication - if not self._authenticate_connection(method, user, password, address): - return xmlrpclib.dumps(xmlrpclib.Fault(3, "Authentication Failure")) + if not authenticated: + if len(rawparams) < 2: + self.logger.error("No authentication included with request from %s" % address[0]) + return xmlrpclib.dumps(xmlrpclib.Fault(2, "No Authentication Info")) + user = rawparams[0] + password = rawparams[1] + params = rawparams[2:] + # check authentication + if not self._authenticate_connection(method, user, password, address): + return xmlrpclib.dumps(xmlrpclib.Fault(3, "Authentication Failure")) + else: + #there is no prefixed auth info in this case + params = rawparams[0:] # generate response try: # all handlers must take address as the first argument diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index 262cdc8b9..333048535 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -7,6 +7,7 @@ import getopt import logging import os import signal +import socket import sys import tempfile import time @@ -15,6 +16,9 @@ import Bcfg2.Options import Bcfg2.Client.XML import Bcfg2.Client.Frame import Bcfg2.Client.Tools +from Bcfg2.Component import * +from Bcfg2.tlslite.Checker import Checker +from Bcfg2.tlslite.errors import * try: import Bcfg2.Client.Proxy @@ -83,6 +87,12 @@ class Client: False, ('communication', 'retries'), '3', False), 'kevlar': (('-k', False, "run in kevlar (bulletproof) mode"), False, False, False, True), + 'agent': (('-A', False, "run in agent (continuous) mode, wait for reconfigure command from server"), + False, False, False, True), + 'agent-port': (('-g', '', 'the port on which to bind for agent mode'), + False, ('communication', 'agent-port'), '6789', False), + 'key': (('-K', '', 'ssl cert + private key for agent mode xmlrpc server'), + False, ('communication', 'key'), False, False), } optparser = Bcfg2.Options.OptionParser('bcfg2', optinfo) @@ -111,6 +121,15 @@ class Client: if (self.setup["file"] != False) and (self.setup["cache"] != False): print "cannot use -f and -c together" raise SystemExit(1) + if (self.setup["agent"] != False) and (self.setup["interactive"] != False): + print "cannot use -A and -I together" + raise SystemExit(1) + if (self.setup["agent"] and not self.setup["fingerprint"]): + print "Agent mode requires specification of x509 fingerprint" + raise SystemExit(1) + if (self.setup["agent"] and not self.setup["key"]): + print "Agent mode requires specification of ssl cert + key file" + raise SystemExit(1) def run_probe(self, probe): '''Execute probe''' @@ -137,8 +156,11 @@ class Client: def fatal_error(self, message): '''Signal a fatal error''' self.logger.error("Fatal error: %s" % (message)) - raise SystemExit(1) - + if not self.setup["agent"]: + raise SystemExit(1) + else: + self.logger.error("Continuing...") + def run(self): ''' Perform client execution phase ''' times = {} @@ -157,18 +179,21 @@ class Client: except IOError: self.fatal_error("failed to read cached configuration from: %s" % (self.setup['file'])) + return(1) else: # retrieve config from server try: proxy = Bcfg2.Client.Proxy.bcfg2(self.setup) except: self.fatal_error("failed to instantiate proxy to server") + return(1) if self.setup['profile']: try: proxy.AssertProfile(self.setup['profile']) except xmlrpclib.Fault: self.fatal_error("Failed to set client profile") + return(1) try: probe_data = proxy.GetProbes() @@ -185,6 +210,7 @@ class Client: self.fatal_error( "server returned invalid probe requests: %s" % (syntax_error)) + return(1) # execute probes try: @@ -227,13 +253,17 @@ class Client: except Bcfg2.Client.XML.ParseError, syntax_error: self.fatal_error("the configuration could not be parsed: %s" % (syntax_error)) + return(1) times['config_parse'] = time.time() if self.config.tag == 'error': self.fatal_error("server error: %s" % (self.config.text)) + return(1) - self.tools = Bcfg2.Client.Frame.Frame(self.config, self.setup, times) + self.tools = Bcfg2.Client.Frame.Frame(self.config, + self.setup, + times) self.tools.Execute() @@ -247,7 +277,114 @@ class Client: self.logger.error("Failed to upload configuration statistics") raise SystemExit(2) +class FingerCheck(object): + def __init__(self, fprint): + self.fingerprint = fprint + self.logger = logging.getLogger('checker') + + def __call__(self, connection): + if connection._client: + chain = connection.session.serverCertChain + else: + chain = connection.session.clientCertChain + + if chain == None: + self.logger.error("Fingerprint authentication error") + raise TLSNoAuthenticationError() + if chain.getFingerprint() != self.fingerprint: + self.logger.error("Got connection with bad fingerprint %s" \ + % (chain.getFingerprint())) + raise TLSFingerprintError(\ + "X.509 fingerprint mismatch: %s, %s" % \ + (chain.getFingerprint(), self.fingerprint)) + +class Agent(Bcfg2.Component.Component): + """The Bcfg2 Agent component providing XML-RPC access to 'run'""" + __name__ = 'bcfg2-agent' + __implementation__ = 'bcfg2-agent' + + def __init__(self, client): + # need to get addr + self.setup = client.setup + self.shut = False + signal.signal(signal.SIGINT, self.start_shutdown) + signal.signal(signal.SIGTERM, self.start_shutdown) + self.logger = logging.getLogger('Agent') + + self.static = True + + if self.setup["agent-port"]: + port = int(self.setup["agent-port"]) + elif self.setup["server"]: + port = int(self.setup["server"].split(':')[1]) + else: + print "port or server URL not specified" + raise SystemExit, 1 + + location = (socket.gethostname(), port) + + keyfile = self.setup["key"] + self.password = self.setup["password"] + + try: + TLSServer.__init__(self, + location, + keyfile, + CobaltXMLRPCRequestHandler, + FingerCheck(self.setup["fingerprint"]), + reqCert=True) + except socket.error: + self.logger.error("Failed to bind to socket") + raise ComponentInitError + except ComponentKeyError: + self.logger.error("Failed to parse key" % (keyfile)) + raise ComponentInitError + except: + self.logger.error("Failed to load ssl key %s" % (keyfile), exc_info=1) + raise ComponentInitError + try: + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) + except TypeError: + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, False, None) + self.logRequests = 0 + self.port = self.socket.getsockname()[1] + self.url = "https://%s:%s" % (socket.gethostname(), self.port) + self.logger.info("Bound to port %s" % self.port) + self.funcs.update({'system.listMethods':self.addr_system_listMethods}) + self.atime = 0 + self.client = client + self.funcs.update({ + "run": self.run, + }) + + def run(self, address): + try: + os.waitpid(-1, os.WNOHANG) + except: + pass + self.logger.info("Got run request from %s" % (address[0])) + if os.fork(): + return True + else: + try: + self.client.run() + except SystemExit: + self.logger.error("Client failed to execute") + self.shut = True + return False + if __name__ == '__main__': signal.signal(signal.SIGINT, cb_sigint_handler) client = Client() - client.run() + spid = os.getpid() + if client.setup["agent"]: + agent = Agent(client) + while not agent.shut: + try: + agent.serve_forever() + except: + critical_error('error in service loop') + if os.getpid() == spid: + print("Shutting down") + else: + client.run() diff --git a/src/sbin/bcfg2-remote b/src/sbin/bcfg2-remote new file mode 100755 index 000000000..4b45965c6 --- /dev/null +++ b/src/sbin/bcfg2-remote @@ -0,0 +1,55 @@ +#!/usr/bin/env python +__revision__ = '$Revision$' + +from Bcfg2.tlslite.api import parsePEMKey, X509, X509CertChain +from xmlrpclib import ServerProxy +from Bcfg2.tlslite.integration.XMLRPCTransport import XMLRPCTransport +import Bcfg2.Options, Bcfg2.Logging, logging, socket + +if __name__ == '__main__': + opts = { + 'agent-port': (('-p', '', 'agent TCP port'), + False, ('communication', 'agent-port'), + '6789', False), + 'host': (('-H', '', 'agent host'), + False, False, False, False), + 'setup': (('-C', '', "config file path"), + False, False, '/etc/bcfg2.conf', False), + 'key': (('-k', '', 'ssl key path'), + False, ('communication', 'key'), False, False), + 'debug': (('-d', '', 'debug level (0-40)'), + False, False, 0, False), + } + optparser = Bcfg2.Options.OptionParser('bcfg2', opts) + setup = optparser.parse() + Bcfg2.Logging.setup_logging('bcfg2-remote', + to_syslog=False, + level=int(setup['debug'])) + logger = logging.getLogger('bcfg2-remote') + if not setup['host']: + logger.error("-H option is required") + logger.error(optparser.helpmsg) + raise SystemExit(1) + s = open(setup['key']).read() + x509 = X509() + x509.parse(s) + certChain = X509CertChain([x509]) + #s = open("/etc/bcfg2.key").read() + privateKey = parsePEMKey(s, private=True) + + transport = XMLRPCTransport(certChain=certChain, + privateKey=privateKey) + host = setup['host'] + port = setup['agent-port'] + server = ServerProxy("https://%s:%s" % (host, port), transport) + + try: + result = server.run() + except socket.error, serr: + if serr.args[0] == 111: + logger.error("Failed to connect to client %s" % host) + elif serr.args[0] == 32: + logger.error("Connection to client %s dropped; authentication failure?" % host) + else: + logger.error("Got unexpected socket error %d" % serr.args[0]) + raise SystemExit(1) -- cgit v1.2.3-1-g7c22