Our current solution uses a php script to check the permissions based on a token created from the file name and a timestamp, very similar to the secure download module available in lighttpd. The PHP script then reads the file and passes it to the user. This is far than optimal in terms of processing speed and memory consumption, so I satrted to search for a solution.
The first idea that came to my mind was using Ligttpd to serve those files with the secure download module, using apache as a proxy to a locally runinng insance of http://www.blogger.com/img/blank.gifLighttpd or using a dedicated server with Lighttpd to host the media files....yet another server to manage, not a good idea...
There must be something out there....if only mod_rewrite could do some string manipulation and calculate md5 hashes...as far as i know it does not, but wait a minute, we have RewriteMap!. The RewriteMap option within mod_rewrite allows the lookup of key/value pairs from plain files, db files or....external programs. The key is in this last feature. Let's see how we can do it.
1. Our token
As I said before we are using a token to control access to the files. This 40 bytes token consists of a md5 hash (32 bytes) plus a timestamp (8 bytes). The hash is calculated from the filename, the timestamp and a "secret" string.
token = md5(secret+filename+timestamp)+timestamp
a sample url to get the file could be:
http://www.example.com/secure/1fb5dcde52ec59f7308c301e5126395b4dd6f000/file.jpg
2. The RewriteMap script
Apache comunicates with the RewriteMap external program via stdin/stdout. Only one instance of the progrm is run, and it serves for all the key lookup. In order to use it we have to set it up in our apache config, for instance at vhost level:
RewriteEngine on
Rewritemap securedownload prg:/etc/apache2/scripts/securedownload.sh
And here is the script, at this point is just a shell script to try the concept, not ready for production:
#!/bin/bash
KEY=VerySecret
DURATION=18000
while read line; do
PREV_HASH=${line:0:32}
PREV_TIME=${line:32:8}
FILENAME=${line:40}
#calculate our hash
MD5=`echo -n $KEY$FILENAME$PREV_TIME|md5sum`
NEW_HASH=${MD5:0:32}
#Time check
NOW=`date +%s`
let TIME=0x${PREV_TIME};
#Add duration
TIME=$(($TIME+$DURATION));
if [ $TIME -ge $NOW ];then
#check hash
if [ $NEW_HASH = $PREV_HASH ]; then
echo $FILENAME
else
echo "NOACCESS.${FILENAME:(-3)}"
fi
else
echo "NOACCESS.${FILENAME:(-3)}"
fi
done;
The script get the complete url and validates the hash and compares the timestamp with the current time to see if we are within the allowed time frame, specified by DURATION. If all is ok then it returns the file name, so in this case the url will be converted to :
http://www.example.com/secure/file.jpg
If not valid it will return "noaccess.jpg", and we have two options: create that file and return it, or let apache return a 404 code,:not found.
3. The RewriteRule
And this is the .htaccess file inside the secure directory:
RewriteEngine on
#secure download via rewrite map
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.{40})/(.*)$ ${securedownload:$1$2}?SECURE=123asd[L]
#no direct access to files
RewriteCond %{QUERY_STRING} !^SECURE=123asd$
RewriteRule ^(.*)$ - [F,L]
See how we append the token and file name and pass it to the securedownload rewritemap if the url matches the pattern with the 40 bytes token and the filename.
To avoid direct access to the file we block it and force a forbidden response, unless we have the magic query string added by the previous rewrite rule. This trick is the only way I have found to set a "flag" that prevents blocking access to the file after internal reruns of the rewrite engine.
4. The results
Our tests with ab show that this method is 10x times faster than our previous php script. Again, remember that using .htaccess files inside directories might not be optimal.
And that's all, drop me a line if you need more details!
2 comentarios:
This is a great idea. Going a little further, would it be possible to use tokens (user id, or session coockies) to make those keys user specific and therefore even more safe? possible, and then how would you do this?
thanks,
It's user agnostic, but you can always control which users have access to the right tokens, all the security checks can be done at the application layer and for me that's more than enough. Having to validate a user token or cookie against an external source can add unnecesary complexity and latency, and will require more resources.
Publicar un comentario en la entrada