Reverse-proxying to a Drobo

This is an old post. It may contain broken links and outdated information.

Some time ago, I bought a Drobo FS to use as a home NAS device. My reasons for going with the Drobo instead of either rolling my own with something like FreeNAS or using a faster home NAS box from Synology or QNAP are complex, but they come down to a desire for an extremely simple-to-use NAS that I can throw hard disks into without worrying about doing anything at all with the disk layout (for a lot more on why and how, check part one of my two-part Ars Technica Drobo FS review). I gained many things coming to the Drobo FS from Windows Home Server v1, but one of the things I lost was the ability to easily and securely access the files on my NAS via the web.

The Drobo FS (and, indeed, most Drobo models) have the ability to run homebrew applications directly on the boxes themselves, though one barrier to simply porting a bunch of awesome utilities over and running them is that the Drobo FS runs on an ARM processor and aspiring Drobo app devs need to construct an ARM toolchain & cross-compiling environment in order to produce programs which will run. Drobo provides a small number of ported essential apps on their site, but the apps aren’t well-maintained or officially supported. There are a couple of web servers, an FTP daemon, an NFS daemon, an SSH daemon, and a few other things—enough to get you excited about the possibility of doing something really neat, but not enough to really do more than play around.

Enter DroboPorts, a site maintained by ambitious and skilled Drobo owner Ricardo Padilha, which offers an awesome variety of ported applications and libraries for the Drobo FS, including current versions of bash, OpenSSH, Python, PHP, lighttpd, Ruby, MySQL, and many others (along with instructions on building the aforementioned cross-compiling environment). The Drobo family of boxes will never be renowned for being speed demons or for having scads of free RAM or CPU to throw around on extra processes & applications like these, but there’s certainly enough capacity on board to let you run a web application or two, and this got me excited.

As I said, I gave up the ability to easily and securely get to the files on my NAS when I’m not at home. There are certainly tons of ways to add this capability back, but the way that I wanted to do it was to share the files directly off of the Drobo and access them via HTTPS. I’m not interested in setting up any kind of streaming server or anything like that—I just want to get to my files through a web browser when I’m not at home with some basic security so that I’m not inadvertently sharing with the world. I could do this with WHS, but doing it with the Drobo wasn’t previously possible because there were no way to get the Drobo FS to serve encrypted pages (no openssl libraries were available for it).

There are two ways around this—set up a reverse-proxy to the Drobo FS from a web server that does support HTTPS, or replace the Drobo-provided versions of Apache and/or lighttpd with versions that have SSL/TLS support compiled in. DroboPorts’ release of lighttpd 1.4.29 gives you the option to do the latter, which is awesome, but I still wasn’t keen on the idea of exposing my Drobo’s tender port 443 to the Big Bad Internet. However, lighttpd is perfectly happy as an upstream web server for Nginx, and with an Nginx web server already in place here at Bigdinosaur.org, it was time to make that puppy into a reverse proxy.

First things first: getting lighttpd working right

But first, I wanted to strip the Drobo-provided version of Apache off my Drobo FS and replace it with DroboPorts’ much slimmer port of lighttpd. Lighttpd (pronounced “lighty”) is a low-memory web server which is well-suited to running on the Drobo FS, which doesn’t have a lot of spare CPU or RAM. The Drobo-provided DroboApps repository contains a web-based admin tool which automates the downloading and installation of apps, but it requires Apache; fortunately, Ricardo has most awesomely preconfigured his lighttpd port to serve the DroboAdmin pages up, meaning you can purge Apache entirely from your Drobo FS after you’ve downloaded & installed lighttpd (a process which is done the same way as the official Drobo apps—by placing the downloaded .zip file in the DroboApps share and rebooting the Drobo FS).

Well, almost—we also need PHP to serve the DroboAdmin pages. Again, Ricardo has us covered with this port of PHP 5.3.6, which is ready to go immediately without needing any configuration. Even better, his lighttpd port immediately picks up on PHP’s presence and starts using it as needed, with no effort on our part. So, after Apache is gone and lighttpd & PHP are installed, the DroboApps page will automatically be available at http://droboname/droboadmin/.

