summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/Component.py12
-rw-r--r--src/lib/SSLServer.py55
-rw-r--r--src/lib/Server/Plugins/Metadata.py7
-rw-r--r--src/lib/Server/XMLRPC.py159
-rwxr-xr-xsrc/sbin/bcfg2-server230
5 files changed, 207 insertions, 256 deletions
diff --git a/src/lib/Component.py b/src/lib/Component.py
index d35759603..b028f0dea 100644
--- a/src/lib/Component.py
+++ b/src/lib/Component.py
@@ -19,7 +19,8 @@ import Bcfg2.Logger
from Bcfg2.SSLServer import XMLRPCServer
def run_component (component_cls, argv=None, register=True, state_name=False,
- cls_kwargs={}, extra_getopt='', time_out=10):
+ cls_kwargs={}, extra_getopt='', time_out=10, certfile=None, keyfile=None,
+ ca=None):
if argv is None:
argv = sys.argv
try:
@@ -46,7 +47,6 @@ def run_component (component_cls, argv=None, register=True, state_name=False,
level = logging.DEBUG
logging.getLogger().setLevel(level)
- Bcfg2.Logger.log_to_stderr(logging.getLogger())
Bcfg2.Logger.setup_logging(component_cls.implementation, True, True)
if daemon:
@@ -73,13 +73,11 @@ def run_component (component_cls, argv=None, register=True, state_name=False,
pidfile.close()
component = component_cls(**cls_kwargs)
-
+ # FIXME
location = ('', 6789)
- keypath = '/etc/bcfg2.key'
- certfile = '/etc/bcfg2.key'
- server = XMLRPCServer(location, keyfile=keypath, certfile=keypath,
- register=register, timeout=time_out)
+ server = XMLRPCServer(location, keyfile=keyfile, certfile=certfile,
+ register=register, timeout=time_out, ca=ca)
server.register_instance(component)
try:
diff --git a/src/lib/SSLServer.py b/src/lib/SSLServer.py
index 99d6410b4..b9ab81f81 100644
--- a/src/lib/SSLServer.py
+++ b/src/lib/SSLServer.py
@@ -30,10 +30,11 @@ class XMLRPCDispatcher (SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
self.allow_none = allow_none
self.encoding = encoding
- def _marshaled_dispatch (self, data):
+ def _marshaled_dispatch (self, address, data):
method_func = None
params, method = xmlrpclib.loads(data)
try:
+ params = (address, ) + params
response = self.instance._dispatch(method, params, self.funcs)
response = (response,)
raw_response = xmlrpclib.dumps(response, methodresponse=1,
@@ -89,11 +90,16 @@ class SSLServer (SocketServer.TCPServer, object):
self.certfile = certfile
self.ca = ca
self.reqCert = reqCert
+ if ca and certfile:
+ self.mode = ssl.CERT_OPTIONAL
+ else:
+ self.mode = ssl.CERT_NONE
def get_request(self):
(sock, sockinfo) = self.socket.accept()
sslsock = ssl.wrap_socket(sock, server_side=True, certfile=self.certfile,
- keyfile=self.keyfile)
+ keyfile=self.keyfile, cert_reqs=self.mode,
+ ca_certs=self.ca)
return sslsock, sockinfo
def _get_url (self):
@@ -119,19 +125,12 @@ class XMLRPCRequestHandler (SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
"""
logger = logging.getLogger("Cobalt.Server.XMLRPCRequestHandler")
- class CouldNotAuthenticate (Exception):
- """Client did not present acceptible authentication information."""
-
- require_auth = True
- credentials = {'root':'default'}
-
def authenticate (self):
- """Authenticate the credentials of the latest client."""
try:
header = self.headers['Authorization']
except KeyError:
self.logger.error("No authentication data presented")
- raise self.CouldNotAuthenticate("client did not present credentials")
+ return False
auth_type, auth_content = header.split()
auth_content = base64.standard_b64decode(auth_content)
try:
@@ -139,12 +138,10 @@ class XMLRPCRequestHandler (SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
except ValueError:
username = auth_content
password = ""
- try:
- valid_password = self.credentials[username]
- except KeyError:
- raise self.CouldNotAuthenticate("unknown user: %s" % username)
- if password != valid_password:
- raise self.CouldNotAuthenticate("invalid password for %s" % username)
+ cert = self.request.getpeercert()
+ client_address = self.request.getpeername()
+ return self.server.instance.authenticate(cert, username,
+ password, client_address)
def parse_request (self):
"""Extends parse_request.
@@ -152,18 +149,18 @@ class XMLRPCRequestHandler (SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
Optionally check HTTP authentication when parsing."""
if not SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.parse_request(self):
return False
- if self.require_auth:
- try:
- self.authenticate()
- except self.CouldNotAuthenticate, e:
- self.logger.error("Authentication failed: %s" % e.args[0])
- code = 401
- message, _ = self.responses[401]
- self.send_error(code, message)
+ try:
+ if not self.authenticate():
+ self.logger.error("Authentication Failure")
+ self.send_error(401, self.responses[401][0])
return False
+ except:
+ self.logger.error("Unexpected Authentication Failure", exc_info=1)
+ self.send_error(401, self.responses[401][0])
+ return False
return True
- ### FIXME need to override do_POST here
+ ### need to override do_POST here
def do_POST(self):
try:
max_chunk_size = 10*1024*1024
@@ -175,7 +172,7 @@ class XMLRPCRequestHandler (SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
size_remaining -= len(L[-1])
data = ''.join(L)
- response = self.server._marshaled_dispatch(data)
+ response = self.server._marshaled_dispatch(self.client_address, data)
except:
raise
self.send_response(500)
@@ -215,7 +212,7 @@ class XMLRPCServer (SocketServer.ThreadingMixIn, SSLServer,
"""
def __init__ (self, server_address, RequestHandlerClass=None,
- keyfile=None, certfile=None,
+ keyfile=None, certfile=None, ca=None,
timeout=10,
logRequests=False,
register=True, allow_none=True, encoding=None):
@@ -242,7 +239,7 @@ class XMLRPCServer (SocketServer.ThreadingMixIn, SSLServer,
"""A subclassed request handler to prevent class-attribute conflicts."""
SSLServer.__init__(self,
- server_address, RequestHandlerClass,
+ server_address, RequestHandlerClass, ca=ca,
timeout=timeout, keyfile=keyfile, certfile=certfile)
self.logRequests = logRequests
self.serve = False
@@ -291,7 +288,7 @@ class XMLRPCServer (SocketServer.ThreadingMixIn, SSLServer,
except AttributeError:
name = "unknown"
self.logger.info("serving %s at %s" % (name, self.url))
-
+
def serve_forever (self):
"""Serve single requests until (self.serve == False)."""
self.serve = True
diff --git a/src/lib/Server/Plugins/Metadata.py b/src/lib/Server/Plugins/Metadata.py
index 86d06d820..f7cf196fc 100644
--- a/src/lib/Server/Plugins/Metadata.py
+++ b/src/lib/Server/Plugins/Metadata.py
@@ -421,8 +421,11 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
setattr(imd, source, data)
imd.connectors.append(source)
- def AuthenticateConnection(self, user, password, address):
- '''This function checks user and password'''
+ def AuthenticateConnection(self, cert, user, password, address):
+ '''This function checks auth creds'''
+ if cert:
+ self.logger.error("Cert checking not yet implemented")
+ return False
if user == 'root':
# we aren't using per-client keys
try:
diff --git a/src/lib/Server/XMLRPC.py b/src/lib/Server/XMLRPC.py
new file mode 100644
index 000000000..5788901cc
--- /dev/null
+++ b/src/lib/Server/XMLRPC.py
@@ -0,0 +1,159 @@
+
+import hashlib
+import logging
+import lxml.etree
+import select
+import socket
+import time
+import xmlrpclib
+
+from Bcfg2.Component import Component, exposed
+import Bcfg2.Server.Core
+
+logger = logging.getLogger('server')
+
+def critical_error(operation):
+ '''Log and err, traceback and return an xmlrpc fault to client'''
+ logger.error(operation, exc_info=1)
+ raise xmlrpclib.Fault(7, "Critical unexpected failure: %s" % (operation))
+
+class SetupError(Exception):
+ '''Used when the server cant be setup'''
+ pass
+
+class bcfg2_server(Component,
+ Bcfg2.Server.Core.Core):
+ '''XML RPC interfaces for the server core'''
+ name = 'bcfg2-server'
+
+ def __init__(self, setup):
+ Component.__init__(self)
+ Bcfg2.Server.Core.Core.__init__(self, setup['repo'], setup['plugins'],
+ setup['password'],
+ setup['encoding'], setup['filemonitor'])
+ self.process_initial_fam_events()
+
+ def process_initial_fam_events(self):
+ events = False
+ while True:
+ try:
+ rsockinfo = select.select([self.fam.fileno()], [], [], 15)[0]
+ if not rsockinfo:
+ if events:
+ break
+ else:
+ logger.error("Hit event timeout without getting "
+ "any events; GAMIN/FAM problem?")
+ continue
+ events = True
+ i = 0
+ while self.fam.Service() or i < 10:
+ i += 1
+ time.sleep(0.1)
+ except socket.error:
+ continue
+
+ @exposed
+ def GetProbes(self, address):
+ '''Fetch probes for a particular client'''
+ resp = lxml.etree.Element('probes')
+ try:
+ name = self.metadata.resolve_client(address)
+ meta = self.build_metadata(name)
+
+ for plugin in [p for p in list(self.plugins.values()) \
+ if isinstance(p, Bcfg2.Server.Plugin.Probing)]:
+ for probe in plugin.GetProbes(meta):
+ resp.append(probe)
+ return lxml.etree.tostring(resp, encoding='UTF-8',
+ xml_declaration=True)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ warning = 'Client metadata resolution error for %s; check server log' % address[0]
+ self.logger.warning(warning)
+ raise xmlrpclib.Fault(6, warning)
+ except:
+ critical_error("error determining client probes")
+
+ @exposed
+ def RecvProbeData(self, address, probedata):
+ '''Receive probe data from clients'''
+ try:
+ name = self.metadata.resolve_client(address)
+ meta = self.build_metadata(name)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ warning = 'metadata consistency error'
+ self.logger.warning(warning)
+ raise xmlrpclib.Fault(6, warning)
+ # clear dynamic groups
+ self.metadata.cgroups[meta.hostname] = []
+ try:
+ xpdata = lxml.etree.XML(probedata)
+ except:
+ self.logger.error("Failed to parse probe data from client %s" % \
+ (address[0]))
+ return False
+
+ sources = []
+ [sources.append(data.get('source')) for data in xpdata
+ if data.get('source') not in sources]
+ for source in sources:
+ if source not in self.plugins:
+ self.logger.warning("Failed to locate plugin %s" % (source))
+ continue
+ dl = [data for data in xpdata if data.get('source') == source]
+ try:
+ self.plugins[source].ReceiveData(meta, dl)
+ except:
+ logger.error("Failed to process probe data from client %s" % \
+ (address[0]), exc_info=1)
+ return True
+
+ @exposed
+ def AssertProfile(self, address, profile):
+ '''Set profile for a client'''
+ try:
+ client = self.metadata.resolve_client(address)
+ self.metadata.set_profile(client, profile, address)
+ except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError,
+ Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
+ warning = 'metadata consistency error'
+ self.logger.warning(warning)
+ raise xmlrpclib.Fault(6, warning)
+ return True
+
+ @exposed
+ def GetConfig(self, address, checksum=False):
+ '''Build config for a client'''
+ try:
+ client = self.metadata.resolve_client(address)
+ config = self.BuildConfiguration(client)
+ if checksum:
+ for cfile in config.findall('.//ConfigFile'):
+ if cfile.text != None:
+ csum = hashlib.md5()
+ csum.update(cfile.text)
+ cfile.set('checksum', csum.hexdigest())
+ cfile.text = None
+ return lxml.etree.tostring(config, encoding='UTF-8',
+ xml_declaration=True)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ self.logger.warning("Metadata consistency failure for %s" % (address))
+ raise xmlrpclib.Fault(6, "Metadata consistency failure")
+
+ @exposed
+ def RecvStats(self, address, stats):
+ '''Act on statistics upload'''
+ sdata = lxml.etree.XML(stats)
+ client = self.metadata.resolve_client(address)
+ self.process_statistics(client, sdata)
+ return "<ok/>"
+
+ def authenticate(self, cert, user, password, address):
+ print cert, user, password, address
+ return self.metadata.AuthenticateConnection(cert, user, password, address)
+
+ @exposed
+ def GetDecisionList(self, address, mode):
+ client = self.metadata.resolve_client(address)
+ meta = self.build_metadata(client)
+ return self.GetDecisions(meta, mode)
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index fa6605f1c..6086f4c2c 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -4,209 +4,19 @@
__revision__ = '$Revision$'
import logging
-import md5
-import select
-import socket
import sys
-import time
from xmlrpclib import Fault
-from lxml.etree import XML, Element, tostring
import Bcfg2.Logger
import Bcfg2.Options
import Bcfg2.Component
import Bcfg2.Daemon
import Bcfg2.Server.Plugins.Metadata
-from Bcfg2.Server.Core import Core, CoreInitError
+from Bcfg2.Server.Core import CoreInitError
+import Bcfg2.Server.XMLRPC
logger = logging.getLogger('bcfg2-server')
-def critical_error(operation):
- '''Log and err, traceback and return an xmlrpc fault to client'''
- logger.error(operation, exc_info=1)
- raise Fault(7, "Critical unexpected failure: %s" % (operation))
-
-class SetupError(Exception):
- '''Used when the server cant be setup'''
- pass
-
-class Bcfg2Serv(Bcfg2.Component.Component):
- """The Bcfg2 Server component providing XML-RPC access to Bcfg2 methods"""
- __name__ = 'bcfg2'
- __implementation__ = 'bcfg2'
- fork_funcs = ['GetConfig', 'GetProbes']
-
- request_queue_size = 15
-
- def __init__(self, setup):
- try:
- self.Core = Core(setup['repo'], setup['plugins'],
- setup['password'],
- setup['encoding'], setup['filemonitor'])
- except CoreInitError, msg:
- logger.critical("Fatal error: %s" % (msg))
- raise SystemExit(1)
-
- if 'DBStats' in self.Core.plugins:
- self.fork_funcs.append("RecvStats")
-
- famfd = self.Core.fam.fileno()
- events = False
- while True:
- try:
- rsockinfo = select.select([famfd], [], [], 15)[0]
- if not rsockinfo:
- if events:
- break
- else:
- logger.error("Hit event timeout without getting "
- "any events; GAMIN/FAM problem?")
- continue
- events = True
- i = 0
- while self.Core.fam.Service() or i < 10:
- i += 1
- time.sleep(0.1)
- except socket.error:
- continue
- try:
- Bcfg2.Component.Component.__init__(self, setup['key'],
- setup['cert'],
- setup['password'],
- setup['location'])
- except Bcfg2.Component.ComponentInitError:
- raise SetupError
-
- self.funcs.update({
- "AssertProfile" : self.Bcfg2AssertProfile,
- "GetConfig" : self.Bcfg2GetConfig,
- "GetProbes" : self.Bcfg2GetProbes,
- "RecvProbeData" : self.Bcfg2RecvProbeData,
- "RecvStats" : self.Bcfg2RecvStats,
- "GetDecisionList" : self.Bcfg2GetDecisionList
- })
-
- # init functions to be exposed as XML-RPC functions
- for plugin in list(self.Core.plugins.values()):
- for method in plugin.__rmi__:
- self.register_function(getattr(self.Core.plugins[plugin.__name__], method),
- "%s.%s" % (plugin.__name__, method))
-
-
-
- def get_request(self):
- '''
- We need to do work between requests, so select
- with timeout instead of blocking in accept
- '''
- rsockinfo = []
- famfd = self.Core.fam.fileno()
- while self.socket not in rsockinfo:
- self.clean_up_children()
- if self.shut:
- raise socket.error
- try:
- rsockinfo = select.select([self.socket, famfd], [], [], 15)[0]
- except select.error:
- continue
-
- if famfd in rsockinfo:
- self.Core.Service()
- if self.socket in rsockinfo:
- return self.socket.accept()
-
- def Bcfg2GetProbes(self, address):
- '''Fetch probes for a particular client'''
- resp = Element('probes')
- try:
- name = self.Core.metadata.resolve_client(address)
- meta = self.Core.build_metadata(name)
-
- for plugin in [p for p in list(self.Core.plugins.values()) \
- if isinstance(p, Bcfg2.Server.Plugin.Probing)]:
- for probe in plugin.GetProbes(meta):
- resp.append(probe)
- return tostring(resp, encoding='UTF-8', xml_declaration=True)
- except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- warning = 'Client metadata resolution error for %s; check server log' % address[0]
- self.logger.warning(warning)
- raise Fault(6, warning)
- except:
- critical_error("error determining client probes")
-
- def Bcfg2RecvProbeData(self, address, probedata):
- '''Receive probe data from clients'''
- try:
- name = self.Core.metadata.resolve_client(address)
- meta = self.Core.build_metadata(name)
- except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- warning = 'metadata consistency error'
- self.logger.warning(warning)
- raise Fault(6, warning)
- # clear dynamic groups
- self.Core.metadata.cgroups[meta.hostname] = []
- try:
- xpdata = XML(probedata)
- except:
- self.logger.error("Failed to parse probe data from client %s" % (address[0]))
- return False
-
- sources = []
- [sources.append(data.get('source')) for data in xpdata
- if data.get('source') not in sources]
- for source in sources:
- if source not in self.Core.plugins:
- self.logger.warning("Failed to locate plugin %s" % (source))
- continue
- dl = [data for data in xpdata if data.get('source') == source]
- try:
- self.Core.plugins[source].ReceiveData(meta, dl)
- except:
- self.logger.error("Failed to process probe data from client %s" % (address[0]), exc_info=1)
- return True
-
- def Bcfg2AssertProfile(self, address, profile):
- '''Set profile for a client'''
- try:
- client = self.Core.metadata.resolve_client(address)
- self.Core.metadata.set_profile(client, profile, address)
- except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError, Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
- warning = 'metadata consistency error'
- self.logger.warning(warning)
- raise Fault(6, warning)
- return True
-
- def Bcfg2GetConfig(self, address, checksum=False):
- '''Build config for a client'''
- try:
- client = self.Core.metadata.resolve_client(address)
- config = self.Core.BuildConfiguration(client)
- if checksum:
- for cfile in config.findall('.//ConfigFile'):
- if cfile.text != None:
- csum = md5.md5(cfile.text)
- cfile.set('checksum', csum.hexdigest())
- cfile.text = None
- return tostring(config, encoding='UTF-8', xml_declaration=True)
- except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- self.logger.warning("Metadata consistency failure for %s" % (address))
- raise Fault(6, "Metadata consistency failure")
-
- def Bcfg2RecvStats(self, address, stats):
- '''Act on statistics upload'''
- sdata = XML(stats)
- client = self.Core.metadata.resolve_client(address)
- self.Core.process_statistics(client, sdata)
- return "<ok/>"
-
- def _authenticate_connection(self, _, user, password, address):
- return self.Core.metadata.AuthenticateConnection(user, password, address)
-
- def Bcfg2GetDecisionList(self, address, mode):
- client = self.Core.metadata.resolve_client(address)
- meta = self.Core.build_metadata(client)
- return self.Core.GetDecisions(meta, mode)
-
if __name__ == '__main__':
OPTINFO = {
@@ -235,30 +45,14 @@ if __name__ == '__main__':
setup = Bcfg2.Options.OptionParser(OPTINFO)
setup.parse(sys.argv[1:])
-
- level = 0
- if setup['daemon']:
- Bcfg2.Logger.setup_logging('bcfg2-server',
- to_console=False,
- level=level,
- to_file=setup['filelog'])
- Bcfg2.Daemon.daemonize(setup['daemon'])
- else:
- Bcfg2.Logger.setup_logging('bcfg2-server',
- level=level,
- to_file=setup['filelog'])
-
- if not setup['key']:
- print("No key specified in '%s'" % setup['configfile'])
- raise SystemExit(1)
-
try:
- BSERV = Bcfg2Serv(setup)
- except SetupError:
- raise SystemExit(1)
- while not BSERV.shut:
- try:
- BSERV.serve_forever()
- except:
- critical_error('error in service loop')
- logger.info("Shutting down")
+ Bcfg2.Component.run_component(Bcfg2.Server.XMLRPC.bcfg2_server,
+ register=False, cls_kwargs={'setup':setup},
+ keyfile='/home/desai/tmp/cert2/b2-s/priv.key',
+ certfile='/home/desai/tmp/cert2/b2-s/cert.pem',
+ ca='/home/desai/tmp/cert2/ca-cert.pem',
+ )
+ except KeyboardInterrupt:
+ sys.exit(1)
+ sys.exit(1)
+