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.