Seitenanfang

Apache2 mit PHP5-FPM und chroot-Jail

Dieser Post wurde aus meiner alten WordPress-Installation importiert. Sollte es Darstellungsprobleme, falsche Links oder fehlende Bilder geben, bitte einfach hier einen Kommentar hinterlassen. Danke.


Seit einiger Zeit arbeite ich mit PSGI/Plack, aber erst jetzt weiß ich, wie schön Plackup, Starman & Co. wirklich sind - denn heute wollte ich "mal eben" PHP5 in eine FastCGI-Umgebung schieben. Dafür gibt es den PHP-FPM (FastCGI Process Manager), der mittlerweile wie auch alles andere was nur wenige Leute brauchen Teil des PHP-Core ist und dieser unterstützt sogar die Absicherung der PHP-Scripte mittels chroot - allerdings habe ich keine Anleitung im Internet gefunden, die dieses wichtige Security-Feature tatsächlich nutzt.

Sonntag Abend schreibt mich ein Kollege an: Das /var/www - Verzeichnis eines Servers ist plötzlich spurlos verschwunden. Wenig später finden wir eindeutige Zeichen für einen der unzähligen PHP-Exploits und man kann auch sehr geteilter Meinung darüber sein, /var/www dem User www-data zu überlassen, aber dieser Vorfall bestätigt wieder eines: PHP ist unsicher! Sie Sprache selbst mittlerweile vielleicht nicht mehr, aber man kann kaum ein PHP-basiertes Script installieren ohne sich eine Sicherheitslücke einzufangen. Der gleiche Server wurde kurz zuvor schon ein paar Mal von einem einzigen wildgewordenen Script gekillt, dass über den Apache ziemlich oft parallel gestartet werden kann.

Die Idee ist einfach: PHP via FastCGI vom Apache abkapseln und damit so weit wie möglich einschränken. Die Umsetzung erforderte zunächst ein Ubuntu-Upgrade, der Server läuft jetzt mit Ubuntu Precise 12.04 LTS.

Module installieren und aktivieren

Als erstes werden die notwendigen Pakete installiert:
sudo apt-get install libapache2-mod-fastcgi php5-fpm
Dann werden die Apache-Module aktiviert:
sudo a2enmod fastcgi actions

Benutzer erstellen

Ich möchte alle Wordpress-Blogs in ein gemeinsames chroot-Verzeichnis verbannen und dieses bekommt einen eigenen Benutzer (der möglichst nichts anderes darf):
useradd -m -c 'PHP-Workers for Wordpress' -s /bin/false wp-fpm
Dabei wird das Verzeichnis /home/wp-fpm angelegt. Ein anderes Home-Verzeichnis kann useradd mit -d übergeben werden.

PHP-FPM konfigurieren

In /etc/php5/fpm/pool.d/ liegt die Konfiguration der PHP-FPM-Pools. Jeder "Pool" ist eine Gruppe von Prozessen, die sich Konfiguration, Verzeichnisse und Aufgaben teilen. Ubuntu bringt eine Standardkonfigurationsdatei www.conf mit, deren Einstellungen mir zwar ganz und gar nicht gefallen, aber ich möchte die Datei als Beispiel und spätere Referenz behalten, also wird sie kopiert und umbenannt, damit sie vom PHP5-FPM ignoriert wird:
cp www.conf wp-fpm.confmv www.conf www.conf.sample
In der neuen wp-fpm.conf benenne ich zunächst den Pool um, dazu muss nur das [www] in Zeile 4 durch den neuen Namen ersetzt werden: [wp]. Die Parameter user und group werden beide auf wp-fpm umgestellt, damit alles unter dem neuen Benutzer und nicht dem Standard www-data läuft. Sollen mehrere Pools auf dem gleichen Server lauen, benötigt jeder in der listen - Zeile einen eigenen Port und schließlich wird die wichtige chroot - Einstellung auf /home/wp-fpm geändert. Nach Abzug aller Kommentarzeilen sind jetzt folgende Einstellungen in der neuen Datei aktiv:
[wp]user = wp-fpmgroup = wp-fpmlisten = 127.0.0.1:9000pm = dynamicpm.max_children = 10pm.start_servers = 4pm.min_spare_servers = 2pm.max_spare_servers = 6pm.status_path = /statuschroot = /home/wp-fpmchdir = /
Die pm.* - Werte sollten (ggf. später) passend zum eigenen Bedarf angepasst werden, insbesondere max_children darf nicht zu hoch gesetzt werden, denn dieser Parameter steuert die maximale Anzahl gleichzeitiger PHP-Prozesse in diesem Pool und eine zu große Anzahl kann den Server wieder relativ leicht in die Überlast treiben.

