Managing permissions on a shared Drupal hosting

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
You are viewing a wiki page. You are welcome to join the group and then edit it. Be bold!

I thought a bit about how to fix the problems regarding shared drupal hosting in the HM2 paradigm. I think I have a good solution and would like to have feedback on it before it gets implemented in full in HM2. But first things first...

The problem(s)

The way web hosting traditionally work is that all files are readable by the webserver and are thrown on the wire when the web client requests it. That's all fine and dandy when you're kernel.org and want your files out as much as possible, but when you start talking about Drupal, things get more complicated.

Read permissions

First, you don't want anyone to read your files. Not only the web client, but the other web applications shouldn't be able to read your (e.g.) settings.php file because that would mean they have read/write access to your database, which bypasses any access control you might have put on your nice Drupal website.

So basically, out of the box, LAMP hosting opens this can of worm

Write permissions

To support file uploads and other "fancy" things, the webserver needs to be able to write to some (and only some) parts of the filesystem. This is generally accomplished by allowing everyone (or just the webserver) write access to the files/ directory (wherever it is is mostly irrelevant here).

This creates an issue similar as the read permission issue: every other website can write to your files now.

Known solutions

Every shared hosting platform out there has their solution. I'll try to go around a few I know of. ( best solutions come first)

"real UNIX permissions" - apache mpm-itk - allows the use of mod_php

Home page: mpm-itk.

It is quite easy to install : for debian just "aptitude install apache2-mpm-itk" - it will remove apache2, and the apache2-mpm-prefork packages - this is OK and you can get them back with aptitude (uninstalling automatically the mpm-itk with that).

After install just put :

        <IfModule mpm_itk_module>
            AssignUserId user group
        </IfModule>

in the vhost definition file
and chown the web-root dir of the same vhost. From now on apache will run this vhost as the user:group you've set up in the vhost def file ...

This mechanism can be used to solve all above-mentioned problems the READ and WRITE one. The best way I (rsvelko) can think of is the following:

  • for each of your hosting clients you create a unix user - say "client_user". This user will have the primary group with the same name - "client_user" - in our example here.

  • for each of your sites, you are going to need a separate unix user - say "example-com" ('.' replaced by '-' ) with his primary group with the same name - "example-com"

  • in the vhost file of each site give
    AssignUserId example-com example-com (tricky part 1. here)

so apache runs restricted.

  • chown the files of example.com site to user:group = example-com:client_user (tricky part 2. here)
  • chmod these files to
    : rwxrwx--- (0770) : for the sites/default/files folder and beneath
    : r-xr-x--- (0550) : for all other folders in there (I am not sure if it should be g+w here ...)
    : r--r----- (0440) : for all files (like drupal php code files and such) (I am not sure if it should be g+w here ...)
    : r--r----- (0440) :for settings.php and r-xr-x--- (0550) for the sites/default folder (I am not sure if it should be g+w here ...)

explanation of tricky parts 1. and 2. : apache will use the example-com user to access your site's files and will have enough access to them - capable of writing ONLY where it is supposed to - in the files uploads dir. In the same time it will not be able to read/write the other sites. And due to the group ownership and permissions the client_user (a human using WinSCP or FTP) will have full access to the same files and all other sites owned by his group "client_user".

