Web sites hosting
Related pages :
- scripts : scripts
Misc TODO
- finish adapting this page to a http://SITE.DOMAIN.TLD configuration (the original design was based on http://DOMAIN.TLD/SITE
- CMS farm : http://help.riseup.net/hosting/cms/
- chroot is now in OpenSSH : http://undeadly.org/cgi?action=article&sid=20080220110039
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
andSftpPermitChmod 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 :
- http://wiki.cacert.org/wiki/VhostTaskForce
- https://docs.indymedia.org/view/Sysadmin/CaCertSsl
- http://lists.debian.org/debian-isp/2005/08/msg00127.html
- http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#vhosts
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 setsuPHP_ConfigPath
by.htaccess
- autoriser
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
andupload_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
andmin_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
onwebmasters:/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.