Jetzt wird der FPM gestartet und kontrolliert:

sudo /etc/init.d/php5-fpm restartps ax|grep fpm18835 ?        Ss     0:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)18836 ?        S      0:00 php-fpm: pool wp18837 ?        S      0:00 php-fpm: pool wp18838 ?        S      0:00 php-fpm: pool wp18839 ?        S      0:00 php-fpm: pool wp
Hier laufen also vier Worker-Prozesse und warten auf eingehende Anfragen. Leider gibt es derzeit noch niemanden, der anfragen kann, also folgt als nächstes die Apache-Konfiguration.

Apache konfigurieren

Diese nehme ich zweigeteilt vor, denn alle (oder zumindest mehrere) Wordpress-Installationen sollen sich die gleichen Pool teilen. Die folgenden Zeilen landen in einer neue Datei /etc/apache2/conf.d/wp-fpm.conf und erklären dem Apache, wie er grundsätzlich mit dem PHP-FPM Kontakt aufnehmen kann:
# Wordpress poolFastCgiExternalServer /php-fpm-wp -host localhost:9001 -pass-header AuthorizationAlias /php-fpm-wp /php-fpm-wpAction php-fpm-wp /php-fpm-wp virtual<Location "/status"> SetHandler php-fpm-wp</Location>
Alles klar? Mir nicht, zumindest zunächst, denn alles ist unnötig kompliziert, wenn man Plack gewohnt ist. Diese Zeilen sind allerdings wichtig, also sollte man verstehen, was hier vor sich geht.
FastCgiExternalServer /php-fpm-wp -host localhost:9000 -pass-header Authorization
Diese Zeile stellt die eigentliche Verbindung zwischen Apache und dem PHP FastCGI-Server her. Dazu wird dem Webserver eine virtuelle Datei mit dem Namen /php-fpm-wp vorgegaukelt. Jeder Zugriff darauf wird an den FastCGI-Server weitergeleitet, der auf localhost:9000 wartet (dies entspricht der Listen - Konfiguration im Pool). Falls ein Script seine Nutzer über einen simulierten .htaccess - Zugriffsschutz prüfen möchte, wird diese Headerzeile zusätzlich zum Standard mit übermittelt. Die nicht ganz intuitive Anleitung dazu findet sich auf der FastCGI-Homepage.

 

Alias /php-fpm-wp /php-fpm-wp
Mit dieser zunächst vielleicht unsinnig aussehenden Zeile wird die (virtuelle) URL /php-fpm-wp auf die (virtuellen) Datei /php-fpm-wp umgeleitet. Jeder Zugriff auf http://www.irgendwas/php-fpm-wp liefert nicht die Datei /var/www/php-fpm-wp zurück, sondern versucht die Datei /wp-fpm-wp auszuliefert. Diese existiert zwar eigentlich gar nicht, aber die die FastCgiExternalServer - Zeile simuliert deren Existenz, also ist wieder alles in Ordnung.
Action php-fpm-wp /php-fpm-wp virtual
Jeder Zugriff wird innerhalb des Apache von einem so genannten Handler bearbeitet. Normalerweise ist das der default - Handler, aber mit Hilfe der Action - Zeile wird ein neuer Handler generiert. Dieser liegt nicht mehr innerhalb des Apache, sondern versucht, die Anfrage mit Hilfe des angegebenen Scriptes /php-fpm-wp zu bearbeiten. Der Zusatz virtual klärt den Apache darüber auf, dass er sich nicht darum kümmern muss, ob dieses Script tatsächlich existiert.

 

Um den Unterschied zwischen Dateien im Dateisystem und URL - Adressen besser deutlich zu machen, habe ich Dateien rot und URLs grün eingefärbt.

Die letzten drei Zeilen erlauben den Zugriff auf die Statusseite des PHP-FPM und sollten im Produktivbetrieb nach Abschluss der Tests entweder auskommentiert oder zugriffsgeschützt werden.