It’s always a good idea to enforce privilege separation for processes which will be interacting with the outside world; that way, if the web server is somehow compromised and the attacker gains control of its process, the attacker isn’t rampaging around with root privilege. To that end, the next thing to do is to to create a new user on the Drobo via the Drobo Dashboard utility. Call the user whatever you’d like (mine is named “lighty”) and give it a long and complex password. You don’t need to worry about granting your user access to shares here, though, as the permissions which the Drobo Dashboard can modify are just the share permissions, not the actual file system permissions. You also don’t need to worry about having to type the password in later, as you’ll never be logging in as the user (though you should probably write it down somewhere safe just in case!).

Once the user is created, we need to edit lighttpd’s configuration files to have lighttpd launch under that user’s context. The first file is /mnt/DroboFS/Shares/DroboApps/lighttpd/service.sh, the script responsible for starting and stopping the lighttpd service, where we need to make this change:

#!/bin/sh
#
# lighttpd service

. /etc/service.subr

prog_dir=`dirname \`realpath $0\``
state_dir=${prog_dir}/var

name="lighttpd"
version="1.4.29"

pidfile=${state_dir}/run/lighttpd.pid
serverpem=${prog_dir}/etc/server.pem
conffile=${prog_dir}/etc/lighttpd.conf

### Add or uncomment these next two lines
user=lighty
group=lighty
...

Then, we need to alter the main lighttpd conf file at /mnt/DroboFS/Shares/DroboApps/lighttpd/etc/lighttpd.conf like this:

## check conf-available/*.conf for the configuration of modules.
## check conf-enabled/*.conf for enabled modules
var.server_root = "/mnt/DroboFS/Shares/DroboApps/lighttpd"
var.log_root    = server_root + "/logs"
server.errorlog = log_root + "/error.log"
var.conf_dir    = server_root + "/etc"
var.vhosts_dir  = conf_dir + "/vhosts"
var.state_dir   = server_root + "/var"
var.cache_dir   = state_dir + "/cache"
var.socket_dir  = state_dir + "/sockets"
server.pid-file = state_dir + "/run/lighttpd.pid"
server.document-root = server_root + "/www"
server.port     = 80

### Add or uncomment these two lines:
server.username  = "lighty"
server.groupname = "lighty"

If you’re logged into the Drobo FS with ssh, you can bounce the service by running ~/Shares/DroboApps/lighttpd/service.sh restart, and then running top or ps and looking for the lighttpd process. If things worked right, it will be running under lighty instead of as root.

Step the second: sharing stuff

The next thing to do is to give lighttpd something to share. If you previously had DroboAdmin installed and working under Apache and you’ve followed this guide, you’ll already be sharing out the DroboAdmin page at http://drobo/droboadmin/, and so now we want to share out some of your NAS files.

First, we’ll turn on directory listings. On most web servers you want this setting disabled, to keep people from poking through a directory’s contents, but in this particular case having the web server auto-generate your directory listings for you saves you from having to maintain lists of files in the directories you’re sharing. To get this setting enabled, ssh into the Drobo and create a symlink from the dirlisting.conf file in conf-available to conf-enabled:

cd /mnt/DroboFS/Shares/DroboApps/lighttpd/etc
ln -s ./conf-available/dirlisting.conf ./conf-enabled/dirlisting.conf

Before restarting lighttpd, you can edit dirlisting.conf and make changes, if desired:

##
## Enabled Directory listing
## This must be "enable" for directory listings to work
dir-listing.activate      = "enable"

## Hide dot files from the listing?
## By default they are listed.
## It is a good idea to set this to "enable" so hidden files remain hidden
dir-listing.hide-dotfiles = "enable" 

## list of regular expressions. Files that match any of the specified
## regular expressions will be excluded from directory listings.
## If you browse the NAS with a Mac, you you might want to add the following to
## this file in order to keep OS X's droppings from being visible. Also, the
## "NOSHOW" at the end is so that any file or directory name that includes that
## string will not be included in the listing.
dir-listing.exclude       = ( "~$", "Network Trash Folder", "Temporary Items", "NOSHOW" )

## Specify the url to an optional CSS file.
## Useful if you want to change how the directory listing looks.
dir-listing.external-css  = "/drobo/dir-styles.css"

We also need to make a quick change to lighttpd.conf to support directory listing and add this line somehwere in a clear section in order to complement the directory exclusion rules:

