Sunday, 14 October 2012

A secure home gateway on the Raspberry Pi in four parts. Part four, proxying to your devices


I have some very nifty devices lying around in my home:
  • A couple of computers
  • A very smart router with the Tomato firmware
  • A Raspberry Pi model B (the only one you can get right now)
  • A Popcorn Hour A200
Besides that, I have full control over a domain name (waleson.com.).

The amount of cool things you can do with this is enormous. However, until yesterday morning, these devices were working with most of their default settings (BOOOORING). Here's how I made it awesome in one evening.

Part four, proxying to your devices.

Objective: You want to access the devices inside from outside over the internet, securely

So far we used all of the devices above, except for the Popcorn Hour. This device offers, amongst other things, a torrent client over the web. If you go to http://192.168.1.133:8077, or whatever IP your popcorn hour has, you'll get redirected to http://192.168.1.133:8077/transmission/web, and you will see the Transmission Web UI. But that's only accessible over the internal network. It'd be nice if we could control our torrents over the web. (Torrents are great for downloading large files like open source linux distro's!).

So in our nginx configuration file, we add a location directive:

server {
server_name home.waleson.com;
listen 443 ssl;
error_log /var/log/nginx/home.error;
access_log /var/log/nginx/home.access;
ssl on;
ssl_certificate /usr/local/nginx/conf/home.waleson.com.crt;
ssl_certificate_key /usr/local/nginx/conf/home.waleson.com.key;
root /srv/www;
index index.html /index.html; 
location /transmission {
proxy_pass http://192.168.1.133:8077;
}
}
Now restart nginx:
/etc/init.d/nginx restart

And voila, we can access the transmission interface securely over the web from https://home.waleson.com/transmission/web. Great!

I said securely, but it's not really. No one can eavesdrop on the connection itself, but anyone will be able to access our torrent server! Not good, not good!

We need to password protect everything under /transmission.

To do that, we add two lines to the location directive:

server {
....
location /transmission {
auth_basic            "Are you l33t enough to torrent?";
auth_basic_user_file  htpasswd;
proxy_pass http://192.168.1.133:8077;
}
}
The auth_basic_user_file is a list of usernames and crypted passwords. It is important to realize that the path is relative to the main nginx.conf file in /etc/nginx.

You can easily create a login entry from bash like so:
printf "USER:$(openssl passwd -crypt PASSWORD)\n" >> /etc/nginx/htpasswd

To see what this does, run
printf "USER:$(openssl passwd -crypt PASSWORD)\n"
to display the output in the terminal itself. It will be:

USER:CRYPTEDPASS
Instead of displaying it directly, we want to append that line to a file, so we use >> /etc/nginx/htpasswd to append the line to the /etc/nginx/htpasswd file. If it does not exist, it will be created.

Restart nginx, and now when you go to https://home.waleson.com/transmission/web, you'll be prompted for a password.

We're not done yet.

Torrenting is fun, but what about accessing the router settings? As said earlier, this is something that would be cool to do, but you need security. We have https now, so if we work with passwords, they can't be eavesdropped. Let's make it so.

We could simply add another location like this:

server {
....
location /router {
auth_basic            "Are you l33t enough to access the router?";
auth_basic_user_file  htpasswd;
proxy_pass http://192.168.1.1;
}
}

But if you try this, you will get a 404. The request you made will be sent directly to the router. However, the router's web server has no idea what /router means. The admin interface is available under /, not under /router/. So instead, we'll have to use a location like this (notice the trailing slashes after /router and after the ip):

server {
....
location /router/ {
auth_basic            "Are you l33t enough to access the router?";
auth_basic_user_file  htpasswd;
proxy_pass http://192.168.1.1/;
}
}

This will strip the /router bit from all of the requests.

