_user/_web strategy of DreamHost website deployment -- DreamHost best practices


 

Introduction

When I first signed up for DreamHost account, I understood, but wasn't overly fond of the way they ran web requests.

All requests to a deployed website on DreamHost are made in the context of the user owning the website.  That is, if a website is deployed under user 'X', calls into that website are performed as user 'X'.  This allows DreamHost to isolate security breaches to the user of the (defective) website.  However, this also has security implications for the individual websites.  If there is an exploitable defect within a particular website and a site is compromised,  files within that software could be modified as the call is done as the owning user.  Setting restrictive permissions (i.e. read-only) on the site's file helps, but doesn't completely mitigate the issue, as the files are owned by the calling user and (in a truly compromised environment) the file permissions could be modified.  In a traditional (non shared) web hosting system, this would not be an issue as web-initiated calls would be made by the (highly restricted) user (e.g apache, httpd, etc.) with permission to read (and not write) only those files required to serve the web page.

What follows is the scheme with which I came up to work around this issue.  In this discussion, I'll talk about Drupal sites; but the scheme is the same regardless of what files are deployed on a site.
 

The Scheme Summary

The scheme involves creating two users, the "user" user and the "web" user.  The "user" user will own the files comprising the website.  Those files will reside in a subdirectory of the "web" user's home directory.  These directories and files will be chmod'ed as group readable but not group writable (save those files that will be edited by the website and directories that will be written to.)  The "web" user will be the website "run as" user (as defined in the DreamHost domain edit screen).  Because the website directories/files were group readable (only), the calling user (the "run-as" user) can read, but not write access to the site's files, and the site functions correctly.  The site's files cannot to be modified during a web call.  This returns functionality to a semblance of a traditional web scenario.
 

Implementing It

Note: in this discussion I use the naming convention 'site_user' for the "user" user and 'site_web' for the "web" user where the token 'site' identifies the site or sites being hosted by that user pair.  For this scheme to work, the "Enhanced User Security" must be turned off for both users on the user edit screen in the DreamHost panel.  Note that this whole scheme works because DreamHost puts all users for the same account on the same physical server.  It is also possible to have your primary account be the "user" user rather than creating a new user for this role.  If electing this, just replace your primary user's username for 'site_user' in the steps below.

Here are the steps I followed:

  1. Start by creating the two user accounts, 'site_user' and 'site_web' in the DreamHost control panel, both with simple-to-use passwords.  (Changing their passwords will be done at the end.)  DreamHost doesn't allow using "password" as a password, so I use "wordpass". The users should be set up as 'shell' users (shell type 'bash').  Ensure that 'Enhanced Security' is not selected.  After saving them, edit them again and click the 'disable ftp' checkbox and re-save.  (I don't know why this option is not present initially.)
     
  2. Log in as site_web and the user's home directory to be group-writable.
    $ ssh site_web@<site domain>
    ....authenticate (password based authentication -- password is from step 1, e.g. "wordpass")
    $ chmod g+w ~
    $ exit
     
  3. As user site_user, create a directory in site_web's home directory called "webroot" and set the permission to 0751. Then, create a symlink to this new directory in site_user's home directory. This will allow site_user to conveniently access the files comprising the websites.
    $ ssh site_user@<site domain>
    ....authenticate
    $ cd ~site_web
    $ mkdir webroot
    $ chmod 0751 webroot
    $ cd
    $ ln -s ~site_web/webroot .
    $ exit

    (At this point, site_user can create new site files (or subdirectories) in ~/webroot.)
     
  4. Now, remove the group writable permission from site_web's home directory, resetting it to its initial state. 
    (This is necessary to enable the .ssh by the sshd (which is central to my scheme of using only ssh to switch between users.)  This obscure bit of ssh functionality cost me an hour of beating my head against a wall.)
    $ ssh site_web@<site domain>
    ....authenticate
    $ chmod g-w ~
    $ exit
     
  5. Load the files for the new website.  This step obviously varies based on the site in particular.  Once the files are in place, set permissions of all subdirectories/sub-files to be group-readable; but not group writable.  This will prevent any compromised web-call from modifying any website files.  If there are any files/directories in the site's tree that need to be writable by the site, those need to be group writable.  An example is the 'files' directory on a Drupal site.
    $ ssh site_user@<site domain>
    ...authenticate
    $ cd webroot
    (I put my drupal files in a 'drupal' subdirectory off the 'webroot' directory)
    $ mkdir drupal
    $ cd drupal
    $ mkdir <site domain>
    ... continue to load files in newly created directory.
    $ cd <site root directory of site, e.g. "webroot/drupal/<site domain>">
    $ chmod 0751 .
    $ find . -type d -exec chmod 0751 {} \;
    $ find . -type f -exec chmod 0644 {} \;
    (now give write permission to any directories that need to be write-enabled.  I set my Drupal files directories to to sites/<site name>/files
    $ chmod g+w sites/<site domain>/files
    $ exit
     
  6. Now that the website the files are in place, create the domain entry for the new site in the DreamHost control panel.
    Set the owning user as 'site_web'.  The 'web directory' (the directory from which the site is served) should be under the 'webroot' tree.  (E.g. in the example above it would be "/webroot/drupal/<site domain>".  It is important to not create the domain entry before placing the files (or at least creating the top-level directory) as DreamHost will create an empty directory for you as the run-as user (in this case site_web) if it does not already exist.  This would break the sceme -- we want site_user to own that directory (and all subdirectories/files) not site_web.

 To create a new website on this user pair, simply repeat steps 5 and 6 -- create the sites files and then create the domain entry in the DreamHost control panel.
 

Updates

UPDATE1: After this was written, I added an article on setting up a ssh based system for the navigation between accounts.
See the post Shell User Logins

UPDATE2: After this was written, I wrote a script to automate the creation of a user pair.   See the setupUserPair.sh script -- a part of my DreamHost script library.  It uses 'expect' to automate the logins.  It also sets up an automatic ssh path between the "user" user and the "web" user as mentioned in UPDATE1.

 

Comments

The functionality described in this post is now available in the setupUserPair.sh script -- a part of my DreamHost script library.

So, why do you want a 'user' user and a 'web' user for every 'domain'? why not recommend using the master user as the 'user' user? I think I'm missing the really good reason to create and manage this many accounts if I'm working by myself. I totally understand the security implications of the way that DreamHost wants to use user accounts and the reason behind setting up a 'web' user. Just not so sold on the extra 'user' user.

I suppose you could create a user pair for every domain, but then, with Drupal sites anyway, you'd loose the power that my Drupal script library provides.  This power comes from having multiple Drupal sites deployed in the same webroot directory.

I like to create user pairs for logical groups of sites.  If for you, a logical group is a group of one, then by all means, make a user pair for every site.

Also, I do have a script available that creates a user pair for you (see UPDATE2, above).  See my Dreamhost script library for more information.