WARNING - OLD ARCHIVES

This is an archived copy of the Xen.org mailing list, which we have preserved to ensure that existing links to archives are not broken. The live archive, which contains the latest emails, can be found at http://lists.xen.org/
   
 
 
Xen 
 
Home Products Support Community News
 
   
 

xen-devel

[Xen-devel] [RFC][PATCH] Secure XML-RPC for Xend

To: xen-devel <xen-devel@xxxxxxxxxxxxxxxxxxx>
Subject: [Xen-devel] [RFC][PATCH] Secure XML-RPC for Xend
From: Anthony Liguori <aliguori@xxxxxxxxxx>
Date: Thu, 08 Jun 2006 21:13:17 -0500
Cc: Ewan Mellor <ewan@xxxxxxxxxxxxx>
Delivery-date: Thu, 08 Jun 2006 19:13:47 -0700
Envelope-to: www-data@xxxxxxxxxxxxxxxxxx
List-help: <mailto:xen-devel-request@lists.xensource.com?subject=help>
List-id: Xen developer discussion <xen-devel.lists.xensource.com>
List-post: <mailto:xen-devel@lists.xensource.com>
List-subscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-devel>, <mailto:xen-devel-request@lists.xensource.com?subject=subscribe>
List-unsubscribe: <http://lists.xensource.com/cgi-bin/mailman/listinfo/xen-devel>, <mailto:xen-devel-request@lists.xensource.com?subject=unsubscribe>
Sender: xen-devel-bounces@xxxxxxxxxxxxxxxxxxx
User-agent: Thunderbird 1.5.0.2 (X11/20060522)
Hi,

The following patch implements a secure XML-RPC protocol for Xend. Instead of using HTTPS with basic authentication and dealing with all that nasty OpenSSL/PAM integration, it just uses SSH. This gives you all the properties you want (great security and PAM integration) with very little code.

There are some minor issues so I'd rather it not be applied immediately. I'd like to get some feedback from people as to whether this approach is reasonable. A user-facing change is that now you can use the XM_SERVER environmental variable to specific an XML-RPC URI.

For instance:

XM_SERVER='ssh://root@xxxxxxxxxxxxxxxxxxxxx/RPC2' xm list

Runs xm list on a local machine but does all of the RPCs over a secure connection (prompting for passwords).

Thoughts?

Regards,

Anthony Liguori


# HG changeset patch
# User anthony@xxxxxxxxxxxxxxxxxxxxx
# 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 <policy>              Load binary policy into 
hypervisor"
 makepolicy_help = "makepolicy <policy>              Build policy and create 
.bin/.map files"
 labels_help     = "labels [policy] [type=DOM|..]    List <type> 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,
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-devel