Simple security for Drupal database settings

I host a number of Web sites for clients on a dedicated server, and most of the time these end-users don't want or need shell or even SSH access to their application backends. Server security is of course always an issue, but without end-users looking around the machine, things like database connection settings can reasonably be stored in plaintext. With only server administrators allowed to log in, there's not much point to hiding them anyway.

That changes when a client wants to be more hands-on in managing their own Web service. Most critical system files are protected in the filesystem, but what about those Drupal settings.php files?

Here's the problem: Apache must be able to read settings.php to connect Drupal to MySQL, but all Web server threads run as the same user. Theoretically, an unfriendly script on a different Virtual Host could quickly index the server's directory tree and hone in on Drupal. A few unfortunate database commands could mean real trouble.

One could theoretically run PHP as CGI, changing the runs-as user for each site. However the performance hit is significant (by some accounts 40 - ! - times slower) and it's overkill for what should be an easy problem.

The solution? Protect database credentials inside Apache's virtual host definitions, which are invoked by root as the server starts. Those files can be safely read-protected from prying eyes. And since environment variables set inside vhost definitions are limited in scope, other servers won't see them when examining their own environments.

Add a line similar to the following into your Drupal vhost:

SetEnv PHP_DRUPAL_KEY drupalpass

For simplicity's sake I'm just hiding Drupal's password here. You could have an environment for the user, too, or even the DB protocol itself.

Then, in settings.php, change the default DB handler to something like:

<?php
$db_url
= 'mysqli://mysql_user:' . getenv('PHP_DRUPAL_KEY') . '@mysql.local/db_name';
$db_prefix = '';

apache_setenv("PHP_DRUPAL_KEY","x");
unset(
$_SERVER['PHP_DRUPAL_KEY']);
?>

This general method is not new, but there are some extra security concerns to address: The final two functions are critical, since administrators with permission to eval() PHP inside Drupal could easily request environment variables and discover the DB password.

(Test a simple getenv() on the above variable inside a Devel PHP block if you don't believe me.)

According to this helpful note on PHP's putenv() documentation, environment variables set before page load are actually stored independently in at least two main sets of variables. The first function above resets apache's version of the variable, but that function doesn't touch $_SERVER, so we do.

(I couldn't find documentation on an apache-specific function that had a similar effect to unset() - if you know of one, leave a comment below.)

A final note: If you use Drush at the command line to manipulate your Drupal install, you'll need to export PHP_DRUPAL_KEY in your .profile or equivalent environment definition. But since environment variables are available in the CLI interface, too, this should work just fine. In a multisite environment, set a different environment variable for each site, unless their mysql passwords are the same.