Web sites hosting

Related pages :

Misc TODO

UID and permissions

Every website has its own UID + GID.

PHP

suPHP is used so that PHP scripts run under their website's UID/GID, instead of www-data. The Apache user (www-data) then needs to be able to list every directory where PHP scripts are stored, in order to decide, based on their ownership information, under which UID/GID they shall be run.

TODO : the choice to use suPHP is not really made yet, see Discussion for the state of the art.

noexec ?

Les petits malins qui auraient pu penser que monter /var/www en noexec pouvait vraiment servir à quelque chose face à des méchant-e-s, et ben ils se trompent. Pour exécuter un keutru sur une partition noexec, il suffit de le passer à /bin/ld, et l'exécutable s'exécutera sans problème. Un setup plus mieux pourrait être envisagé avec /var/www en noexec + des applis chrootées dans un répertoire sans 'ld' ou avec un 'ld' modifié pour l'occasion; cela dit, ce second setup fait reposer sa sécurité sur la sécurité de chroot, donc comme d'hab', si quelqu'un-e obtient le compte root dans la chroot, c'est fini.

Apache

Static files (html pages, pictures, etc.) are served by Apache, running as the www-data user in the web VServer. This user must then be allowed to read any file in web:/var/www.

Conclusion

A few simple ACLs are enough to ensure the Apache user is allowed to read what it needs to :

  • reset the ACL on /var/www : setfacl -bR /var/www
  • chmod -R go-rwx /var/www
  • chmod 0755 /var/www /var/www/*
  • initialize the ACL on existing files : setfacl -R -m user:www-data:rX /var/www/*/*
  • set this as the default for newly created files : setfacl -R -m d:user:www-data:rX /var/www/*

And thus these permissions are ok :

  • directories : rwx------+ (i.e. 700 + ACL) siteuid sitegid
  • files : rw-------+ (i.e. 600 + ACL) siteuid sitegid

A hourly cronjob takes care that every website's root directory has the correct permissions (especially o-rwx). See /etc/cron.hourly/frangipane-web-fix_www_perms

The only issue comes with the sftp chmod command : most sftp clients try to preserve file permissions by default, and thus a 600 file gets permissions 600 on the server once uploaded, and no effective read rights for www-data... because of the mask being removed by chmod 600. We solve this by applying the http://sftpfilecontrol.sourceforge.net/ patch to webmasters VServer's openssh and adding the following directives to webmasters:/etc/ssh/sshd_config :

SftpUmask 0022
SftpPermitChmod no
SftpPermitChown no

This way, all files uploaded with sftp are given the right permissions.

fixme : but php can create files with custom permissions, thus breaking the ACL, and then the webmaster can't fix these wrong permissions with sftp... a possible solution would be :

  • only give www-data access to the sites' directories : chmod 700 /var/www/*/* ; setfacl -m user:www-data:rX /var/www/*/*
  • default permissions for created files inside sites' directories : setfacl -m d:group:rX /var/www/*/* ; setfacl -m d:other:rX /var/www/*/*
  • set SftpUmask 0022 and SftpPermitChmod yes
  • set suPHP umask to 0022
  • tell the webmasters to :
    • either give 755 permissions to the files they upload
    • or to configure their sftp client so that it does not preserve uploaded files permissions, relying on the default umask

Paths and URLs

On construit le répertoire, base_url et le nom de la base MySQL dynamiquement, histoire de ne pas les stocker en dur ds la base :

  • '/var/www/' || domains.name || '/' || dns_records.hostname || '/' || (web_sites.name OU 'root')
  • fixme : 'http://' || dns_records.hostname || '/' || domains.name || '/' || web_sites.nom

TODO : en fait, en passant en mode SITE.DOMAINE.TLD, de bons bouts de ce qui a déjà été pensé est à revoir... qui plus est, une arborescence du type /var/www/TLD/DOMAIN/SITE serait p't'être plus propre

Il ne faut jamais créer de site dont le nom est aussi celui d'un répertoire déjà existant dans la racine de son domaine. Réciproquement, il ne faut jamais créer un répertoire dans la racine correspondant au nom d'un site de ce domaine. Pour résoudre ce problème ennuyeux, on n'a qu'à dire qu'on ne crée des sites!=racine que sous les domaines pour lesquels nous nous occupons de la racine (boum.org, et c'est tout), et que :

  • Nous vérifions la première condition dans la méthode de création de site.
  • Nous faisons attention de respecter la seconde condition, manuellement.

TODO Les trucs chelous (racine de boum.org, mail, phpMyAdmin, etc.) sont-ils rangés dans la base (pas interdit, en l'état actuel des choses) ? Dans l'arborescence std ? De toute façoon, va falloir les faire rentrer ds le moule, en les traitant en cas particuliers ds les PerlSections.

TODO : pour l'instant, il est possible de créer des sites en sous-répertoire d'un FQDN pour lequel aucun site racine n'existe ; le seul cas où ça peut se présenter, c'est boum.org, et donc la réponse à cette question dépend de la précédente.

Performance

Configurable setting : Apache mod_deflate & PHP zlib.output_compression enabled if, and only if CPU power is more affordable than bandwidth.

The web VServer

This VServer is dedicated to run Apache, PHP and MySQL for hosted websites. Webmasters are not given access to it.

General configuration

  • /etc/adduser.conf is setup with DIR_MODE=0700, so that any home directory is chmod'ed 0700 on site's creation.

Apache

Apache fait générer sa conf par un programme Perl, grâce aux PerlSections.

https & certificats

Les VirtualHost en https sont générés par les PerlSections, et n'ont donc rien à faire dans la base.

Les PerlSections s'occupent d'interroger le FS pour voir s'il y a un certificat au bon nom.

Il est maintenant possible d'obtenir des certificats CAcert correspondant chacun à plusieurs noms de domaines, sur la même IP :

Virtual hosts configuration

  • disable option Indexes
  • disable option Multiviews (-Multiviews)
  • AllowOverride :
    • autoriser AuthConfig pour qu'il soit possible par défaut d'utiliser une authentification par .htpasswd
    • autoriser FileInfo pour autoriser les RewriteRule
    • forbid everything else, especially :
      • Options : else, any webmaster can set suPHP_ConfigPath by .htaccess

Logs

Apache

Every VirtualHost sends its logs to the host system syslog-ng :

ErrorLog "| /usr/bin/logger -t SITE.DOMAIN.TLD -p local2.info"
CustomLog "| /usr/bin/logger -t SITE.DOMAIN.TLD -p local1.info" noip

Same for the global error log, in /etc/apache2/conf.d/frangipane : ErrorLog "| /usr/bin/logger -t apache -p local2.info"

The host system syslog-ng writes the VirtualHosts' logs to /var/log/frangipane/web/SITE.DOMAIN.TLD/apache/{access,error}.log, and the global error log in /var/log/vservers/web/apache/.

PHP

Every php.ini contains the error_log = syslog stanza, so that all PHP logs are sent to the global syslog-ng daemon, that writes :

  • the host system php log to /var/log/php/error.log
  • the hosted web sites' php log to /var/log/vservers/web/php/error.log.

PHP

Enabling / disabling PHP

TODO : It would be great to enable PHP only on sites that actually need it. This can be done in several ways :

  • add a flag to the website table in the DB, used by PerlSections
  • disable PHP by default and allow the webmasters to enable it via .htaccess

Configuration

Par ailleurs, pour assurer la sécurité, même avec suPHP, il faut un fichier php.ini pour chacun des sites hébergés. Surtout pour que la variable open_basedir soit identique au DocumentRoot du site. Comme on ne veut pas générer ces fichiers très peu différents les uns des autres à la main, il faudrait que les PerlSections les génère à la volée au chargement d'Apache. (rock n'roll!)

  • disable display_errors
  • enable safe_mode
  • disable magic_quotes_gpc, since it breaks some webapps and has been removed anyway from PHP6
  • make PHP session_save_path and upload_tmp_dir site-specific (cf discussion). Should we mix it with ACLs?
  • Follow guidelines from :
  • ini_set(), php_value et php_flag dans les htaccess: Les users peuvent modifier certaines variables php dans leur script grace a ini_set() et leur .htaccess. Ca pourrait pas etre trop grave, certaines des plus sensibles sont protegees (cf http://fr3.php.net/manual/fr/ini.php#ini.list) sauf que y'a session.save_path par ou include_path par exemple qui le sont pas. Le seul moyen d'eviter que nos utilisateurices puissent jouer avec c'est de pas mettre AllowOverride a Options dans la conf apache (pour la parti .htaccess) et interdire ini_set() dans les php.ini. Le hic c'est que beaucoup de cms utilisent ini_set, genre pour regle la zlib_compression...
  • the following functions must be disabled to prevent access to the objects in XCache (see XCache API): cache_get,xcache_set,xcache_isset,xcache_unset,xcache_unset_by_prefix,xcache_inc,xcache_dec,xcache_clear_cache,xcache_coredump,xcache_coverager_decode,xcache_coverager_start,xcache_coverager_stop,xcache_coverager_get

    php_admin_value[open_basedir] = "/var/www/DOMAIN.TLD/SITE/:/tmp" php_admin_value[upload_tmp_dir] = "/var/tmp/frangipane/DOMAIN_TLD/SITE/upload" php_admin_value[error_log] = syslog php_admin_flag[html_errors] = on php_value[error_reporting] = E_ALL & ~E_NOTICE php_admin_value[memory_limit] = 16M php_admin_flag[allow_url_fopen] = off php_admin_flag[output_buffering] = off php_admin_value[session.save_path] = "/var/tmp/frangipane/DOMAIN_TLD/root/php/sessions" php_admin_value[session.entropy_length] = 16 php_admin_value[session.entropy_file] = /dev/urandom php_admin_value[session.hash_function] = 1

suPHP

If we end up using suPHP... :

  • suPHP: set min_uid and min_gid to a reasonable value (10000)
  • SetEnv PHPRC in .htaccess does not work, which is desired, but why ?

Suhosin

The Suhosin PHP extension, that now replaces the Hardened PHP patch, is now available as a Debian package. A small patch still has to be applied to the PHP packages to get the "Engine protection" feature, see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=397179, but all other features already work out of the box on Etch.

TODO : test this with suPHP.

The webmasters VServer

The webmasters are given sftp access to this dedicated VServer, running no service apart of sshd with the sftpfilecontrol patch applied, in order to force an upload umask and to deny chmod ; this is the best way we found to prevent webmasters from messing up the ACLs setup.

libnss-pgsql is used to provide the login, home and password information.

Bind mounts

From the host system, we bind-mount :

  • web:/var/www on webmasters:/var/www (read-write)
  • every website log directory (host:/var/log/frangipane/web/DOMAIN.TLD/SITE) inside this website home directory (webmasters:/home/webmasters/DOMAIN.TLD/SITE/log) (read-only)

Home directories

A site's $HOME directory is located in /home/webmasters/DOMAIN.TLD/SITE.

/etc/adduser.conf is setup with DIR_MODE=0700, so that any home directory is chmod'ed 0700 on site's creation. Such a directory contains :

  • a www link to /var/www/DOMAIN.TLD/SITE
  • a log read-only directory

and optionnaly :

  • a backup read-only directory where automated backups could be performed, thus allowing webmasters to download them to a safe place
  • a pics read-write offline directory, which might be used some day to generate a static online pictures gallery

Quotas

Ils sont gérés au niveau FS, mais on les stocke qd même ds la BDD, pour pouvoir en faire de jolis trucs. Les deux sont donc synchronisés : scripts.

TODO : comment Apache et ssh/scp gèrent les quotas FS ?

MySQL

One web site = one MySQL database.

  • BASE_ID : ((dns_records.hostname || domains.name) s/./_) || ( ("_" || site) OR '' if root)
  • MySQL database and user names (since MySQL supports neither usernames longer than 16 chars, nor database names long enough) : echo -n BASE_ID | sha1sum | head -c 16

Examples :

echo -n boum_org_panoptique | sha1sum | head -c 16
echo -n les-renseignements-genereux_org | sha1sum | head -c 16

The example page http://boum.org/id_mysql.php allows to compute this online.

DNS

L'enregistrement DNS parent d'un site web est de type A.

Limitations du modèle

franGiPane ne permet pas d'héberger de site correspondant à un CNAME externe (dont les DNS sont gérées ailleurs) et pointant vers nous.