Multisite with hosting control panel, like CPanel, Plesk - possible? How?

Events happening in the community are now at Drupal community events on www.drupal.org.
playfulwolf's picture

Hi,

Want to use multisite with some control panel on low-memory VPS, ok, lets say any server, wich has some kind of hosting control panel like Plesk, CPanel, Directadmin or any other. Prefer CPanel, but as I am using ~10% of it's features, almost any decent one is ok. The onli administrative person will be me.

The problem is that I want to make some kind of an "incubator" for low traffic sites on single server which use standard D6 codebase and db but need more freedom than shared hosting (ok, I will overuse resources anyway), more comfort than server with no control panel and still share as much Drupal as possible, exept with core hacking tricks.

Googled web, but haven't found much about this... Can someone drop some resources for the beginning?

Comments

I'm afraid I'm not sure of

Garrett Albright's picture

I'm afraid I'm not sure of what you're asking. What do you mean by "incubator?" Why do you think you need to hack core?

Something like CPanel isn't necessary to use Drupal multisite, if you're familiar with how to configure your server "manually." On both my work and personal servers, which both run multisite Drupal installations, I get by perfectly fine without it.

I think I've cracked it.

cclafferty's picture

I have successfully been able to run multiple cPanel users sites from a single drupal code base and without recreating any files.

I literally wrestled with this problem all day yesterday however I believe I've got something which could work. I will post a full detailed document when I get more time to test it however here is the gist of it.

