Django deployment, FastCGI and UNIX-domain sockets

For various (mostly security) reasons I prefer to deploy Django applications as separate processes, with web server and actual application running under different UIDs, communicating with each other via FastCGI or SCGI socket. This way application is safer from e.g. vulnerable PHP scripts running within the web server process – it’s pretty common for the attackers to use PHP vulnerabilities to read files accessible by the web server itself (for smaller sites it’s normal to use a single web server installation for many different applications).

For security reasons I also prefer to use UNIX-domain sockets over TCP sockets (only if web server and the application are deployed on the same machine, obviously). With TCP sockets in use, any process may connect to the FastCGI/SCGI socket and effectively bypass the web server running in front of the application. What if e.g. the web server itself is used to perform user authentication and authorization? With UNIX-domain sockets one can protect the application by means of regular UNIX permission mode.

Such deployment model is perfectly feasible with Django, as it uses flup internally for a WSGI to SCGI/FastCGI bridge, deployment procedure is described in the Django documentation. Following the manual, application process can be started e.g. with the following command line (note that I do not daemonize the application, as I prefer to use e.g. daemontools to supervise the application process):

./ runfcgi protocol=scgi socket=~/django/testapp.sock daemonize=false

Unfortunately, the socket gets created with permission mode that do not allow any user other than the application process owner to write to the socket:

czajnik@lapsko:~/django$ ls -l testapp.sock 
srwxr-xr-x 1 czajnik czajnik 0 2010-08-29 20:49 testapp.sock

Obviously, this is not the way to go – web server will not be able to write to such a socket. One way to overcome the problem is to manually change the permissions mode and/or the uid/gid of the socket after the application is started, but this is far from perfect.

Digging in the source code I’ve realized that the flup module itself allows for specifying the umask value used while creating the socket, but this possibility is not used by Django. I’ve modified the code to pass 0 as socket creation umask, effectively making the socket accessible for every process:

diff -ruN Django-1.2.1.orig/django/core/servers/ Django-1.2.1/django/core/servers/
--- Django-1.2.1.orig/django/core/servers/	2010-08-29 21:00:37.000000000 +0200
+++ Django-1.2.1/django/core/servers/	2010-08-29 21:06:32.000000000 +0200
@@ -177,6 +177,7 @@
         fp.write("%d\n" % os.getpid())
+    wsgi_opts['umask'] = 0 
     WSGIServer(WSGIHandler(), **wsgi_opts).run()
 if __name__ == '__main__':

To control the accessibility I place the socket alone inside a designated directory and use directory permission mode to control who can access the socket. In my particular case the web server runs under www-data user/group. Directory where SCGI socket is created has the following permission mode:

czajnik@lapsko:~/django$ ls -ld socket
drwxr-x--- 2 czajnik www-data 4096 2010-08-29 21:10 socket
czajnik@lapsko:~/django$ ls -l socket/testapp.sock 
srwxrwxrwx 1 czajnik czajnik 0 2010-08-29 21:10 socket/testapp.sock

This works, but is not really a nice solution. Ideally, Django should allow for settings any socket permission mode from a command line. I’d even more prefer a model where application is started with UID 0, creates the socket owned by any configured UID/GID/permission mode, and then drops the root privileges.

Perhaps the best option is to drop flup altogether and switch to another WSGI host – uWSGI is an interesting option. It allows for chrooting, setting arbitrary socket permission mode, changing process UID and GID (if started as root), and much much more. However it doesn’t seem very popular yet, I’m yet to evaluate its quality.

As usual, any suggestions welcome!