nginx-ssl-reverse-proxy

Secure remote access to your IoT devices

When you are hacking with IoT devices at home you get to face the challenge of accessing remotely to them, that is from outside your home network. I’m not saying your home network is a safe place, beware. But that thing outside, you know, “the Internet”, it’s so scary… Unfortunately, most IoT devices are just not ready for the jungle. Neither the commercial ones, nor the hacked ones you might have. I wouldn’t dare to open a port in my router to anything inside unless it’s encrypted. So what should we do?

Options

There exist different apps that let you control your devices from anywhere you might be. Think of Blynk for instance. You can use SSL with the blynk-library for ESP8266 or Arduino. You can also do MQTT over SSL. But either one eats so much memory you can do very little else. ESP8266 is barely capable of handling one SSL connection, don’t ask it to also perform as a webserver.

The other popular solution is to create a secured entry point to your network and the most common configuration for that is a reverse proxy using Nginx with SSL. This is what I have at home and since I’m doing some changes to it (soon on another post) I have decided to write down here all the steps required to configure a Raspberry Pi as a reverse proxy to access you0 IoT devices (or any other service you might have at home) from the outside in a secure way.

I’m using a Raspberry Pi 3 at home which hosts several services (Node-RED, InfluxDB, Grafana, Mosquitto,…). It uses Raspbian 8 based on Debian Jessie. I guess you could use older version of the RPi but this one is really fast and I have had no problems so far.

Nginx is a web server, just like Apache. I have been using it as a web server for the last 6 years maybe because it’s so much light weight and faster than Apache and you can pretty much anything you want with it. But we will not be setting up a webserver here, but a reverse proxy. Nginx stands out as a reverse proxy precisely because it’s so light weight it adds very little overhead to the communication.

What is a reverse proxy, you might say? A reverse proxy is a service that sits typically on the edge of your network, just behind your router, and retrieves resources from inside the network. While doing that it might do some other tasks like translating adresses, encrypting/decrypting the communication or hidding resources, to name a few. Here we are interested in the encrypting/decrypting part. Our reverse proxy will encrypt the communication going outside our network and decrypt it when it comes back to us. So even thou our devices do not “speak” SSL all the communications going outside our network throught the proxy will be encrypted on behalf of them.

 

Install Nginx

Ok, hands on. I’m assuming you have a machine with a Debian Jessie OS (be it a Raspbian 8 or any other). If you have some other OS there might be some differences on the commands below but the overall picture should be the same.

First thing to do is to install Nginx. It couldn’t be easier:

sudo apt-get update
sudo apt-get install nginx

Done. You now should have a Nginx server running on your machine. By default it exposes the contents of “/var/www/html/” on port 80 (HTTP, not secured). If you write the URL of the machine on your browser you should see a welcome message from Nginx. Nice.

Install Let’s Encrypt agent

Now let’s install the SSL certificates. We don’t want to use port 80 but 443 (HTTPS) but before disabling it, let’s see how to create and install the certificates. You can create them yourself or use the awesome service Let’s Encrypt. You can easily create and deploy Let’s Encrypt certificates using EFF Certbot. Certbot comes as a command line tool that automates everything.

if you want to install Certbot on a Debian Jessie you will need to install it from the backports repository. Backports are packages that are being “ported back” from newer versions of the OS. The latests stable Debian is 9.1.

echo "deb http://ftp.debian.org/debian jessie-backports main contrib" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8B48AD6246925553
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7638D0442B90D010
sudo apt-get update
sudo apt-get install certbot -t jessie-backports

Get SSL certificates for your proxy

Now we just have to tell Certbot to request and deploy a new certificate from Let’s Encrypt. First Certbot will create a key pair and start the communication with Let’s Encrypt providing the public key. Let’s Encrypt must validate that we are administrators of the domain we are requesting the certificate for. So they will provide a challenge: a certain file has to be created under that domain. Certbot will create that file for us, sign the nonce with the private key and tell Let’s Encrypt that we are ready for the challenge. If everything goes OK now Let’s Encrypt will register that our public key is authorized to manage the certificates for our domain. You can read a (probably better) explanation about the process in the how it works page in letsencrypt.org.

So basically Certbot takes care of the whole process. Only, we need to ensure that Let’s Encrypt will be able to read the challenge file from our server under that certain domain. Now, I have dynamic IP at home so I use DynDNS.org services to access my server at home (usually only for SSH communications). Dynamic DNS or even subdomains are not supported by most certificate authorities, but Let’s Encrypt does support them. It may happen that browsers complain about the certificate or you might face an error saying there have been too many requests for a certain domain (like homeip.org, managed by dyndns.org) when requesting the certificate.

To solve this second issue I map a subdomain in one of my public (true) domains to the dynamic domain as a CNAME. So imagine you have a “geek.homeip.net” dynamic domain always pointing at your home IP. And you are also the proud owner of “fibonacci.com” (valued at 55k€ right now). All you have to do is to create a CNAME record in your main domain pointing your dynamic domain:

geek.fibonacci.com.        CNAME  geek.homeip.net.

Of course if you map a domain to your dynamic one you might as well solve the first problem above. For the moment I’m living with it…

You will then issue a request to get the certificates for geek.fibonacci.com. On your home router you will also have to open a port to your proxy server. Opening a port simply means mapping the communications coming to a certain port to a machine inside your network. You will need to look for “NAT” on your router configuration and tell the router to redirect all communications on port 80 (HTTP) to your proxy server. You will need to do the same for port 443 (HTTPS).

Now you are ready to instruct Certbot to request the certificate:

sudo certbot certonly --webroot -w /var/www/html -d geek.fibonacci.com

If you are getting errors double check that you can reach your Raspberry Pi from outside your network. I normally place a file with some random content on “/var/www/html/hello.txt” and then I visit “http://geek.fibonacci.com/hello.txt” from outside my home network, disabling WiFi on my mobile phone, for instance. If there were no problems you can check where the certificate and key have been stored on the system like this:

[email protected]:~ $ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Found the following certs:
Certificate Name: geek.fibonacci.com
Domains: geek.fibonacci.com
Expiry Date: 2017-12-01 09:38:00+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/geek.fibonacci.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/geek.fibonacci.com/privkey.pem
-------------------------------------------------------------------------------

Configure the reverse proxy

Now that we have the certificate let’s configure Nginx to work as an SSL reverse proxy. I usually create a “/etc/nginx/sites-available/proxy” file with these contents:

server {

    # general server parameters
    listen                      443;
    server_name                 geek.fibonacci.com;
    access_log                  /var/log/nginx/geek.fibonacci.com.access.log;       

    # SSL configuration
    ssl                         on;
    ssl_certificate             /etc/letsencrypt/live/geek.fibonacci.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/geek.fibonacci.com/privkey.pem;
    ssl_session_cache           builtin:1000  shared:SSL:10m;
    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers                 HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers   on;
    
    location /livinglamp/ {

      # header pass through configuration
      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;

      # extra debug headers
      add_header              X-Forwarded-For $proxy_add_x_forwarded_for;

      # actual proxying configuration
      proxy_ssl_session_reuse on;
      proxy_pass              http://livinglamp.local/;
      proxy_redirect          default;
      #proxy_redirect          ^/(.*)$ /livinglamp/$1;
      proxy_read_timeout      90;

    }


}

There as some things here to note. First we specify the server name (geek.fibonacci.com) and we listen on port 443 (HTTPS). Remember you will have to open that port in your router pointing to your reverse proxy machine. Then we configure the certificates, the routes are the same we got previously when running “sudo certbot certificates”. Then we can define as many locations as devices or services we want to map under that domain. Here I just have one (/livinglamp/) mapped to an Itead S20 I have on my living room flashed with ESPurna. The local address of the device is used here (livinglamp.local). As you can guess, we are saying that all traffic comming to “https://geek.fibonacci.com/livinglamp/” should be redirected to “http://livinglamp.local/”. We are translating a public URL to a private one and changing from HTTPS to HTTP.

The tricky part here is the proxy_redirect option. It defines the “translation back” and will depend on how your device fetches other local resources like images, for instance. Also this configuration does not deal with query strings, the part of the URL that comes after the ‘?’.

Autorenew certificates

Once you have this working you can go back to your home router settings and delete or disable the map you did on port 80 (HTTP). The only ports you should open are those meant for encrypted communications (HTTPS, SSL, MQTT of SSL,…).

No. Don’t do that. You will need that port to autorenew your SSL certificates. Let’s Encrypt certificates have a validity of 90 days so you will be reneweing them often or forget and the whole set up  will fail. That’s why autorenewing is cool.

Leave that connection open but beware that anything under “/var/www/html” will be publicly visible. We will be using a crontab to try to renew them weekly, if they are not close to the expirity date it will just end silently. We will have to do it from the root crontab:

sudo crontab -e

The file will probably be populated with some explanation and maybe some crontab rules already. We will add these two lines at the bottom:

@weekly /usr/bin/certbot renew --quiet --no-self-upgrade -w /var/www/html/
@daily  service nginx reload

Basically we are asking the crontab to execute the autorenew procedure once a week, and since Nginx won’t reload the new certificates we are also reloading it once a day.

That’s all. I hope this helps you having a more secure experience accessing your IoT devices from the Internet. Of course any comments or ammends you might have, just use the comments section below.

CC BY-SA 4.0 Secure remote access to your IoT devices by Tinkerman is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

4 thoughts on “Secure remote access to your IoT devices

  1. Pingback: Using Google Assistant to control your ESP8266 devices - Tinkerman

    1. Xose Pérez Post author

      Yeah, I use ssh tunnels often but it’s not an option when using 3rd party services like IFTTT.

      Reply
  2. Pingback: Hacking the Sonoff RF Bridge 433 - Tinkerman

Leave a Reply (all comments are moderated, be patient)