Requirements:
Root access to server
OS Supports symlinks (there's a good few in here, sorry Windows)

Drupal directory: called this folder drupal
The sites folder in drupal must be empty. This is important

move drupal to:
/usr/share/drupal

So no-one can peek at code
chmod 750 /usr/share/drupal/ -R

Now for the confusing part. cPanel users have their multisite folder inside their home directory. eg. /home/user1/site1.com
You now create the symlink "site1.com" inside /user/share/drupal/sites (this is why we needed the sites directory empty earlier)
e.g.

ln -s /home/user1/site1.com /usr/share/drupal/sites/site1.com

Now we rename the users public_html or whatever to "public_html.bk" as a backup.

Next is one last symlink. Create a symlink which will replace public_html and point to our shared drupal code base
e.g.

ln -s /usr/share/drupal /home/user1/public_html.

Last step and one which game me a world full of problems.
You cannot to the best of my knowledge complete this with suPHP enabled. I turned on CGI under PHP Configuration within WHM and it worked!

Oh, and you set the owner of the drupal code base to your apache user. In my case, this was user "nobody"
e.g.
chown nobody:nobody /usr/share/drupal -R

Assuming you've been able to follow this mind bend of a solution, you should now....hopefully... have multiple cPanel users each with their own drupal site, running from a single drupal code base and with their own databases.

Voila!

As I said, I will be posting one huge recipe for this in more specifics later. With charts and graphs along with how I manage this all with SVN. I will post back here when its done.

-- If anyone can put in some feedback for this solution then get posting. Security is a concern I have and would like to hear how this solution tackles it.

Sounds great

mstrelan's picture

Hi cclafferty,

This sounds like a fantastic approach. I am going to do some testing on it today and see how good it is. I am also considering combining the update process with what is mentioned at http://acquia.com/blog/minimizing-maintenance-time-while-updating-thousa...

When it is time to run updates you follow this procedure

  • clone /usr/share/drupal to /usr/share/drupal_temp
  • redirect all the symlinks to /usr/share/drupal_temp
  • update the code base of /usr/share/drupal

Then one by one you

  • put a site in to maintenance mode
  • redirect the symlink back to /usr/share/drupal
  • run update.php
  • take site out of maintenance mode

Then finally you can trash /usr/share/drupal_temp

Tested

mstrelan's picture

Hi again,

I have put this to the test and have some questions / potential solutions. I think it's best if we work through these together.

file ownership

If apache is running as nobody this means that uploaded files are also owned by nobody. CPanel reports disk usage based on ownership. I have solved this issue by having cron running a daily script that changes ownership of all files in the files directory, based on the user. Eg user1:nobody, 775

accessing other users files

Since the sites directory is full of symlinks where the destination is owned by nobody:nobody or user1:nobody administrators are able to set the files directory path (at admin/settings/file-system) to another websites files directory. This is highly undesirable. The only workaround I can think of is to remove the permission "administer site configuration" and only access it with the admin account. This does not help if the user has access to the database, eg via cpanel. Luckily in WHM you can restrict the feature set and remove users access to MySQL databases and PhpMyAdmin. I would also recommened changing ownership of the site's settings.php to root:nobody with permissions 440

publishing other users files

Considering all previously mentioned there is nothing stopping anybody from accessing files for example2.com from example1.com. For example you could navigate to example1.com/sites/example2.com/files/test.jpg. Since example2.com is just a symlink, and the apache user has access to the files dir, the image will be served up. I guess this is not such a big problem since the files directory would be public to the world anyway, however it really shouldn't occur. The work around I have come up with is to place the files directory in /home/user1/files/example.com, set that absolute path in admin/settings/file-system, set download method to private and remove access to administer that page. Can you think of a better way to handle this?

Agreed

cclafferty's picture

Hi mstrelan,

I'm glad you've found my initial solution helpful and I'd be more than willing to work together to hopefully solve this problem once and for all.

Truth be told, I have still to properly "crack it". I lost a lot of time with this problem between actual site building work and haven't had time to return to it since. Well at least that's my excuse. Anyway, an update on what I had problems with.

I know in my comment I said about not getting it to work with suPHP but ultimately I think if we can get it to work with it then this is going to solve all the other ugly problems with accessing other peoples sites etc. Just a thought.

Another problem I had, which I couldn't understand was that either PHP or Drupal starts to look for real folders and not symlinks in the sites/ directory (correct me if im wrong on this) and such my symlinks didn't fully work as desired. When I posted the above, I was able to get the drupal install screen on any account I wanted; figured it a success; and later regretted it. I believe the problem was that drupal couldn't see/find the other sites in the sites folder and simply presented the install screen again.

There is a solution around this and that is to symlink in depth to our implementation:
e.g.
Base install is /usr/share/drupal

each account will have multiple symlinks to the drupal files and folders, with the sites folder being held locally in their account

I'm thinking a better way of approaching this would be via some form of mount command. If we mount the drupal files into the users directory and use suPHP's directory checker to ensure the user cannot access files outside their directory. Its a matter of getting suPHP to share files thats the problem. I spoke to one of the guys who maintains it and this might involve some tweaking.

Oh finally, concerning your problem with the file system directory in drupal. How about a custom module to hook the form and remove it from view and to provide our own set of file systems dependant on site. I can look into seeing if we can have a module turned on by default/forever or maybe even have it load into core. This would take database access out of the picture hopefully.

I understand that these all need to be considered in much more detail and maybe generalised a little so that people in the future can approach it with their platform. So if you could give this stuff a think and let me know what you've got working we can get the ball rolling on this.

Apache Directives

mstrelan's picture

There are apache directives FollowSymLinks and SymLinksIfOwnerMatch (see http://httpd.apache.org/docs/1.3/mod/core.html#options) that may be relevant. Tomorrow I am going to test out PHPSuExec and see if I can run apache as user1. User1 will be in a group called drupal. Drupal will be owned by root:drupal or drupal:drupal. I have a feeling that SymLinksIfOwnerMatch applies to group and not just user. And so the files directory will be owned by user1:user1 so user2 will have no way of accessing it.
I will report back on how it goes.

And yes I had the thought the same about the custom module. Easiest solution is to do a hook_menu_alter to change the access callback / arguments to just check if it is user 1.

Working with SuExec!

mstrelan's picture

Hi again,

Great news, I've got it working with SuExec. I have set the PHP Handler to cgi and turned on Apache suEXEC. If PHP handler is dso then apache runs as nobody. If PHP handler is suphp then PHP won't execute any file that can be executed by any other user. Eg if /usr/share/drupal/index.php is not owned by user1, or it is owned by user1 but group or everyone permissions are allowed to execute or write then apache throws a 500 internal server error. So stick with cgi.

The main drupal files should be owned by root, so that the users can't mess with them. I am using 644 and it is working fine. The users' files directory needs to be owned by the user and I am using permissions 655, although 644 should work in theory. I was having trouble directly viewing an image in the browser with 644.

The files and settings no longer need to live inside the users' home directorys, they can simply live in the drupal sites directory, removing the need for the symlink, as long as the file ownership is set correctly. You may however prefer to keep the files in their home directories.

Since the files directories are now owned by the user there is no issue with users changing their drupal settings for the files directory, because they don't have write access to other users directories.

The only issue remaining is that you can still point to example1.com/sites/example2.com/test.jpg, but again this can be resolved using private file downloads.

Let me know if this works for you, and if there are any obvious issues. I'd be keen to know if there is a performance hit with suexec.

Cheers,
Michael

Great!

cclafferty's picture

Congrats mstrelan, thats further than I've managed to get.

I was experimenting with binded mounts of the drupal codebase inside the users homedirectory, so the users actually see all the files. I maybe got a little too adventurous and ended up corrupting something important. 6 hours of vps rebuilding later and I'm back on track.

I am aiming to get this working with suPHP also, just for that last squeeze of security. This could work if we do a readonly mount of the drupal files inside the users directory. Unfortunately there is a bug in CentOS 5.4 which does not address the allowance of bind + readonly mounts together so you can imagine, I'm jumping through a lot of hoops.

One concern I have with having the users files and shared files in the same group is this:
Is it possible to create a small module that will open/view a file from the other users site maybe a simple fopen or fread to the absolute location of another users site files.

I understand that suExec is supposed to stop execution of another persons files but as the drupal code and another users code is in the same group this may allow escalation. Just a thought if you could do a simple test for this.

Another factor I am looking into implementing is the Aegir system. Obviously with having the site folders in one place this is a lot more straight forward.

Finally, a solution which maybe seems a little backwards to our goal and maybe I'll look into more as a last resort is to actually keep rsynced copies of the sites which update themselves on cron run. This is going to reproduce the codebase per account but at least we'll have them in sync for changes.

I'll let you know how I get on. Hopefully today won't involve 101 reconfigurations of apache.

groups

mstrelan's picture

In my latest post the users' files directories are owned by user1:user1 rather than user1:drupal or user1:group1 so this is no problem.

I had the same rsync idea, I have read about achieving something similar by using SVN. Each site can just run svn checkout when ever cron is run and download the latest files, and then use drush to update the db. I don't think I am going to investigate this route unless I experience major problems with the symlink method

I think your right

cclafferty's picture

For some reason I was thinking the drupal base would run as a higher user. So ignore that. I am in the process of moving my sites into this type of structure using SVN on both my drupal base and per account sites. I used a similar setup as posted by Matt Petrowsky on gotdrupal.com I'll post my setup diagram tomorrow once I'm happy with it. Slight concern with SVN is that the password to the repo is visible in the checkout. Means you have to deal with SVN user access. Small price I suppose.
Have you tried running PHP as a fastcgi process? Coupled with suExec should keep things secure and speedy. Keep me informed

I think your right

cclafferty's picture

For some reason I was thinking the drupal base would run as a higher user. So ignore that. I am in the process of moving my sites into this type of structure using SVN on both my drupal base and per account sites. I used a similar setup as posted by Matt Petrowsky on gotdrupal.com I'll post my setup diagram tomorrow once I'm happy with it. Slight concern with SVN is that the password to the repo is visible in the checkout. Means you have to deal with SVN user access. Small price I suppose.
Have you tried running PHP as a fastcgi process? Coupled with suExec should keep things secure and speedy. Keep me informed

How to use SuPHP to acheive this

mstrelan's picture

Hi there,

My previous post on using SuExec seemed great, except it means that you cannot use php_value directives in .htaccess files. This is not usually a problem except that I am running this on the same server as an old OSCommerce site, which requires register_globals to be On. By enabling SuPHP I can create a separate php.ini file in the oscommerce user's public_html directory. But now I need to get my Drupal symlinks working with SuPHP.

SuPHP basically requires the drupal php files to be owned by the current user, so symlinking ~/public_html to /usr/share/drupal just isn't going to cut it, no matter what permissions you assign. The great thing about Drupal however, is that users only ever access index.php, update.php, cron.php and install.php. So now what we can do is create a group called drupal, assign the user to this group (as a secondary group), assign group ownership to the core drupal files to this drupal group and finally include these files from actual files owned by the user. Let me describe this in more detail.

Create a group
groupadd drupal

Add the user to this group
usermod -G drupal

Assign drupal files to this group

chown -R root:drupal /usr/share/drupal
chmod -R 755 /usr/share/drupal

Setup php files
In each users public_html directory:
index.php, cron.php, update.php, install.php

<?php
chdir('/usr/share/drupal');
include('/usr/share/drupal/index.php'); // change value depending on the file
exit;

Also need to

cd /home/user1/public_html
ln -s /usr/share/drupal/includes includes
ln -s /usr/share/drupal/misc misc
ln -s /usr/share/drupal/modules modules
mkdir sites
ln -s /usr/share/drupal/sites/all sites/all
ln -s /usr/share/drupal/themes themes
cp /usr/share/drupal/.htaccess .
cp /usr/share/drupal/robots.txt .
chown user:user *

Then we just have to move the sites/example.com directory in to the users public_html/sites directory. The settings.php file can have 400 permissions and the files directory can have 755. All of these should be owned by user1:user1

There is now no way to write to another users files directory, or to fopen another users file. You cannot include another users settings.php, or read it in any way. Heck, you can't even include another users file.
Since the sites directory exists in the users account there is also no way for them to list what other sites exist in the actual sites directory

What do you think about this approach? There is a bit more setup involved, although this can be scripted. There is also the chance the user could screw up their php files, but when it comes down to it that's their own fault, and if the setup is scripted it could be fixed again easily.

i guess when you get down to

mstrelan's picture

i guess when you get down to this level of detail you start to wonder if there is really any advantage to this approach

Nice idea

cclafferty's picture

I hadn't even considered this approach. It requires a lot of symlinking which could get a little messy but it looks like it would do the job. I probably wont be using this approach as I have since given up on suPHP on the basis that its security was a little too suffocating for me. This should be ticked or checked off as being another possible solution though, good work.

I have successfully got the initial symlink method to work which I believe we should mark as "a working solution with some disadvantages". I am currently investigating the use of binded mounts to give users a the drupal files inside their directory. I may combine this with a chroot of the users home directory so they can't go exploring the rest of the file system or anyone else's files.

Here's the process: Same file permissions and ownership as the initial symlink solution.

/usr/share/drupal does not contain any drupal files. It instead contains another folder called drupal

drupal files go in: /usr/share/drupal/drupal
This is important for slick verson control later if we want different versions of drupal available e.g: /usr/share/drupal/drupal_6.13

Go into our new drupal file base

cd /usr/share/drupal/drupal

Take the sites folder out and up a directory
mv sites ../

Sites folder will only be used for "all" and "default" site setups

Also remove the .htaccess file and place it up a directory along with sites
This will allow each user to be able to use their own .htaccess file later
Create a symlink in our drupal file store for sites and .htaccess
Symlinks should be relative, not absolute. This is important

cd /usr/share/drupal/drupal
ln -s ../.htaccess .htaccess
ln -s ../sites sites

Go into a user account
cd /home/user1

I've chosen to add these folders outside of the public_html but you can add them anywhere, just make sure Apache Document root knows where to find them.

Make a new sites folder

mkdir sites

Make a drupal folder
mkdir drupal

Link public_html to our new drupal folder (This is pointing apache document root at our drupal files)
mv public_html public_html.bk
ln -s ./drupal public_html

Copy a version of our .htaccess file
cp /usr/share/drupal/.htaccess ./

Mount our drupal base
sudo mount --bind /usr/share/drupal/drupal ./drupal

To have our "all" and "default" sites we need more mounts. This time within the sites folder
cd sites
mkdir all
mkdir default

sudo mount --bind /usr/share/drupal/sites/all all
sudo mount --bind /usr/share/drupal/sites/default default

Because the symlinks we created earlier were relative, during the mount process it will look up 1 directory for the sites folder. As the mount was within the users home this symlink will correspond to their own sites folder. Likewise for the .htaccess file. The seperate .htaccess file allows users to take advantage of modules like boost which need access to it.

Now the user can create new sites as they please within their own sites folder and own all the site files themselves without a spider web of symlinks criss-crossing the system.

The process of giving a new user a drupal setup is now quite straight forward and easily pasted into a script as its the same list of commands for each user.

I also noticed that for one reason or another jailshelling the user prevents viewable access to the drupal files. So although the files can't be seen, apache still picks them up fine and root can see them too.

I've got this working now and it's suited my setup the best so far. Please give it a try and let me know how you get on.

While I've been working on this, I have been organising my SVN to help keep the transition between my developing machine and live server as simple as possible. I will be posting a few diagrams when I get a chance which really shows how all this works out.

My next idea would be to collect our thoughts on this problem and start to feed it back as some form of documentation. Each solution we have found could be worked out as a separate page; a recipe to follow. They all seem to work so I'm hoping we can lay them out and have others the option to choose the one they want to implement given their server setup. Let me know if you'd be interested in this and which section of documentation you feel we need to file this under.

*edit: Pretty printed commands in code blocks
*edit2: Fixed broken mount command for "all" and "default" directories

Sounds good, I will try this

mstrelan's picture

Sounds good, I will try this when I get that chance. I am interested in the documentation part of this as well. I have some thoughts on the updating of this but we'll cross that bridge when we get that far.

mounts disappear on server reboot

mstrelan's picture

When you reboot the server (which is rare, but it can happen!) the binded mounts disappear. Is there any way to make them persist? I don't really know much about binded mounts, but one thread I saw suggested adding the mount in /etc/fstab. I'm also not sure if that works well with cPanel

or you could have a startup

mstrelan's picture

or you could have a startup script that creates all the mounts

Multisite

Group organizers

Group notifications

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