# HG changeset patch # User anthony@rhesis.austin.ibm.com # Node ID 4de241a7e91a1e59b6db965f5d2ef10014f764e5 # Parent 4f1e39ec05d6ec711f9ba4a66a3653ed3e168311 Add support secure XML-RPC. This is done by multiplexing multiple SSH sessions over a single session (to avoid multiple password entries). Here are the changes: 1) Add support to xmlrpclib2.ServerProxy for ssh:// protocol 2) Add an xm serve command which proxies XML-RPC over stdio 3) Make xm look at the XM_SERVER variable to determine which XML-RPC protocol to use There are some issues that need to be addressed before inclusion. Namely: 1) Python moans about tempnam(). I don't think there's a better solution though. 2) A command *must* be executed to cleanup the ssh session on exit. I currently use __del__() which doesn't seem to make Python happy in certain cases. 3) I have done basic testing but not regression testing with xm-test diff -r 4f1e39ec05d6 -r 4de241a7e91a tools/python/xen/util/xmlrpclib2.py --- a/tools/python/xen/util/xmlrpclib2.py Thu Jun 8 15:51:39 2006 +++ b/tools/python/xen/util/xmlrpclib2.py Fri Jun 9 01:59:02 2006 @@ -24,14 +24,117 @@ import types from httplib import HTTPConnection, HTTP -from xmlrpclib import Transport +from xmlrpclib import Transport, getparser + from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler import xmlrpclib, socket, os, stat import SocketServer -import xen.xend.XendClient from xen.xend.XendLogging import log +import os, commands, getpass, termios, fcntl + +class SSH: + def __init__(self, host, user=None, askpass=None): + """Constructor for SSH object. + + This constructor allows you to specify a hostname, a username, + and an askpass program. username will default to the current + user and if askpass is not specified, the password will be + read (if necessary) from the controlling terminal.""" + + self.sock = os.tempnam() + self.host = host + sock = self.sock + if user == None: user = getpass.getuser() + self.user = user + + pid = os.fork() + if pid == 0: + if askpass: + f = open('/dev/tty', 'w') + os.environ['SSH_ASKPASS'] = askpass + fcntl.ioctl(f.fileno(), termios.TIOCNOTTY) + f.close() + + os.execvp('/usr/bin/ssh', ['ssh', '-f', '-2', '-N', '-M', '-S', + sock, '-a', '-x', '-l', user, host]) + + p,s = os.waitpid(pid, 0) + if s != 0: + raise OSError(s, 'Failed to start ssh server') + + def close(self): + """Closes an SSH object destroying the SSH server. + + This command must be called when the object is no longer + needed to ensure that the SSH server is destroyed properly.""" + + if self.sock: + commands.getstatusoutput('ssh -q -t -S %s -l %s -O exit %s' % + (self.sock, self.user, self.host)) + + # is this the best way to deal with this?? + def __del__(self): + self.close() + + def getcmd(self, cmd): + """Returns a command expanded to include SSH options. + + This function will add the appropriate ssh command and + options to the passed in command string. This is useful + if commands.getstatusoutput is not an appropriate exec + interface or if you want to handle error conditions in + your own way.""" + + return 'ssh -q -t -S %s -l %s %s %s' % (self.sock, self.user, + self.host, cmd) + + def runcmd(self, cmd, data=None): + """Runs a command using an existing SSH connection. + + This function will run the passed in command on a remote + machine and either return the output or raise an OSError + if the command exits with a non-zero status (or some + other failure occurs).""" + + cmdline = self.getcmd(cmd) + if data: + f = open("/tmp/stuff.txt", "w") + f.write(data) + f.close() + cmdline = "cat /tmp/stuff.txt | %s" % cmdline + else: + cmdline = cmdline + + o,s = commands.getstatusoutput(cmdline) + if o != 0: + raise OSError(o,s) + return s + +class SSHTransport(object): + def __init__(self, ssh): + self.ssh = ssh + + def request(self, host, handler, request_body, verbose=0): + p, u = getparser() + s = self.ssh.runcmd("xm serve", + """POST /%s HTTP/1.0 +User-Agent: Xen +Host: %s +Content-Type: text/xml +Content-Length: %d + +%s""" % (handler, host, len(request_body), request_body)) + lines = s.split('\n') + for i in range(len(lines)): + if lines[i] == '' or lines[i] == '\r': + s = '\n'.join(lines[(i + 1):]) + break + + p.feed(s) + p.close() + return u.close() # A new ServerProxy that also supports httpu urls. An http URL comes in the # form: @@ -68,13 +171,32 @@ class ServerProxy(xmlrpclib.ServerProxy): + def process_ssh_uri(self, uri, askpass): + uri = uri[6:] + parts = uri.split('@') + if len(parts) > 1: + user = parts[0] + uri = '@'.join(parts[1:]) + else: + user = None + parts = uri.split('/') + if len(parts) < 2: + raise ValueError("Invalid ssh:// URL '%s'" % uri) + host = parts[0] + path = '/'.join(parts[1:]) + ssh = SSH(host, user, askpass=askpass) + transport = SSHTransport(ssh) + return transport, 'http://%s/%s' % (host, path) + def __init__(self, uri, transport=None, encoding=None, verbose=0, - allow_none=1): + allow_none=1, askpass=None): if transport == None: (protocol, rest) = uri.split(':', 1) if protocol == 'httpu': uri = 'http:' + rest transport = UnixTransport() + elif protocol == 'ssh': + transport, uri = self.process_ssh_uri(uri, askpass) xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, verbose, allow_none) @@ -121,6 +243,7 @@ except xmlrpclib.Fault, fault: response = xmlrpclib.dumps(fault) except Exception, exn: + import xen.xend.XendClient log.exception(exn) response = xmlrpclib.dumps( xmlrpclib.Fault(xen.xend.XendClient.ERROR_INTERNAL, str(exn))) diff -r 4f1e39ec05d6 -r 4de241a7e91a tools/python/xen/xend/XendClient.py --- a/tools/python/xen/xend/XendClient.py Thu Jun 8 15:51:39 2006 +++ b/tools/python/xen/xend/XendClient.py Fri Jun 9 01:59:02 2006 @@ -18,6 +18,7 @@ #============================================================================ from xen.util.xmlrpclib2 import ServerProxy +import os XML_RPC_SOCKET = "/var/run/xend/xmlrpc.sock" @@ -25,4 +26,7 @@ ERROR_GENERIC = 2 ERROR_INVALID_DOMAIN = 3 -server = ServerProxy('httpu:///var/run/xend/xmlrpc.sock') +if os.environ.has_key('XM_SERVER'): + server = ServerProxy(os.environ['XM_SERVER']) +else: + server = ServerProxy('httpu:///var/run/xend/xmlrpc.sock') diff -r 4f1e39ec05d6 -r 4de241a7e91a tools/python/xen/xm/main.py --- a/tools/python/xen/xm/main.py Thu Jun 8 15:51:39 2006 +++ b/tools/python/xen/xm/main.py Fri Jun 9 01:59:02 2006 @@ -124,6 +124,7 @@ loadpolicy_help = "loadpolicy Load binary policy into hypervisor" makepolicy_help = "makepolicy Build policy and create .bin/.map files" labels_help = "labels [policy] [type=DOM|..] List labels for (active) policy." +serve_help = "serve Proxy Xend XML-RPC over stdio" short_command_list = [ "console", @@ -171,7 +172,8 @@ host_commands = [ "dmesg", "info", - "log" + "log", + "serve", ] scheduler_commands = [ @@ -833,6 +835,36 @@ arg_check(args, "log", 0) print server.xend.node.log() + +def xm_serve(args): + arg_check(args, "serve", 0) + + try: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(xen.xend.XendClient.XML_RPC_SOCKET) + f = sys.stdin + + content_length = 0 + line = f.readline() + headers = '' + while len(line) != 0: + if line.lower().startswith('content-length: '): + content_length = int(line[16:]) + headers += line + if line in ['\r\n', '\n']: + break + line = f.readline() + + buf = f.read(content_length) + s.sendall(headers + buf) + + buf = s.recv(4096) + while len(buf) != 0: + sys.stdout.write(buf) + buf = s.recv(4096) + s.close() + except Exception, ex: + print ex def parse_dev_info(info): def get_info(n, t, d): @@ -1072,6 +1104,7 @@ "dmesg": xm_dmesg, "info": xm_info, "log": xm_log, + "serve": xm_serve, # scheduler "sched-bvt": xm_sched_bvt, "sched-bvt-ctxallow": xm_sched_bvt_ctxallow,