Another problem arises, unless you've been careless. Your router's admin interface will prompt you for a password, but nginx has already prompted you for a password. You can only specify one username/password for the entire connection though. If you chose the exact same username/password combination, nginx will probably pass the credentials along with the requests. This could be what you want, but the problem is that it is an implicit contract, which makes it hard to debug when things go awry. Furthermore, I'm not sure that the basic auth attributes aren't stripped from the request by nginx. Fortunately, we have two options to make this work anyway.
  1. No nginx authentication for /router
  2. Let nginx fill in the router credentials for you
If we omit the auth_basic settings for the /router/ location, we are prompted for credentials by the router. It has security so that's all good. Unfortunately, we have to use remember multiple passwords within our nice https portal.

I chose the second option, by putting the router's credentials in the nginx directive:
server {
....
location /router/ {
auth_basic            "Are you l33t enough to access the router?";
auth_basic_user_file  htpasswd;
proxy_pass http://192.168.1.1/;
proxy_set_header Authorization "Basic XXXXX";
}
}
Of course you shouldn't put XXXXX there, you should base64 encode the string "USER:PASS" and put it there. Something like this:


jt@augustine:~$ python
Python 2.7.3 (default, Aug  1 2012, 05:14:39)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> base64.b64encode("USER:PASS")
'XXXXX'
>>>
Put that XXXXX value in the nginx proxy_set_header and you're all set!

So there you have it, I can now safely manage my home devices from over the internet! Thank you for reading, and please be thankful for the RaspberryPi foundation and all of the open source packages I used.

A secure home gateway on the Raspberry Pi in four parts. Part three, free HTTPS to the rescue

I have some very nifty devices lying around in my home:
  • A couple of computers
  • A very smart router with the Tomato firmware
  • A Raspberry Pi model B (the only one you can get right now)
  • A Popcorn Hour A200
Besides that, I have full control over a domain name (waleson.com.).

The amount of cool things you can do with this is enormous. However, until yesterday morning, these devices were working with most of their default settings (BOOOORING). Here's how I made it awesome in one evening.

Part three, free HTTPS to the rescue

  1. Part one - Dynamic DNS
  2. Part two - nginx on the Raspberry Pi

Objective: You want to wear protection before we take this to the next level

Congratulations to you all, you now have a working http server on the RaspberryPi in your home, accessible from a nice url. Sure, it's not fast, and only serves static pages, but that doesn't matter.

I said earlier that opening up your router's web interface over http to the entire internet was a bad idea. Eavesdroppers are able to see your credentials and do all kinds of nasty stuff to your router. However, having access to your router from the outside would be very useful! And this is just one of the many cool things you want to do that requires authentication and security. We need an encrypted connection.

As many of you will now, StartSSL offers free SSL certificates to natural persons. I happen to be just that. This is absolutely fantastic. I went the the express lane on their site, filled out my info, and selected to create a certificate for waleson.com. I already had a paid SSL certificate for that, but you can select one additional subdomain in the class one certificate that you get for free. For my paid certificate I chose waleson.com and subdomain www.waleson.com.

