I’d like to rename this to HowtoSuexec or HowtoSuexecMultiuser, but I don’t see how… any help?
Most RoR installations assume one project on one server owned by one developer. If, like me, you need to set things up so multiple untrusted users can make rails apps, things can be a little tricky.
This howto assumes you can install RoR in a single-user environment. I won’t cover the basics, only the specific challenges for running in a multi user environment. I also assume you are handy with unix. If ”cd ” or ”chmod -R og-a” are outside your experience, you’ll probably want to consult man pages or your friendly neighborhood unix geek.
I’m setting up RoR for a class at a community college.
That means I should try and protect my users not only from external threats, but also from each other.
In the traditional CGI setting, every script runs with the user-ID of the web server. This is very bad in a multi-user setup because it means one users scripts can trivially stomp all over every other users’ scripts.
The way I’ve handled this problem in the past is with CGIWrap or Apache’s suexec. These utilities act as wrappers that can change the active user id. Of course, this adds it’s own challenges.
My solution involves a few tweaks on the server, but mostly tweaks on the part of the users. This is obviously not optimal. A better solution would include scripts or modifications to the rails defaults so users could proceed without jumping through any special hoops. For now though, this works so I think it’s worth sharing.
First I installed RoR in the normal way, so go follow someone else’s howto until you’ve gotten that done.
First, make sure you have suexec installed. This is part of the Apache distribution, so I’ll leave it up to you to figure how to do that on your system.
You will also need to install mod_fcgid or mod_fastcgi.
I used mod_fcgid primarily because it came with my Linux distribution, but also because it claims to be better at handling problem scripts, which is a serious concern in my environment. This howto assumes you are using mod_fcgid, so if you are using mod_fastcgi, keep a sharp eye out.
Next, set AllowOverride all for a directory above your users’ home directories. In my case, I made a
<Directory /home> AllowOverride all </Directory>
“But!”, I hear you exclaim, “why not just put the AllowOverride statement in the DirectoryMatch that describes users’ public_html dirs?”
The AllowOverride directive is sligtly odd in that it will not work in any DirectoryMatch or Directory that uses a wildcard. So, unless you want to define a specific Directory for every user’s public_html dir , you’ll need to set AllowOverride for a directory above your users’ home directories and that is probably /home.
In order to work, the directory containing the dispatchers (dispatch.cgi,dispatch.fgci & dispatch.rb) must be configured to allow the execution of cgi scripts. The Options +ExecCGI line in the .htaccess file in the public dir of a user’s RoR project is supposed to take care of that. For some reason, this didn’t seem to be working on my system, so I put Options +ExecCGI
in the DirectoryMatch that describes users’ public_html dirs. I probably did something wrong, but if you have this problem, there is one solution.
As part of the user account generation process on my server, I also generate a MySQL account with the same name as the user’s system username. I also generate a database that the user’s MySQL user has all privileges on. This database also has the same name as the user’s system username. I have a simple web form that allows users to set and change their MySQL passwords.
When running the rails script, users need to specify that they’re using MySQL. For example:
$ rails myproject -d mysql
Otherwise they’ll probably wind up with default database configs for sqlight.
After running rails, they’ll want to edit those database configs. The file is config/database.yml in the user’s RoR project directory and it has sections for a development, test and production database. I’m only interested in the development database and I’ve never used the other two, so I don’t have any advice about them. Here is an example of the config section for the development database.
development:
adapter: mysql
encoding: utf8
database: dylan
username: dylan
password: secret-pass-word
socket: /tmp/mysql.sock
The adapter and encoding are probably fine. Users will need to set the database,username and password. The password is the clear text password.
I think rails is smart enough to check the MySQL configs to find the correct socket, but users should know the correct value so they can check that this is correct.
I want my users to have RoR projects in their home dirs and the public bits in public_html. Normally, this is done by creating the RoR app as usual and making a link from the public dir of the RoR app to a dir in public_html. Because Apache and particularly suexec gets finicky about symbolic links, my users need to move the public dir to a dir in public_html and then made a link from that moved dir back to the public dir in the RoR project. If that didn’t confuse you, you win a special prize… Here’s an example for the rest of us:
$ cd ~
$ rails myproject -d mysql
$ cd myproject
$ mv public ~/public_html/myproject
$ ln -s ~/public_html/myproject public
Here’s what that did
Because we’re using suexec, we can and should have much stricter file
permissions. Esentially, everything should be readable and writable by the
owner and no one else, except for the files in public, which should be er…
public. Absolutely nothing should ever be world writable.
For example, the database.yml file should be readable only by the file’s
owner and not by anyone else. That way everyone can’t trivially steal their
classmate’s mysql passwords.
Here’s a timeline of how the user ID changes. This might help visualise which
files need to be readable by who. This is actually kindof a guess, so take
ths with a grain of salt.
1. apache, running as apache user gets request
2. apache reads your .htaccess file as the apache user
3. apache invokes suexec
4. suexec changes the running uid to your uid
5. suexec invokes fast cgi
6. fast cgi reads your dispatch script as your uid
7. fast cgi runs your dispatch script as your uid
8. dispatch does whatever it does as your uid
So, if I’m right, the public dir needs to be readable and executable by
‘other’, and the .htaccess files needs to be readable by ‘other’, but
everything else can be absolutely forbidden to ‘other’.
One way to tighten your permissions to set your umask before running rails, e.g.
$ OLDMASK=umask $ umask 077 $ rails myproject .... $ umask $OLDMASK
...and then open up the permissions on ‘public’.
$ chmod a+rX public $ chmod a+x public/dispatch*
Again, I assume you know enough about unix to know how to check what the file
permissions are and how to set them. These examples are just me remembering
as I type, so don’t just copy and paste and hope for the best.
Because my system uses mod_fcgid instead of mod_fastcgi my users need to
change the AddHandler line from fastcgi-script to fcgid-script, like this:
AddHandler fcgid-script fcg fcgi fpl
Next, users need to use the RewriteBase. They need to set this to the file
part of the URL where the RoR project will be accessed. This is from a web
point of view, not a filesystem point of view.
RewriteBase /~jdoe/ror
Finally, we want to call the fast cgi version of the dispatch script. So,
users must change the RewriteRule to look like this:
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
The script dispatch.fcgi needs a few tweaks. First, users must change the
require line that includes the environment file to use a static absolute
path.
The original line looks like this:
require File.dirname(__FILE__) + "/../config/environment"
Users should make it look something like this.
require "/home/jdoe/myproject/config/environment"
I don’t have to say “substitute your username for ‘jdoe’” or “substitute your
project name for ‘myproject’”, do I?
If you care about security, I recommend setting your umask by adding this
line to your dispatch.fcgi file.
File.umask(0077);
RoR can bring your server to it’s knees if you have a classroom full of
students trying to run scripts at the same time. For that reason I highly
recommend having your users run their scripts “nice”. To do that, they would
add This line:
Process.setpriority(Process::PRIO_PROCESS, 0, 10)
to their dispatch.fcgi files before the line that looks like this:
RailsFCGIHandler.process!
Finally, RoR uses cookies and your user’s cookies can collide with eachother.
By that I mean, if you view one user’s RoR project and then view another, the
2nd RoR project will think the 1st project’s cookie is from it (the 2nd
project) and then the 2nd project will explode. I never found a really good
way to make this not happen, but the instructor’s work around was to make sure
everyone had different RoR project names. Having different usernames is not
enough. This still makes the cookie names predictable and therefore insecure,
but at least it stops exploding.
If anyone has a better way to customize the
cookie names, please add it to this howto (it’s a wiki after all!).
You’ll definitely want to give your users a way to view the apache error log.
When a cgi or fcgi fails to compile or throws any output to stderr, the apache
server shows a really unhelpful error and no details. Usually the error
output can be found in the apache error log.
Make sure your users can kill their dispatch.fcgi processes. If left alone,
they will die after 5 minutes. If a user makes a change and then goes and
checks their project, they won’t see the changes and they’ll reset the 5
minute timer. This can really confuse and frustrate users! We just told them
how to use ps and kill.
If you have questions about this process or suggestions , feel free to contact me at dmartin at sccd.ctc.edu.