mod_rewrite

The TASK:
Add a new static content server running Apache

The CAVEAT:
Direct links to static content must vend the content
Content might exist on any (or multiple) servers
Old links pointing directly at the old server must still work
SSL Certificates must still validate

The SOLUTION:
Took me a couple hours to decide how to do this. Historically when faced with diminishing storage capacity, we would just buy a new server with a larger capacity, clone everything, and plop it in to replace the old one. Needless to say, this was neither efficient nor scalable. The solution came in the night: mod_rewrite.

For those not familiar with what Apache’s mod_rewrite does, it’s intended to allow you to use RegEx on URL requests and ‘rewrite’ them to something different. In this case, direct URLs to static content would look something like this:

/folder/sub/item.ext

What mod_rewrite will let us do is take that request, and mangle it into something infinitely more dynamic – like this:

/get_content.php?file=/folder/sub/item.ext

Yes, I understand that the mere idea of directly vending a by filename is terrifying to those with some appreciation for security. DO NOT just copy/paste this into your public-facing server and call it a day – the hardening has been stripped for brevity. First, let’s cover how we accomplish this rewrite.

In your Apache config (I suggest within a <Directory> definition), add the following directive:

   RewriteEngine On
   RewriteRule   "^/?(.*)$" "get_content.php?item=$1"

One Apache is reloaded, this rule will take any incoming request and instead interpret it as a parameter to our PHP script. This now empowers us to use the capabilities of PHP when vending our content. Once this is working, we just connect some hoses.

mkdir /server1; mount server1:/content /server1
mkdir /server2; mount server2:/content /server2

(it’s advisable to add these to /etc/fstab – less fires after an unexpected reboot)

Now, let’s create get_content.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
Seriously, filter your $_GET['item'].
If you let someone pull ../../../etc/shadow
or wordpress config.php, you deserve whatever happens to you.
*/
 
# Set your content directories:
$dirs = array(
   0 => '/mountpoint1/content',
   1 => '/mountpoint2/content'
);
 
foreach ($dirs as $id => $path) {
   if (file_exists($path . $_GET['item'])) {
      // vend the file
   }
}

And there we have it. All we have to do at this point is update the DNS record for our original server, and we’re golden!

Additional Notes:

If you are concerned about the file security of the get_content.php file, there are a few things you can do to mitigate the capabilities. The most obvious would be to hard-code accessible subfolders, or use a file naming convention that is secure and easy to validate (a-z, forward-slash, and ONE period for the file extension?). Another option would be to evaluate the full path, and ensure it’s a legal path before vending content from it. There are multiple solutions – use your head, and hack against it yourself. If you let someone type ‘&file=../../../etc/passwd’ and get away with it, you deserve whatever happens.

RewriteRule: I learned quickly that the ‘/?’ is important in the RewriteRule for the  original forward-slash. If your rule isn’t catching (going to 404 instead), make sure that you are accounting for the *possible* inclusion of that original forward-slash. After that, make sure your RegEx isn’t too rusty 😛

Leave a Reply

Your email address will not be published. Required fields are marked *