Project

General

Profile

HowToSetupFastCgiIndividualPermissions » History » Revision 30

Revision 29 (Anonymous, 2007-06-24 15:24) → Revision 30/39 (blueyed, 2007-09-14 20:20)

= Setup FastCGI and PHP with individual user permissions = 

 ''First of all: please notice that this how-to is only a suggestion on how to do this, so please don't blame anybody if you prefer to do things differently, or get mad customers, or whatever....'' 

 ''Note: This only works on *nix like operating systems. I don't know how to do this on Windows.'' 


 == Introduction == 

 Running a website hosting service for individual users/customers requires some extra brain-work when you set up your web-server. 

 Basically, you give every user an individual (ordinary) user account on your web-server. The user then uploads her PHP script files to her own virtual host document root. 

 What we want to do, is to execute all PHP script files with the exact same user permissions as the user that manages the virtual host in question. If this is accomplished, you can be sure that none of your users will be able to browse through other users' PHP scripts. 

 Consider the following PHP script executed on a web-server without individual user permissions on PHP-scripts (please do not attempt to do this, since you might end up with the police knocking on your door!): 

 {{{ 
 #!php 
 <?php 
 $filename = "/path_to_other_users_vhost_root/index.php"; 
 $handle = fopen($filename, "rb"); 
 $contents = fread($handle, filesize($filename)); 
 fclose($handle); 

 echo $contents; 
 ?> 
 }}} 

 This will read (and show) the source code of PHP script of some other user. The source code might contain passwords that gives access to that user's MySQL databases, or other interesting stuff. You could even make a PHP script that writes PHP script files to other user's virtual host directories! 

 This is the setup we want to get rid of! 


 == What about PHP's built-in safe_mode == 

 I will not say any bad things about PHP here, and it is NOT recommended to use PHP's built-in ''safe_mode'' features. (See the [http://www.php.net/manual/en/features.safe-mode.php safe_mode documentation at php.net] for a detailed description.) 

 There are, however, some php.ini settings that can stop or slow down the most common forms of attack without touching the source code. To stop php's remote access, see [http://www.php.net/manual/en/ref.filesystem.php#ini.allow-url-fopen allow_url_fopen], and to stop php from including remote files you can see [http://www.php.net/manual/en/ref.filesystem.php#ini.allow-url-include allow_url_include].    Setting [http://www.php.net/manual/en/features.safe-mode.php#ini.open-basedir open_basedir] is a good way to slow down an attacker, but is no replacement for user permissions.    And to slow down some forms of session stealing, [http://www.php.net/manual/en/ref.session.php#ini.session.use-trans-sid session.use_trans_sid] can be turned off. 

 It is always best if you rely on your operating system's build-in user permissions. 


 = Installation = 

 We assume that you already have Lighttpd installed, and installed PHP with FastCGI support. ([http://trac.lighttpd.net/trac/wiki/TutorialLighttpdAndPHP How to install PHP with FastCGI support]) 

 You need to log in as ''root'' to do this. 

 == 1. Add users to the operating system == 

 (This is only needed if you haven't added users yet.) 

 You must add a user account to the operating system for each user that you want to give separate user permissions, in order to deny access to other users' source code. 

 Let's assume that we need to create three users (fred, george, and ron): 

 {{{ 
 #!ShellExample 
 # useradd fred 
 # useradd george 
 # useradd ron 
 }}} 


 == 2. Add user groups to the operating system == 

 You need to add one user group for each user added above. To keep things simple, we just name the user groups similar: 

 {{{ 
 #!ShellExample 
 # groupadd fred 
 # groupadd george 
 # groupadd ron 
 }}} 

 Now you need to add users to each of these user groups. For each user group, there must be two members: the corresponding user and the lighttpd daemon user. 

 You configure the user groups by editing /etc/group with your favourite text editor. 

 The file must look something like this (group numbers may vary): 

 {{{ 
 ..... [lots of stuff above] 
 fred:x:441:fred,lighttpd 
 george:x:442:george,lighttpd 
 ron:x:443:ron,lighttpd 
 }}} 

 You might also use a ''sed'' command like this: 

 {{{ 
 sed -i "s/^\(fred.*\)$/\1,fred,lighttpd/g" /etc/group 
 sed -i "s/^\(george.*\)$/\1,george,lighttpd/g" /etc/group 
 sed -i "s/^\(ron.*\)$/\1,ron,lighttpd/g" /etc/group 
 }}} 

 These commands add the user and the lighttpd user to the groups. 

 == 3. Set up filesystem structure == 

 Let's assume that you want to keep all files associated with the web-server's virtual hosts under the directory ''/var/www''. (Of course you can choose another location, just make sure that the users created above have read and execute rights to the directory. (I.e. ''chmod 755 /var/www && chown root:root /var/www''). 


 == 3.1 Create server root directory == 

 Now, create two directories: One for some start-up scripts that only ''root'' have access to, and another for all your virtual hosts: 

 {{{ 
 #!ShellExample 
 # cd /var/www 
 # mkdir fastcgi 
 # mkdir vhosts 
 # chown lighttpd:lighttpd * 
 # chmod 755 * 

 # ls -l /var/www 

 drwxr-xr-x    2 lighttpd lighttpd 4096 Feb 15 12:17 fastcgi 
 drwxr-xr-x    9 lighttpd lighttpd 4096 Feb 15 11:21 vhosts 
 }}} 


 == 3.2 Create a directory for each virtual host == 

 Now create a directory for each virtual host in the directory ''/var/www/vhosts'', and set up appropriate user rights to them: 

 {{{ 
 #!ShellExample 
 # cd /var/www/vhosts 
 # mkdir fred-weasley.com 
 # mkdir george-weasley.com 
 # mkdir ron-weasley.com 
 # chown fred:fred fred-weasley.com 
 # chown george:george george-weasley.com 
 # chown ron:ron ron-weasley.com 
 # chmod 750 * 

 # ls -l /var/www/vhosts 

 drwxr-x---    7 fred       fred       4096 Feb 15 20:18 fred-weasley.com 
 drwxr-x---    6 george     george     4096 Feb 15 11:02 george-weasley.com 
 drwxr-x---    6 ron        ron        4096 Feb 15 11:23 ron-weasley.com 
 }}} 

 Now we have created three directories where the three users cannot see each others' files; however, the ''lighttpd'' daemon user can see it all. 

 == 3.3 Create directory structure for each virtual host == 

 Now, we want to create the directory struture needed for each virtual host: 

 {{{ 
 #!ShellExample 
 # cd /var/www/vhosts/fred-weasley.com 
 # mkdir html 
 # mkdir includes      (optional) 
 # mkdir logs 
 # chown fred:fred * 
 # chown lighttpd:fred logs 
 # chmod 750 * 

 # ls -l /var/www/vhosts/fred-weasley.com 

 drwxr-x---    14 fred       fred     4096 Feb 17 11:55 html 
 drwxr-x---     2 fred       fred     4096 Feb 15 12:05 includes 
 drwxr-x---     2 lighttpd fred     4096 Feb 15 11:11 logs 
 }}} 

 '''You need to repeat this for each virtual host, replacing the user name 'fred' with the appropriate user name.''' 


 == 3.4 Create a FastCGI directory for each user == 

 Now we have to do all the fun stuff! 

 Now, go to the ''/var/www/fastcgi'' directory where we want to create a directory for each user. (When we're finished, these directories will hold the sockets to the FastCGI server processes): 

 {{{ 
 #!ShellExample 
 # cd /var/www/fastcgi 
 # mkdir fred 
 # mkdir george 
 # mkdir ron 
 # chown fred:fred fred 
 # chown george:george george 
 # chown ron:ron ron 
 # chmod 750 * 

 # ls -l /var/www/fastcgi 

 drwxr-x---    7 fred       fred       4096 Feb 15 20:18 fred 
 drwxr-x---    6 george     george     4096 Feb 15 11:02 george 
 drwxr-x---    6 ron        ron        4096 Feb 15 11:23 ron 
 }}} 

 (Note that the lighttpd user can read all directories, while the three users can only access their own directory.) 


 == 4. Create a FastCGI start-up script for each user == 

 Create a directory that will hold all your FastCGI start-up scripts: 

 {{{ 
 #!ShellExample 
 # cd /var/www/fastcgi 
 # mkdir startup 
 # chmod 750 startup 

 # ls -l /var/www/fastcgi 

 drwxr-x---    7 fred       fred       4096 Feb 15 20:18 fred 
 drwxr-x---    6 george     george     4096 Feb 15 11:02 george 
 drwxr-x---    6 ron        ron        4096 Feb 15 11:23 ron 
 drwxr-x---    6 root       root       4096 Feb 15 11:23 startup 
 }}} 

 Now, go to the ''/var/www/fastcgi/startup'' directory, create a start-up script for ''fred'' (let's call it ''fred-startup.sh'', using your favourite text editor: 

 {{{ 
 #!sh 
 #!/bin/sh 

 ## ABSOLUTE path to the spawn-fcgi binary 
 SPAWNFCGI="/usr/bin/spawn-fcgi" 

 ## ABSOLUTE path to the PHP binary 
 FCGIPROGRAM="/usr/bin/php-cgi" 

 ## bind to tcp-port on localhost 
 FCGISOCKET="/var/www/fastcgi/fred/fred.socket" 

 ## uncomment the PHPRC line, if you want to have an extra php.ini for this user 
 ## store your custom php.ini in /var/www/fastcgi/fred/php.ini 
 ## with an custom php.ini you can improve your security 
 ## just set the open_basedir to the users webfolder 
 ## Example: (add this line in you custom php.ini) 
 ## open_basedir = /var/www/vhosts/fred/html 
 ## 
 #PHPRC="/var/www/fastcgi/fred/" 

 ## number of PHP childs to spawn in addition to the default. Minimum of 2. 
 ## Actual childs = PHP_FCGI_CHILDREN + 1 
 PHP_FCGI_CHILDREN=5 

 ## number of request server by a single php-process until is will be restarted 
 PHP_FCGI_MAX_REQUESTS=1000 

 ## IP adresses where PHP should access server connections from 
 FCGI_WEB_SERVER_ADDRS="127.0.0.1" 

 # allowed environment variables sperated by spaces 
 ALLOWED_ENV="PATH USER" 

 ## if this script is run as root switch to the following user 
 USERID=fred 
 GROUPID=fred 

 ################## no config below this line 

 if test x$PHP_FCGI_CHILDREN = x; then 
   PHP_FCGI_CHILDREN=5 
 fi 

 export PHP_FCGI_MAX_REQUESTS 
 export FCGI_WEB_SERVER_ADDRS 
 export PHPRC 

 ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS PHPRC" 

 # copy the allowed environment variables 
 E= 

 for i in $ALLOWED_ENV; do 
   E="$E $i=$(eval echo "\$$i")" 
 done 

 # clean environment and set up a new one 
 env - $E $SPAWNFCGI -s $FCGISOCKET -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN 

 chmod 770 $FCGISOCKET 
 }}} 

 Please be careful with the paths, USERID and GROUPID. 

 Note that, in this example, the php process runs as the user we created above 
 ('fred'). This means that the php code will have write access to the html and 
 php files. This can be convenient, but might be a security risk. 
 Alternatively, you could set USERID to 'nobody' (or any other user without any 
 specific permissions), to deny write access to the php process. 

 You need to repeat the process and create a startup-script for each user in the ''/var/www/fastcgi/startup'' directory. (Just copy the file and replace FCGISOCKET, USERID and GROUPID with the correct values). 

 Remember to set execute permissions on all your startup-scripts: 

 {{{ 
 #!ShellExample 
 # cd /var/www/fastcgi/startup 
 # chmod 750 * 
 }}} 


 == 5. Check your PHP configuration == 

 If you're uncertain about the location of your php.ini, just run the following command: 

 {{{ 
 #!ShellExample 
 $ php-cgi -i | grep php.ini 
 }}} 


 Please check, that you have the following line in your php.ini: 

 {{{ 
 cgi.fix_pathinfo=1 
 }}} 

 If you have uncommented the PHPRC line in the shell script under issue 4., be sure that the php.ini has the correct owner an rights. To get things work this must be  

 {{{ 
 chmod 644 php.ini 
 chown root:root php.ini 
 }}} 


 == 6. Execute all FastCGI start-up scripts == 

 Now, fire up all your FastCGI server processes: 

 {{{ 
 #!ShellExample 
 # /var/www/fastcgi/startup/fred-startup.sh 
 spawn-fcgi.c.170: child spawned successfully: PID: xxxxx 
 # /var/www/fastcgi/startup/george-startup.sh 
 spawn-fcgi.c.170: child spawned successfully: PID: xxxxx 
 # /var/www/fastcgi/startup/ron-startup.sh 
 spawn-fcgi.c.170: child spawned successfully: PID: xxxxx 
 }}} 

 If you get any error messages, please re-check your startup-scripts and the permissions to the ''/var/www/fastcgi'' directory, including all user sub-directories. 


 == 7. Configure virtual hosts in the lighttpd server == 

 Edit ''/etc/lighttpd.conf'' in your favourite text-editor: 

 {{{ 

 .....[lots of configuration stuff above]..... 

 $HTTP["host"] =~ "(^|\.)fred-weasley.com$" { 
     server.document-root = "/var/www/vhosts/fred-weasley.com/html" 
     server.errorlog = "/var/www/vhosts/fred-weasley.com/logs/error_log" 
     accesslog.filename = "/var/www/vhosts/fred-weasley.com/logs/access_log" 
     fastcgi.server = ( ".php" => 
                        ( 
                           ( "socket" => "/var/www/fastcgi/fred/fred.socket", 
                             "broken-scriptfilename" => "enable" 
                           ) 
                         ) 
                       ) 
 } 


 $HTTP["host"] =~ "(^|\.)george-weasley.com$" { 
     server.document-root = "/var/www/vhosts/george-weasley.com/html" 
     server.errorlog = "/var/www/vhosts/george-weasley.com/logs/error_log" 
     accesslog.filename = "/var/www/vhosts/george-weasley.com/logs/access_log" 
     fastcgi.server = ( ".php" => 
                        ( 
                           ( "socket" => "/var/www/fastcgi/george/george.socket", 
                             "broken-scriptfilename" => "enable" 
                           ) 
                         ) 
                       ) 
 } 


 $HTTP["host"] =~ "(^|\.)ron-weasley.com$" { 
     server.document-root = "/var/www/vhosts/ron-weasley.com/html" 
     server.errorlog = "/var/www/vhosts/ron-weasley.com/logs/error_log" 
     accesslog.filename = "/var/www/vhosts/ron-weasley.com/logs/access_log" 
     fastcgi.server = ( ".php" => 
                        ( 
                           ( "socket" => "/var/www/fastcgi/ron/ron.socket", 
                             "broken-scriptfilename" => "enable" 
                           ) 
                         ) 
                       ) 
 } 

 }}} 

 ''Please note the paths to the FastCGI sockets for each virtual host.'' 

 '''server.errorlog is NOT working in conditionals, all errors go to the last logfile specified. See http://trac.lighttpd.net/trac/ticket/665'''  

 == 8. Restart the lighttpd daemon process == 

 Simply run this command: 

 {{{ 
 #!ShellExample 
 # /etc/init.d/lighttpd restart 
 }}} 

 If you get any errors, please re-check your ''/etc/lighttpd.conf'' configuration file. 


 == 9. Hello World! == 

 Now, log in as the user ''fred'' and create a PHP script file in his virtual host (e.g. ''/var/www/vhosts/fred-weasley.com/html/index.php''): 

 {{{ 
 #!php 
 <?php 
 echo "<h1>Hello World!</h1>"; 
 echo "<p>Current User ID is: ". posix_getuid(); 
 echo "<p>Current Group ID is: ". posix_getgid(); 
 ?> 
 }}} 

 Also, make sure to set the file permissions: 

 {{{ 
 #!ShellExample 
 # chown fred:fred /var/www/vhosts/fred-weasley.com/html/index.php 
 # chmod 640 /var/www/vhosts/fred-weasley.com/html/index.php 

 # ls -l /var/www/vhosts/fred-weasley.com/html 

 -rw-r-----     1 fred     fred         116 Jul 25    2004 index.php 
 }}} 

 Now fire up your web-browser and check the output of your PHP script. (Here: http://www.fred-weasley.com/index.php) 

 If everything went well, you will see an output showing the User ID of the user ''fred'', and the Group ID of the user group ''fred''. (You can see these IDs in the files ''/etc/passwd'' and ''/etc/group''). 

 == 10. Automatically start the FastCGI startup scripts == 

 Optionally, you may also create a crontab entry to automatically execute the FastCGI startup scripts when your server boots. 

 Use the following command to edit your crontab: 

 {{{ 
 # crontab -e 
 }}} 

 Now add the following line: 

 {{{ 
 @reboot for i in /var/www/fastcgi/startup/*.sh; do $i; done 
 }}} 

 And finally type ":x" to save and exit. 

 This crontab entry will execute all .sh files found in the /var/www/fastcgi/startup directory after the server has booted. 

 Congratulations! You now have a working fast server configuration with individual (separate) user rights. 


 == Questions == 
 With a php-fastcgi process per user, each has to use its own memory area, right? Or is it possible to share the memory used for caching scripts? (See http://forum.lighttpd.net/topic/847)