Wednesday, June 1, 2016

SSL-enabled Django runserver development

It used to be the case that you could safely ignore SSL encryption in your web application up to the point where the thing goes live. I mean, who cares about man-in-the-middle attacks in your home network, right?

Not anymore.

Not only is it good practice to encrypt everything by default, but nowadays iOS apps enable app transport security out of the box. In short this means every unencrypted http connection just silently dies.

While it is possible to have a debug app build that includes exceptions to this rule, I think it's better to be as consistent between debug and release as possible. So let's get going - it's not actually complicated. You will need:
  • A (preferrably free) dynamic DNS provider or a proper DNS hostname pointing to your public IP address.
  • A valid SSL certificate for your public hostname. Letsencrypt is free and works well.
  • A web server acting as reverse proxy to Django. This is the main difference to a typical Django development setup. I'm using nginx.
  • Port forwarding from the public IP to your web server, if required.
Not too frightening.

First, make sure your web server is actually reachable under its public hostname. As a quick sanity check, configure it for SSL and let it serve some static files:


server {
    listen 443 ssl;

    server_name localhost your-public-hostname.example.com;

    ssl_certificate /etc/letsencrypt/live/your-public-hostname.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-public-hostname.example.com/privkey.pem;
    ... ssl hardening parameters omitted for brevity ...

    location / {
        root   /srv/helloworld;
        index  index.html index.htm;
    }
}

open https://your-public-hostname.example.com/ in your browser and check for SSL errors. I like to completely disable http by forwarding all requests to https:

server {
    listen 80;
    server_name localhost your-public-hostname.example.com;
    return 301 https://$host$request_uri;
}

sweet.

Now configure it to reverse proxy to Django. The cool part: you don't actually need a WSGI daemon for that - while Django's own runserver is not suitable for production, it's totally fine here. 

location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://localhost:8000/;
    proxy_buffering off;
}

(Add some source IP firewalling if desired though - a Django app in DEBUG mode should not be exposed to the public.)

And that's it! Not even a staticfiles setup is required (unless you choose so). Enjoy your SSL-enabled Django development!

2 comments:

  1. The same can be accomplished with ngrok[1]. Less hassle, but you are using someone elses infrastructure. One awesome thing about ngrok is the replay feature. You can send exactly the same request over and over again. It's not like you couldn't do that with a browser or the shell, but it's very convenient.


    [1] https://ngrok.com

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete