Wednesday, March 27th, 2013

Layering SSL over the XMLRPCServer

I really enjoy how simple it is to use xmlrpclib over other APIs such as REST. I also understand that it's insecure, as it does not support authentication as REST APIs do. Although there should technically be a way to implement an authentication layer over XML-RPC. The SimpleXMLRPCServer class which ships in Python's standard library doesn't natively support SSL socket encryption. For private XML-RPC Services, which I do not want otherwise exposed, especially over plain text, SSL encryption is required. I also prefer a stand-alone server over piggybacking on an SSL enabled web-server. This enables me to choose a random port, and create services which can otherwise monitor the web service remotely. How can you monitor a web service using a web service, if the web service is having trouble... Also since the xmlrpclib module ships in the Python standard library, and supports SSL/HTTPS servers, it makes it a great light-weight protocol to use anywhere Python can be installed. I have Python installed on my Symbian smartphone, and both my Android phone and tablet. Running a quick script from my home screen yields quick results from my XML-RPC service. Anyways, enough of my rambling and justifications for using XML-RPC over a more popular protocol... Here's the class you came here to steal from my blog:

from SocketServer import TCPServer
import ssl
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCDispatcher, SimpleXMLRPCRequestHandler
try:
    import fcntl
except ImportError:
    fcntl = None

CERT_FILE = '/home/kveroneau/cert.pem'

class SSLServer(TCPServer):
    def get_request(self):
        newsocket, fromaddr = self.socket.accept()
        connstream = ssl.wrap_socket(newsocket, server_side=True,
                                     certfile=CERT_FILE, keyfile=CERT_FILE,
                                     ssl_version=ssl.PROTOCOL_SSLv23)
        return (connstream, fromaddr)

class SimpleXMLRPCServer(SSLServer,
                         SimpleXMLRPCDispatcher):
    """Simple XML-RPC server.

    Simple XML-RPC server that allows functions and a single instance
    to be installed to handle requests. The default implementation
    attempts to dispatch XML-RPC calls to the functions or instance
    installed in the server. Override the _dispatch method inhereted
    from SimpleXMLRPCDispatcher to change this behavior.
    """

    allow_reuse_address = True

    # Warning: this is for debugging purposes only! Never set this to True in
    # production code, as will be sending out sensitive information (exception
    # and stack trace details) when exceptions are raised inside
    # SimpleXMLRPCRequestHandler.do_POST
    _send_traceback_header = False

    def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
                 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
        self.logRequests = logRequests

        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
        SSLServer.__init__(self, addr, requestHandler, bind_and_activate)

        # [Bug #1222790] If possible, set close-on-exec flag; if a
        # method spawns a subprocess, the subprocess shouldn't have
        # the listening socket open.
        if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
            flags |= fcntl.FD_CLOEXEC
            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)

if __name__ == '__main__':
    print 'Running XML-RPC server on port 8000'
    server = SimpleXMLRPCServer(("localhost", 8000))
    server.register_function(pow)
    server.register_function(lambda x,y: x+y, 'add')
    server.serve_forever()

There must be a more elegant way of doing this, but I was unsuccessful with creating a Mixin that could simply overwrite TCPServer.get_request(). If you know of a better way of implementing this code, please send it my way, and I will update this post and credit you for your assistance. To generate the PEM file mentioned above, use this command-line:

kveroneau@sys1:~$ openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
...++++++
..................................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CA
State or Province Name (full name) [Some-State]:Manitoba
Locality Name (eg, city) []:Winnipeg
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Veroneau.net
Organizational Unit Name (eg, section) []:I.T.
Common Name (eg, YOUR name) []:Kevin
Email Address []:***HIDDEN***

A bonus of this, is that you can use SSLServer class to replace any TCPServer class and add SSL to any existing program that uses TCPServer. For non-light weight programs, consider using Twisted, it is a much more featureful Python package for creating and deploying networked applications.

About Me

My Photo
Names Kevin, hugely into UNIX technologies, not just Linux. I've dabbled with the demons, played with the Sun, and now with the Penguins.




Kevin Veroneau Consulting Services
Do you require the services of a Django contractor? Do you need both a website and hosting services? Perhaps I can help.

This Month

If you like what you read, please consider donating to help with hosting costs, and to fund future books to review.

Python Powered | © 2012-2014 Kevin Veroneau