url.access-deny = ( "~", ".inc", "Network Trash Folder")

Bounce lighttpd with service.sh restart to make the changes effective.

Lighttpd installs itself with its web root set at /mnt/DroboFS/Shares/DroboApps/lighttpd/www, and so that’s where we’ll start. It’s a good idea to put an index.html file there, so you have something to land on and so that you can control what visitors see at the top level; or, you could leave the directory without an index file and let lighttpd simply show a directory listing. Either way, to get lighttpd to serve NAS files out of www, it’s time for some more symlinks!

First we need to make sure lighttpd knows that it’s okay to follow symlinks. As with directory listings, on most web servers you’d have this ability disabled because it could be used to allow the web server to serve up files located outside the web root—in fact, that’s exactly what we’re doing, and so we want the ability turned on. Make the following change in lighttpd.conf:

### Add or modify this directive to enable it
server.follow-symlink = "enable"

Restart lighttpd to make the chang effective, and then create symlinks inside the web root which lead to the directories you want to share over the web. For instance, if you had three Drobo shares named MyMoviesMyMusic, and MyApps and you wanted lighttpd to be able to serve those directories over the web, you’d do this:

cd /mnt/DroboFS/Shares/DroboApps/lighttpd/www
ln -s /mnt/DroboFS/Shares/MyMovies ./MyMovies
ln -s /mnt/DroboFS/Shares/MyMusic ./MyMusic
ln -s /mnt/DroboFS/Shares/MyApps ./MyApps

If you’re using an index.html file in www, which I’d recommend, you’d then edit that file to contain hyperlinks to the three symlinks you just added:

...
<a href="/MyMovies/">Click here to see my Drobo's movies</a>
<a href="/MyMusic/">Click here to see my Drobo's music</a>
<a href="/MyApps/">Click here to see my Drobo's applications</a>

One very important note about the hyperlink locations: we’re going to change them in a bit so that reverse proxying from another web server works correctly. If you don’t intend to do the reverse proxying thing, then don’t worry about it; if you do, read on.

If you wanted to call it done at this point, you could—your Drobo FS is now serving files via lighttpd, and you could edit the main lighttpd.conf file to add some basic htpasswd authentication (or just copy the authentication already applied to the DroboAdmin directory). But this only gets us part of the way to where we really want to be!

The third step: reverse proxying from Nginx

So here we are, with our Drobo sharing files via HTTP. Great! But if we’ve already got a web server on the same LAN serving files out, it would be a lot more convenient to access that web server for Drobo files and have that web server send requests on to the Drobo—that way, we can take advantage of the web server’s existing HTTPS set-up (if it has one) and it also adds an element of security in the event of a web server compromise, since at no point is anyone outside the LAN actively connecting to the Drobo. The web server itself talks to the Drobo unencrypted over the LAN and fetches files for the web users.

A web server used in such a fashion is called a “reverse proxy”, and Nginx is aptly suited to such a task. There are some configuration changes we need to make both on the Nginx web server and also on the Drobo’s lighttpd server, so let’s get to it.

First, we’re going to assume you already have Nginx configured for HTTPS; it’s not necessary, but one of the benefits of using a server like Nginx as a reverse proxy is that you can take advantage of SSL/TLS encryption, even if the server being proxied to (the Drobo’s lighttpd server, in this instance) is running HTTP only. You can still do all of what we’re about to do on HTTP, but you need to be aware that everything will go over the web in the clear, unencrypted.

Second, we’re going to also assume that you know how to set up basic authentication with an htpasswd file. Nginx has a good basic authentication module which is compiled in by default if you install Nginx from a repository, and there are web sites you can use to populate your htpasswd file with user names and passwords.

Once you’ve got your htpasswd file ready to go, modify the HTTPS server block of your Nginx site definition file and add the following:

### The location directive tells Nginx where to listen for traffic to proxy
location /drobo/ {

    ### We turn off access logging here because we'll be logging access on the
    ### Drobo instead—no need to clutter our logs
    access_log off;

    ### Enabling basic htpasswd-based authentication, so that users will be
    ### prompted for a username and password
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/drobo_htpasswd;

    ### A fast rewrite rule so that requests to /drobo/ are passed to the
    ### lighttpd back-end without "/drobo/" as part of the URI.
    rewrite /drobo/(.*) /$1 break;

    ### These directives are the meaty part, first telling Nginx to pass all
    ### traffic to this location on to drobo.yourlan.fqdn, which you'd obviously
    ### replace with your Drobo FS's fully-qualified domain name...
    proxy_pass http://drobo.yourlan.fqdn;

    ### ...and then setting headers on the forwarded traffic so that the Drobo
    ### knows the hostname the request went to and the IP address from which the
    ### request came. This is important for accurate logging on the Drobo,
    ### because otherwise the Drobo will log all traffic showing the reverse
    ### proxy's IP address as the requesting address!
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    ### Defining an index file—not necessary if you already have one defined
    ### elsewhere
    index index.html;
}

### This directive prevents the reverse proxy from accessing the DroboAdmin
### directory at all.
location /drobo/droboadmin { deny all; }

This establishes the ability to reverse proxy from Nginx to lighttpd on the Drobo FS and defines how and when traffic is passed. It’s important to note the rewrite rule above, which takes traffic bound for https://yoursite.com/drobo/whatever and modifies them so that the /drobo/ part is stripped out of the URI before it gets passed on to lighttpd on the Drobo. This is necessary because there is no /drobo/ directory on the Drobo—lighttpd will see incoming requests as relative to its web root, such that if your remote web user wants yoursite.com/drobo/mymovies/something.avi, lighttpd needs to actually serve up /mymovies/something.avi, not /drobo/mymovies/something.avi, because the latter doesn’t exist. The simple rewrite rule above ensures that this is handled correctly when Nginx sends on requests to lighttpd.

Now we need to fix up lighttpd so that it knows what to do with incoming reverse proxy connections from the Nginx web server. Log back onto the Drobo with ssh (or browse to the right place under the DroboShare shared folder with Finder or Explorer) and first edit lighttpd.conf like this:

...
server.modules = (
    "mod_access",
    "mod_alias",
    "mod_auth",

    ### Uncomment the next line, if it's commented out
    "mod_extforward",
...
)

### Add the following two items below the server.modules block, replacing
### "W.X.Y.Z" with the IP address of the reverse proxy server
    extforward.headers = ("X-Forwarded-For")

    extforward.forwarder = (
        "W.X.Y.Z" => "trust"
    )

Almost done! The penultimate change we need to make is to ensure lighttpd’s access log shows the actual IP of the remote host asking for files, rather than logging all the requests as coming from the reverse proxy. To do this, edit lighttpd’s access log configuration file at ~/Shares/DroboApps/lighttpd/etc/conf-enabled/access_log.conf and change the access log’s format to this:

### Modify the "accesslog.format" line so that it looks like this:
accesslog.format = "%{X-Forwarded-For}i %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\""

This will give you lighttpd error logs that look mostly similar to Nginx’s logs. Restart lighttpd to make the configuration changes go live.

Final steps: cleaning up

Remember those hyperlinks in the lighttpd web root? We need to change them so that instead of /MyMovies/ and so on, they instead read /drobo/MyMovies. The drobo portion of the path is necessary because even though the index file is dished up by lighttpd on the Drobo, the requests to access those locations will come from remote hosts hitting yoursite.com/drobo/. Without /drobo/ in the hyperlink paths, the Nginx web server would try to access those locations on itself rather than proxying the requests on. Again, the small rewrite rule we added to the Nginx site definition a bit earlier will tell Nginx to strip out the drobo part of the URI when requests are passed back to lighttpd and make everything okay. The links should look like this:

<a href="/drobo/MyMovies/">Click here to see my Drobo's movies</a>
<a href="/drobo/MyMusic/">Click here to see my Drobo's music</a>
<a href="/drobo/MyApps/">Click here to see my Drobo's applications</a>

And that really is it! You should now be able to hit your Nginx server from the web via HTTPS, navigate to https://yoursite.com/drobo/ and be magically connected to your Drobo FS. Additionally, the lighttpd logs on your Drobo at ~/Shares/DroboApps/lighttpd/logs should correctly show remote hosts’ IP addresses instead of the reverse proxy’s LAN IP address.

This guide took most of the day to write, and if you find any mistakes in it, please let me know!