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
|