summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/bcfg2-remote.840
-rw-r--r--man/bcfg2.conf.52
-rw-r--r--src/lib/Component.py57
-rwxr-xr-xsrc/sbin/bcfg2145
-rwxr-xr-xsrc/sbin/bcfg2-remote55
5 files changed, 281 insertions, 18 deletions
diff --git a/man/bcfg2-remote.8 b/man/bcfg2-remote.8
new file mode 100644
index 000000000..8e1aab956
--- /dev/null
+++ b/man/bcfg2-remote.8
@@ -0,0 +1,40 @@
+.TH "bcfg2-remote" 8
+.SH NAME
+bcfg2-remote \- Connect to a bcfg2 agent and trigger client execution
+.SH SYNOPSIS
+.B bcfg2-remote
+.I [-d <debug level>]
+.I [-H <agent host>]
+.I [-C <configfile>]
+.I [-k <ssl key>]
+.I [-p <agent tcp port>]
+.SH DESCRIPTION
+bcfg2-remote connects to a client agent, uses the server certificate
+for authentication, and causes that client to run the bcfg2 client
+code with the options specified on the client.
+.SH OPTIONS
+.PP
+.B "\-d debuglevel"
+.RS
+Set debug level (0-40).
+.RE
+.B "\-H hostname"
+.RS
+Connect to the agent on the specified host.
+.RE
+.B "-C configfile"
+.RS
+Use the specified bcfg2.conf.
+.RE
+.B "\-k sslkey"
+.RS
+Use the ssl certificate at the following path.
+.RE
+.B "\-p port"
+.RS
+Connect to the agent on the specified TCP port.
+.RE
+.SH "SEE ALSO"
+.BR bcfg2(1)
+
+
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index feebcb05a..e9b142f5d 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -24,6 +24,8 @@ key=/path/to/ssl/key
.TP
protocol=xmlrpc/ssl
.TP
+agent-port=TCP_port
+.TP
.B [components]
list of component urls
.PP
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', '<agent tcp port>', 'the port on which to bind for agent mode'),
+ False, ('communication', 'agent-port'), '6789', False),
+ 'key': (('-K', '<client key file>', '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>', 'agent TCP port'),
+ False, ('communication', 'agent-port'),
+ '6789', False),
+ 'host': (('-H', '<agent host>', 'agent host'),
+ False, False, False, False),
+ 'setup': (('-C', '<configfile>', "config file path"),
+ False, False, '/etc/bcfg2.conf', False),
+ 'key': (('-k', '<ssl key>', 'ssl key path'),
+ False, ('communication', 'key'), False, False),
+ 'debug': (('-d', '<debug level>', '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)