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
Post a Comment