For this new certificate I chose waleson.com and home.waleson.com. (I'm not going to use it for waleson.com, I just want the subdomain here).

Somewhere in the process you can create your own private key for which you need a passphrase. I created a random passphrase and put it in my password safe. Out of the StartSSL express lane, you end up with two files: the certificate (ssl.crt) and the key (ssl.key). You need to download two more: ca.pem and sub.class1.server.ca.pem from https://ca.startssl.com/certs/. Move all of these files to a location on the Raspberry Pi. Let's say /usr/local/etc/ssl. Do this:

cd /usr/local/etc/ssl
cat ca.pem sub.class1.server.ca.pem ssl.crt > crt.pem
This will create a certificate chain, from the root certificate of StartCOM to their intermediate certificate and then down to our certificate. We'll present this entire chain to the clients, so we conCATenate all these certificates into one large certificate chain file.

Now, in nginx, let's configure the server section (/etc/nginx/sites-enabled/home.waleson.com.conf) once more.

server {
listen 80;
rewrite ^(.*) https://$host$1 permanent;
}
server {
server_name home.waleson.com;
listen 443 ssl;
error_log /var/log/nginx/home.error;
access_log /var/log/nginx/home.access;
ssl on;
ssl_certificate /usr/local/etc/ssl/crt.pem;
ssl_certificate_key /usr/local/etc/ssl/ssl.key;
root /srv/www;
index index.html /index.html;
}
To everyone connecting on port 80 we say: "No way man. Be secure. Connect on port 443 and you'd better remember it, forever!"

Now when we try to restart the server:
/etc/init.d/nginx restart
we'll get asked for the passphrase because the server needs to access the private key.

You could type it in, but I don't recommend it. We don't want to do this every time we restart the server. So instead we go back to the certificate directory and store a decrypted version of the private key.
cd /usr/local/etc/ssl
mv ssl.key ssl.key.secure
openssl rsa -in ssl.key.secure -out ssl.key (Now you enter the passphrase)
/etc/init.d/nginx restart
Try opening the site again! If everything went alright, you will now see a secure version of the index.html file.

Objective three accomplished, we now have a protected connection.

Read on: part four - proxying to your devices

A secure home gateway on the Raspberry Pi in four parts. Part two, setting up nginx on the Raspi

I have some very nifty devices lying around in my home:
  • A couple of computers
  • A very smart router with the Tomato firmware
  • A Raspberry Pi model B (the only one you can get right now)
  • A Popcorn Hour A200
Besides that, I have full control over a domain name (waleson.com.).

The amount of cool things you can do with this is enormous. However, until yesterday morning, these devices were working with most of their default settings (BOOOORING). Here's how I made it awesome in one evening.

Part two, nginx on the Raspberry Pi.

  1. Part one - Dynamic DNS
Objective: You want to access the devices inside from outside over the internet. 

Naturally any other linux machine will do, but I have a Raspi lying around. It is energy efficient and fun to play with. Also, the Raspberry Pi is hot on the web, so it does wonders for your Hacker News article or your SEO leads. So if you don't have a Raspberry Pi, please do the substitution to whatever you have yourself, in your head.

I take it you have set-up Debian on a Raspberry Pi. Please use ssh keys as well: http://raspberrypi.stackexchange.com/questions/1686/how-do-i-set-up-ssh-keys-to-log-into-my-rpi

Connect your Raspberry Pi to the network. In your router, assign a static IP lease to the raspberry pi. That is, each time the raspberry connects to the network, the DHCP server in the router will assign it the same IP. Then, still in your router, forward ports 80 and 443 to the IP of the raspberry pi. Now all incoming traffic to your domain name on port 80 (http://) and 443 (https://) will be handled by the raspberry pi. However it does not listen on those ports yet. We need to set up a web server: nginx. Why not apache? Nginx is simpler, faster and most important on the Raspi: it has a low memory profile.

The process is rather simple. Set up nginx (all commands prefixed with sudo, or run as root):
apt-get update
apt-get install nginx
Your /etc/nginx/nginx.conf file is good to go out of the box. Do this:
cd /etc/nginx
YOUR_EDITOR sites-available/YOUR_DOMAIN.conf
in my case I substitued YOUR_EDITOR for vim, and YOUR_DOMAIN for home.waleson.com. Put this in the file:
server {
        server_name YOUR_DOMAIN;
        listen 80;
        error_log /var/log/nginx/home.error;
        access_log /var/log/nginx/home.access;
        root /srv/www;
        index index.html /index.html;
}
restart nginx:
/etc/init.d/nginx restart

Create a /srv/www directory and put an index.html file in there.

Try it! Go to http://YOUR_DOMAIN to see the index.html page!

Objective two accomplished!

Read on: part three - free HTTPS to the rescue

A secure home gateway on the Raspberry Pi in four parts. Part one, Dynamic DNS

tldr; How I set up this: DDNS to my home ip. Forward 443 & 80 to nginx on raspberry pi. Register free SSL certificates for own domain. basic_auth and proxy_pass to router and torrent box.

I have some very nifty devices lying around in my home:
  • A couple of computers
  • A very smart router with the Tomato firmware
  • A Raspberry Pi model B (the only one you can get right now)
  • A Popcorn Hour A200
Besides that, I have full control over a domain name (waleson.com.).

The amount of cool things you can do with this is enormous. However, until yesterday morning, these devices were working with most of their default settings (BOOOORING). Here's how I made it awesome in one evening.

Part one, dynamic DNS.

Objective: You want to access your home router from over the internet using a domain name. 

The first thing you'll need is an IP address. Well, your ISP gives you one. You can either remember this IP, or set up a DNS record to point a domain to it. We'll use DNS because we're humans. Remembering IP's is for strange people.

The problem is that the IP changes every now and then. Each time your modem reboots, you might find that your ISP has assigned you a new IP. Each time that happens you'll have to change the domain info again, which is tedious. The solution here is Dynamic DNS. Basically, it's like one of those terrible friends who lost a phone and sends you a message: "Hey Jouke, I'm xxxxxx and this is my new phone number because I lost my old one." The router says: "Hey DNS Server, my token is xxxxx and this is my new IP now, because I lost my old one". (As you can see, the DNS service is slightly more reliable than your friend, because it at least uses a random token instead of a name which is common knowledge. For security over 9000, you could consider giving all your friends a new password that they'll have to use in communication towards you.)

FreeDNS

In your router you can see something like this:


A screen where you can tell your router which dynamic DNS service to contact. I chose freedns.afraid.org because it seemed cool. I registered a free subdomain (jt.nl.am) and set it as an A record to my current IP address.


Where is the token I was supposed to use? It's sort of hidden under the Dynamic DNS page.


If you copy the Direct URL link and paste it into your router settings as in the Tomato screenshot a little up, you should keep only the part after the question mark.

Sidetrack: The token looks like a base64 encoded string, let's see what it entails:

jt@augustine:~$ python
Python 2.7.3 (default, Aug  1 2012, 05:14:39)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> base64.b64decode("THE-FREE-DNS-TOKEN")
'XXX:YYY'
I obfuscated everything in capitals, but this is the thing you'll get. Looks like a random userid XXX and password YYY. Nice.

Sorry for that sidetrack, I could not resist. Well, now we have an ugly domain name that will always point to our current IP. Great! But I already laid in some money for a nice domain...

The solution is simple. Create a new subdomain using your domain manager, whichever one you have. Mine is offered by a iXLHosting.nl. Let that subdomain be a CNAME record to your ugly domain name.

Nifty. Now home.waleson.com always points to my home router! Normally the router will not respond on port 80. If you open up the web interface to the external network, you can now access that from the outside. I wouldn't do that however, because the traffic is not encrypted and you're sending your basic auth settings in every request. Anyone listening in on your connection can then access your router as well.

Nevertheless, objective one accomplished:
jt@augustine:~$ dig -t A home.waleson.com

; <<>> DiG 9.8.1-P1 <<>> -t A home.waleson.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32749
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 4, ADDITIONAL: 0

;; QUESTION SECTION:
;home.waleson.com. IN A

;; ANSWER SECTION:
home.waleson.com. 14400 IN CNAME jt.nl.am.
jt.nl.am. 3600 IN A 83.87.81.43

;; AUTHORITY SECTION:
nl.am. 3600 IN NS ns1.afraid.org.
nl.am. 3600 IN NS ns2.afraid.org.
nl.am. 3600 IN NS ns3.afraid.org.
nl.am. 3600 IN NS ns4.afraid.org.

;; Query time: 277 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Oct 14 09:15:35 2012
;; MSG SIZE rcvd: 154
Read on: part two - setting up nginx on the raspberry pi