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:
xose@acrux:~ $ 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;# WS Headers
# websocket support, thanks to Miguel Otero
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;
}
}
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.
"Secure remote access to your IoT devices" was first posted on 02 September 2017 by Xose Pérez on tinkerman.cat under Learning, Tutorial and tagged arduino, blynk, certbot, debian, dyndns, encrypted, esp8266, grafana, influxdb, let's encrypt, mqtt, nginx, node-red, proxy, raspberry pi, reverse proxy, ssl.