Bei einer Standardinstallation liegen die Konfigurationsdateien der einzelnen Apache VirtualServer in /etc/apache2/sites-available/. In der Datei zu meiner ersten WordPress-Installation, die den PHP-FPM nutzen soll, ergeben sich eigentlich nur zwei Änderungen. Im folgenden Beispiel verwede ich exemplarisch die Domain www.blog.wp, diese ist natürlich durch jeweils durch die eigene Domain zu ersetzen. Das DocumentRoot muss jetzt von /var/www/blog.wp auf /home/wp-fpm geändert werden und am Ende - direkt vor dem </VirtualHost> werden drei neue Zeilen eingefügt, die alle Zugriffe auf PHP-Dateien auf den PHP-FPM umleiten:

<FilesMatch "\.php$"> SetHandler php-fpm-wp</FilesMatch>
Damit ist die Apache-Konfiguration abgeschlossen und nach einem /etc/init.d/apache2 restart sollte die Statusseite via http://www.blog.wp/status erreichbar sein. Ansonsten stimmt etwas nicht und ich wünsche viel Spaß bei der Fehlersuche.

chroot - Jail einrichten

Zunächst müssen die Dateien von ihrem ursprünglichen Zuhause in das neue Verzeichnis verschoben werden. Dort finden sowohl Apache als auch PHP-FPM alles notwendige. Gleichzeitig muss der Inhaber geändert werden. Dieser Schritt muss später für jeden VirtualHost wiederholt werden, der über den neuen Pool bedient werden soll.
mv /var/www/blog.wp /home/wp-fpm/blog.wpchown wp-fpm.wp-fpm -R /home/wp-fpm/blog.wp
Leider übernimmt der PHP-FPM das DocumentRoot des Apache. Damit der Zugriff trotzdem funktioniert, muss in /home/wp-fpm jetzt ein entsprechender Symlink eingerichtet werden:
cd /home/wp-fpmmkdir homecd homeln -sv .. wp-fpm
 

Jetzt ist der Pfad /home/wp-fpm sowohl innerhalb als auch außerhalb der chroot-Umgebung erreichbar. Diese Lösung ist bestenfalls suboptimal, aber der PHP-FPM lässt einem hier keine andere Chance, denn ich habe leider keine Möglichkeit gefunden, das übergebene DocumentRoot zu ignorieren oder zu verändern.

Innerhalb der chroot - Sandbox benötigt PHP die grundlegenden Zeitzoneninformationen, diese müssen einfach kopiert werden:

cd /home/wp-fpmmkdir etccp /etc/localtime etc/mkdir -p usr/sharecp -a /usr/share/zoneinfo usr/share/
 

Jetzt sind alle notwendigen Dateien vorhanden.

Eine letzte Gemeinheit wird noch vom mySQL-Client beigesteuert, der schlauer als sein Nutzer sein möchte und Verbindungen auf localhost über einen Socket herzustellen versucht. Das schlägt natürlich fehl, da der PHP-FPM innerhalb der chroot - Umgebung keinen Zugriff auf den Socket in /var/run/mysqld/mysqld.sock hat. Wenn der Datenbankserver in der Wordpress-Konfiguration wp-config.php allerdings von localhost auf 127.0.0.1 geändert wird, besteht dieses Problem nicht und der Zugriff ist möglich.

Jetzt sollte die gerade umgestellte Domain über den PHP-FPM erreichbar sein - und kann keine Dateien außerhalb /home/wp-fpm mehr verändern. Ist das nicht der Fall - hilft leider nur Debugging, Kontrolle der Apache-Logfiles, ggf. das Aktivieren des PHP-FPM Access-Logs und selbst strace kam bei meinen Bemühungen zum Einsatz.

Nachdem alle PHP-Scripte auf den FPM verschoben wurden, empfiehlt es sich noch, PHP im Apache gänzlich zu deaktivieren:

a2dismod php5

 

Jetzt liefert der Apache nur noch statische Dateien aus und überlässt die PHP-Scripte dem FastCGI-Server.

h1Hier laufen also vier Worker-Prozesse und warten auf eingehende Anfragen. Leider gibt es derzeit noch niemanden, der anfragen kann, also folgt als nächstes die Apache-Konfiguration.

 

Noch keine Kommentare. Schreib was dazu

Schreib was dazu

Die folgenden HTML-Tags sind erlaubt:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>