Setting up a HTTPS server in Python (WSGI)

The situation

Setting up a HTTP server in Python is quite easy. You overload the handlers.CGIHandler class, and Bob's your uncle... You have to overload the run function, and that's about it.

def run(self, host='localhost', port=8000):
    # Initialize the server deamon
    # HTTP    self._httpd = simple_server.make_server(host, port, self.__call__)

    print("Serving on port {0}...".format(port))

    # Start the server    self._httpd.serve_forever()

Nothing to it, right?

So, you'd expect that setting up a HTTPS server would also be a piece of cake... but it isn't that straightforward. Luckily, for you, I have spent some time to make this easy as frig.

The idea is to make a SSL socket, and to do that, you have to use the wrap_socket command... but where?

Look at this line of code from the run function

    self._httpd = simple_server.make_server(host, port, self.__call__)

When you open the simple_server package, you see that there's an optional server_class argument.

The default WSGIServer overloads the HTTPServer class which overloads the TCPServer class which ... creates a socket we can wrap.

Now, the game plan becomes clear. Overload the TCPServer class to wrap the socket and propagate our overloaded class upward. Easy.

I'll leave you with ...

The code

class SSLServer(TCPServer):
    # Overload the __init__ function, the rest is inherited from TCPServer    
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): TCPServer.__init__(self, server_address, RequestHandlerClass) try: context = SSLContext(PROTOCOL_SSLv23) context.load_cert_chain('/path/to/cert.pem', '/path/to/key.key', 'passphrase') except SSLError as e: print(e) self.socket = context.wrap_socket(self.socket, server_side=True) class HTTPSServer(SSLServer): # Copy the code from HTTPServer but use SSLServer instead of TCPServer
    allow_reuse_address = 1    # Seems to make sense in testing environment
    def server_bind(self):
        """Override server_bind to store the server name."""        SSLServer.server_bind(self)
        host, port = self.server_address[:2]
        self.server_name = getfqdn(host)
        self.server_port = port


class WSGISServer(HTTPSServer):
    # Copy the code from WSGIServer but use HTTPSServer instead of HTTPServer
application = None def server_bind(self): """Override server_bind to store the server name.""" HTTPSServer.server_bind(self) self.setup_environ() def setup_environ(self): # Set up base environment env = self.base_environ = {} env['SERVER_NAME'] = self.server_name env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['SERVER_PORT'] = str(self.server_port) env['REMOTE_HOST']='' env['CONTENT_LENGTH']='' env['SCRIPT_NAME'] = '' def get_app(self): return self.application def set_app(self,application): self.application = application
Nearly there. All we have to do now is pass the optional argument to the make_server function.

def run(self, host='localhost', port=8000):
    # Initialize the server deamon    # HTTPS    self._httpd = simple_server.make_server(host, port, self.__call__, server_class=WSGISServer)

    print("Serving on port {0}...".format(port))

    # Start the server    self._httpd.serve_forever()

Done.

OpenSSL

Creating the cert.pem and key.key files.

As you have probably noticed, the SSL part contains some files we need to provide with a valid passphrase.

context.load_cert_chain('//cert.pem', '//key.key', 'passphrase')

I got that from the OpenSSL Cookbook and it's quite easy, just follow the steps.

(1) generate a strong private key,
(2) create a Certificate Signing Request (CSR) and send it to a CA
(3) install the CA-provided certificate in your web server.

Let's do that.

1- Generate private key
openssl genrsa -aes128 -out key.key 2048
This is where you establish the passphrase.

check
cat

2A- Generate Certificate Signing Requests
openssl req -new -key key.key -out cert.csr

check
openssl req -text -in cert.csr -noout

2B- Signing Your Own Certificates
openssl x509 -req -days 365 -in cert.csr -signkey key.key -out cert.crt

check
openssl x509 -text -in cert.crt -noout

2C- Create PEM
openssl x509 -inform PEM -in cert.crt > cert.pem

... and that's about all there is to it.

I still have to investigate how to load the certificate without putting the passphrase in the code, but I'm sure there is something out there.

Comments

Popular posts from this blog

A recipe for failure

Ubuntu - Auto-mount an encrypted drive

Spaghetti code