As for security/performance - the mpm-itk is tested enough (see the homepage - http://mpm-itk.sesse.net/). A recent tests done at the servers of segments.at showed minor to none performance hit - with "ab -n 100 -c 10 http://test.site.com/" we got 10.6 req/s (mpm-prefork) and 10.3 (mpm-itk) with 3 tests each ...

"real UNIX permissions" - suexec apache module and php in CGI mode

This is, I hope, the "proper" solution to the problem. This implies using Apache's suexec support in conjunction with PHP's CGI mode (optionnally running with FastCGI for better performance) to make the webserver run as a "real user".

This is how I think it should be implemented:

  • each site has a "user" assigned to it (say "userA")
  • each hostmaster "client" is actually a "group" (say "groupA")
  • the PHP environment runs in a userA:groupA environment (as opposed to www-data:www-data)
  • the settings.php file is mode 600 (read/write only by the user: that takes care of the read problem)
  • uploaded files can be similarly protected from writes
  • the rest of the files in Drupal are owned by the hostmaster user and group, unless we want to give access to themes and modules to the "user"

I think this reliably resolves all of the issues documented here, at the cost of some issues:

  • performance cost: FastCGI is slower than mod_php, especially if it needs to have seperate users for seperate domains
  • additionnal complications in the Apache setup: a configuration file needs to be created for each virtual domain (which we already do anyways, but some hosting platforms don't need that)
  • a file manager gets hard to implement in an eventual control panel (that would run as the hostmaster user). The only solution I could find here was to use some web2FTP gateway: the file browser just becomes an FTP client. Note that in the context of the "general shared hosting solution", the files could be owned by a user that would be the same username as the group and would therefore be a different user than the apache user, enhancing security...

Note that this approach also has advantages from a generic shared hosting point of view. "users" have various privileges: FTP access, SSH access, Drupal site, email, all in a central database with ACLs to control which user has which accesses. "groups" become the "control panel user": the group itself is the login/password to the control panel and can create users, only within its own group of course.

open_basedir

open_basedir is a PHP configuration setting that restricts access to certain parts of the filesystem only. The "home directory" of the "user" is usually the only place where the scripts should have access to. All PHP function calls are then restricted to that directory.

The main issue with that approach is that it doesn't really work properly. Without safe_mode (see below), it's fairly trivial to workaround the basedir restrictions using system() calls, for example. Also, this approach assumes that all potential PHP extensions (curl, imap, etc...) respect this functionality, which is very often not the case.

This theorically resolves the read and write issues.

For demonstration purposes, the following will add open_basedir to your vhost files. Please note, this only works between platforms. Create a drush include, say customstuff.drush.inc with the following code (rename the function).

<?php
//
// Implementation of hook_provision_apache_vhost_config()
//
// This will provide some protection against malicious PHP whereby the hacker
// tries to view files outside the platform. It is recommended that you give Aegir frontend
// it's own platform.
//
// The result will be extra lines of code in your vhost.
//
//
function customstuff_provision_apache_vhost_config($url, $options) {
 
// Allow sites on a platform to only scan that platform with PHP.
 
$allowed_dirs[] = "/tmp";                         // PHP upload location
 
$allowed_dirs[] = $options['docroot_path'] . '/'; // The platform path
 
$allowed_dirs[] = '/var/aegir/config/includes/'// Aegir's global.inc extension of settings.php
  
 
$output = "\n    # Added by " . <strong>FILE</strong>;
 
$output .= "\n    php_admin_value open_basedir " . implode(':', $allowed_dirs);
 
$output .= "\n";
  return
$output;
}
?>

safe_mode

When safe_mode is on, PHP checks to see if the owner of the current script matches the owner of the file to be operated on by a file function or its directory. Some hosting providers set the gid or the uid of hosted files to keep seperate website to access each other's file. This can easily be worked around by using system calls, which is why safe_mode also comes with a safe_mode_exec_dir directive that restricts allowed executables to those in a directory.

Safe_mode has similar issues has the open_basedir directive in that it is wildly misimplemented through the PHP codebase and extensions. Relying on it for security is considered bad practice and the extension will be removed in PHP6.

This theorically resolves the read and write permission issues.

environment variables

In a controled environment like HM, it's possible to set environment variables from the Apache configuration that is read as root by apache and can therefore be hidden from the www-data user. Those variables are then read by a modified settings.php to setup the database connection.

That resolves the read issue, but not the write issue.

Document Status

It's a bit rough for now, but you probably get the idea. I'm interested in getting comments, feedbacks, flames, similar experiences, edit this page!

Comments? Questions?

From jwthompson2 (Sep 26, 2008 - 16:17 EST - UPDATED @ 16:27 EST):

I don't have any problems with PHP under FastCGI since I've used it with LightTPD and been very satisfied by its performance running PHP apps. I instinctively prefer mod_php because I don't have to modify anything within a typical CentOS install and tweaking Apache performance is well understood and documented. Tuning FastCGI can bee a bit more hairy in my experience but isn't a huge problem. In a shared hosting environment I would expect you would want to use dynamically spawned FastCGI instances that have a defined life span. I think using FastCGI might be the best solution although it isn't the most popular because of its added complexity. I've used FastCGI particularly with Ruby apps as an alternative to proxying and it is a very good solution. mod_fcgid would be my recommendation if you were to go down that road and I wonder if pairing it with APC or XCache could negate some of the performance concerns, although in my experience those differences are not always as substantial as you might expect. A potential benefit I also see is that you could conceivably use Apache's Worker MPM which would help reduce Apache memory overhead since you've isolated the PHP app via FastCGI and I'm always for being able to use the Worker MPM.

Aegir hosting system

Group organizers

Group categories

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: