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/sbin/bcfg2 | 145 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/sbin/bcfg2-remote | 55 +++++++++++++++++++ 2 files changed, 196 insertions(+), 4 deletions(-) create mode 100755 src/sbin/bcfg2-remote (limited to 'src/sbin') 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