16157
Kommentar: init
|
← Revision 20 vom 2023-06-30 15:15:44 ⇥
0
|
Gelöschter Text ist auf diese Art markiert. | Hinzugefügter Text ist auf diese Art markiert. |
Zeile 1: | Zeile 1: |
Einige [[https://riseup.net/en/security/resources/radical-servers|Technik-Kollektive]] bieten Wordpress-Hosting auf Basis von [[https://wordpress.org/support/article/create-a-network/|Wordpress-Multisite]] an, bspw. [[https://blackblogs.org/|blackblogs.org]] und [[https://noblogs.org/|noblogs.org]]. In der Regel ist die Funktionalität von Wordpress beschränkt, so dass sich nur eine Auswahl von Themes und Plugins installieren lassen. Ein anderer Ansatz ist der Betrieb einzenen Wordpress-Seiten in einem abgesichertem Hosting. Diese Seite beschreibt die Einrichtung eines solchen Hostings. Entgegen anderen Anleitungen verwenden wir Apache, um mittels [[http://httpd.apache.org/docs/2.4/mod/mod_macro.html|Macros]] die Seiteneinrichtung zu vereinfachen. Zur Absicherung verwenden wir ein WikiPediaDe:chroot, basierend auf PHP-FPM. <<TableOfContents>> = Einführung = Dieser Anleitung basiert auf anderen sehr gut Howtos. Für ein grundlegendes Verständnis solltest du diese auch lesen: * [[https://nickyreinert.de/blog/2019/04/12/mehrere-virtuelle-server-mit-nginx-und-php-fpm-fur-wordpress-teil-1-3/|nickyreinert.de]] für einen allgemeinen Überblick * [[https://blog.kthx.at/2015/09/23/php-fpm-chroot/|blog.kthx]] zum Setup-Skript * [[https://www.vennedey.net/resources/3-Secure-webspaces-with-NGINX-PHP-FPM-chroots-and-Lets-Encrypt|vennedey.net]] zu den Sicherheitimplikation von NSCD und opcache * [[https://binary-butterfly.de/artikel/das-perfekte-php-wordpress-setup/|binary-butterfly.de]] zur Verwendung von `mknod` statt den mount-binds == Variablen und Pade == Zur Veranschaulichung werden die nachfolgenden Variablen und Werte in der Anleitung genutzt: || Beispiel-Domain || example.org || || Benutzerkonto || katja || || Quota-Partition (ext4) || /data || || Skript-Verzeichnis || /usr/local/bin || || Wordpress-Speicherort || /data/wordpress || Bitte beachte, dass sich einzelnen Pfadangaben teilweise in mehreren Konfigurationsdateien benutzt werden. Bei Änderungen eines Pfades musst du also alle Vorkommen ändern. = Benutzerkonto zur PHP-Ausführung = * Ein Systemnutzer mit eingeschränkten Rechten ist für die Auführung des PHP-Prozesses zuständig. Die Anmeldung per Passwort<<FootNote(Die Anmeldung per ssh-Schlüssel ist weiterhin möglich)>> und das Anlegen eines Home-Verzeichnissen werden deaktiviert:{{{ adduser --disabled-login --disabled-password --no-create-home --gecos katja}}} * Im späteren Verlauf wird das Wordpress-Verzeichnis diesem neuen Benutzerkonto übergeben. = Quotas einrichten = * Disk-Quotas sorgen dafür, dass eine einzelne Wordpress-Instanz nicht den gesamten Speicherplatz auf einer Partition aufbrauchen kann. Läuft Wordpress in einem KVM-Gast muss ein Kernel installiert werden<<FootNote(Damit die Quota-Kernelmodule verfügbar sind)>>:{{{ apt install linux-image-amd64 quota}}} * Anschließend werden der ext4-Partition in `/etc/fstab` die notwendigen Parameter übergeben:{{{ /dev/vdb /data auto noatime,nodiratime,usrjquota=aquota.user,jqfmt=vfsv1 0 0}}} * Die Partition mit den neuen Werten remounten:{{{ mount -vo remount /data}}} * Den Quota-Index erstellen und Quotas aktivieren<<FootNote(Falls `quotaon` den Fehler `Devive or ressource busy anzeigt`, Quota deaktivieren und erneut aktivieren)>>:{{{ quotacheck -cum quotaon -v /data}}} * Nun können für den neuen Nutzer die Quota-Regeln erstellt werden:{{{ edquota katja}}} * Weitere Hinweise dazu im [[https://wiki.archlinux.org/index.php/Disk_quota|Arch-Wiki]] = Apache: Seitenkonfiguration mit Macros = * Durch Apache-Macros ist es möglich, nur eine Seitenkonfiguration für alle Wordpress-Instanzen zu erstellen. * Die Seitenkonfiguration `/etc/apache/sites-available/wordpress.conf` basiert auf dem später näher erläuterten Verzeichnisaufbau. Die Konfiguration verhindert durch Rewriting die Installation von eigenen Themes und Modulen über das Dashboard<<FootNote(Die Installation über das Wordpress Theme- und Pluginverzeichnis ist weiterhin möglich)>>. Zudem wird eine optionale htaccess-Datei eingebunden. Damit lassen sich einzelnen Wordpress-Seiten per htacess zusätzlich schützen:{{{ <Macro WPSite $domain $pool> <VirtualHost *> ServerName $domain SetEnv HTTPS on DocumentRoot /data/wordpress/$domain/htdocs ErrorLog /var/log/apache2/$domain.error IncludeOptional /data/wordpress/_htaccess.d/$domain.conf Include /etc/apache2/conf-available/wordpress-cache.conf # Installation von eigenen Themes und Plugins verhindern <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_URI} ^/wp-admin/update.php RewriteCond %{QUERY_STRING} action=upload-(plugin|theme) RewriteRule (.*) /wp-admin/plugin-install.php [QSD,R=302,L] </IfModule> <Directory /data/wordpress/$domain/htdocs> Require all granted Options SymLinksIfOwnerMatch MultiViews IncludesNoExec AllowOverride AuthConfig FileInfo Indexes Limit Options # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-includes <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^wp-admin/includes/ - [F,L] RewriteRule !^wp-includes/ - [S=3] RewriteRule ^wp-includes/[^/]+\.php$ - [F,L] RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L] RewriteRule ^wp-includes/theme-compat/ - [F,L] </IfModule> </Directory> <IfModule proxy_fcgi_module> <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php-fpm-$pool.sock|fcgi://$domain" </FilesMatch> <FilesMatch ".+\.phps$"> Require all denied </FilesMatch> <FilesMatch "^\.ph(ar|p|ps|tml)$"> Require all denied </FilesMatch> <Files xmlrpc.php> Require all denied </Files> <Files wp-config.php> Require all denied </Files> </IfModule> </VirtualHost> </Macro> # Includieren der Variablen Include /etc/apache2/conf-available/wordpress-sites.conf UndefMacro WPSite}}} * Alle Variablen werden in `/etc/apache2/conf-available/wordpress-sites.conf` definiert. Die `pool`-Variable bezeichnet den Namen des PHP-FPM-Pools:{{{ # Muster: Use WPSite $domain $pool Use WPSite example.org katja}}} * Für mehr Performance wird über `/etc/apache2/conf-available/wordpress-cache.conf` ein globaler Cache definiert:{{{ <IfModule mod_expires.c> ExpiresActive on # whitelist expires rules ExpiresDefault "access 1 month" # Favicon (cannot be renamed) ExpiresByType image/x-icon "access plus 1 week" # Media: images, video, audio ExpiresByType image/gif "access plus 1 month" ExpiresByType image/png "access plus 1 month" ExpiresByType image/jpg "access plus 1 month" ExpiresByType image/jpeg "access plus 1 month" ExpiresByType video/ogg "access plus 1 month" ExpiresByType audio/ogg "access plus 1 month" ExpiresByType video/mp4 "access plus 1 month" ExpiresByType video/webm "access plus 1 month" # Webfonts ExpiresByType application/x-font-ttf "access plus 1 year" ExpiresByType font/opentype "access plus 1 year" ExpiresByType application/x-font-woff "access plus 1 year" ExpiresByType image/svg+xml "access plus 1 year" # CSS and JavaScript ExpiresByType text/css "access plus 1 month" ExpiresByType text/javascript "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" <IfModule mod_headers.c> Header append Cache-Control "public" </IfModule> </IfModule>}}} * Anschließend werden alle benötigten Module und die Seite aktiviert:{{{ a2enmod macros expires a2ensite wordpress.conf systecmtl reload apache2}}} = Chroot-Verzeichnis-Struktur = * Oberste Ebene eines jeden chroots ist das jeweilige Domain-Verzeichnis. Für die Beispieldomain ist es `data/wordpress/example.org`. * Das `tmp`-Verzeichnis hat eine doppelte Funktion: Es markiert ein chroot-Verzeichnis. Das ist für das später vorgestellte chroot-setup-Skript wichtig. Zudem werden dort temporäre Daten gespeichert. * Wordpress selbst liegt unter `htdocs`. Daraus ergibt sich folgende Struktur:{{{ ├── data/wordpress/example.org │ ├── htdocs/ │ │ └── index.php │ ├── logs/ | ├── sessions/ | └── tmp/}}} * `htdocs` gehört dem oben erstellten Benutzerkonto `katja:katja`. Alle anderen Verzeichnisse `root:root`. = Chroot-Setup-Skript = * Das Skript `/usr/local/bin/php-fpm-chroot-setup` nimmt die Ersteinrichtung des chroots und der Verzeichnisstruktur vor. Es kopiert zudem die notwendigen Daten in das chroot. * Im Wesentlichen handelt es sich um Daten für die Namensauflösung, das Überprüfen von Zertifikaten und den mysql-Socket:{{{ #!/bin/sh BASE_DIR="/data/wordpress" CHROOT_DIR="$BASE_DIR/*" COPY_SOURCES="\ /etc/hosts \ /etc/resolv.conf \ /etc/ssl/certs \ /lib/x86_64-linux-gnu/libnss_dns.so.2 \ /usr/share/zoneinfo" MOUNTS="/var/run/mysqld" MKNOD_DIRS="\ dev \ logs \ sessions \ tmp" if [ $# -ge 1 ]; then ACTION=$1 shift else ACTION=-help fi case "$ACTION" in init) [ $# -ne 1 ] && echo "action 'init' requires one parameter: domain - e.g. 'example.org'" && exit 1 DOMAIN="$1" for i in $MKNOD_DIRS; do mkdir -p "$BASE_DIR"/"$DOMAIN"/"$i" done for i in $CHROOT_COPY_SOURCE; do cp -urpL --parents "$i" "$BASE_DIR"/"$DOMAIN" done mknod -m 666 "$BASE_DIR"/"$DOMAIN"/dev/null c 1 3 mknod -m 444 "$BASE_DIR"/"$DOMAIN"/dev/random c 1 8 mknod -m 444 "$BASE_DIR"/"$DOMAIN"/dev/urandom c 1 9 mknod -m 666 "$BASE_DIR"/"$DOMAIN"/dev/zero c 1 5 ;; restart|force-reload|start) $0 stop 2>/dev/null for chrootdir in $CHROOT_DIR; do if [ -d "${chrootdir}/tmp" ]; then chmod 777 "${chrootdir}/tmp" chmod +t "${chrootdir}/tmp" echo "Setting up ${chrootdir}..." for f in $MOUNTS; do if [ -d "$f" ]; then mkdir -p "${chrootdir}${f}" mount --bind -o ro "${f}" "${chrootdir}${f}" else mkdir -p "${chrootdir}$(dirname "${f}")" touch "${chrootdir}${f}" mount --bind -o ro "${f}" "${chrootdir}${f}" fi done cp -urpL --parents $COPY_SOURCES "${chrootdir}" fi done ;; stop) for chrootdir in $CHROOT_DIR; do if [ -d "${chrootdir}/tmp" ]; then echo "Destructing ${chrootdir}..." for f in $MOUNTS; do umount "${chrootdir}${f}" if [ -d "${chrootdir}${f}" ] && [ ! $(ls -A "${chrootdir}${f}") ]; then rmdir "${chrootdir}${f}" elif [ -f "${chrootdir}${f}" ]; then rm "${chrootdir}${f}" fi done fi done ;; help|--help) echo "Usage: $N {start|stop|restart|force-reload|init}" >&2 exit 1 ;; *) "$0" help >&2 exit 1 ;; esac }}} * Nähere Erläuterungen zu dem Skript findest du [[https://blog.kthx.at/2015/09/23/php-fpm-chroot/|hier]]. * Zudem ist [[https://www.vennedey.net/blog/3-NSCD-socket-in-a-PHP-FPM-chroot-leaks-user-database|hier]] erklärt, weshalb die Verwendung von NSCD zur Namensauflösung keine gute Idee ist. Als Alternative kopiert das Skript `/etc/resolv.conf` und `libnss_dns.so.2`. == Initieren beim Start == * Einige Daten (im Beispiel der mysql-Socket) werden durch das Skript per bind-mount in das chroot gemountet. Diese müssen bei jedem Start wieder hergestellt werden. Dies übernimmt der systemd-Service `/etc/systemd/system/php-fpm-chroot.service`:{{{ [Unit] Description=Set up PHP-FPM chroot mounts After=network.target php7.3-fpm.service mysqld.service [Service] Type=forking ExecStart=/usr/local/bin/php-fpm-chroot-setup start ExecStop=/usr/local/bin/php-fpm-chroot-setup stop RemainAfterExit=yes [Install] WantedBy=multi-user.target}}} * Den Service aktivieren und den Deamon neu laden:{{{ systemctl enable php-fpm-chroot.service systemctl daemon-reload}}} = Konfiguration des PHP-FPM-Pools mit Chroot = * Die Konfigurarion des PHP-FPM-Pools basiert auf den Pfadangaben, die in der [[#Apache: Seitenkonfiguration mit Macros|Apachekonfiguration]] und in der [[#Chroot-Verzeichnis-Struktur|Verzeichnisstruktur]] verwenden werden. * Damit das PHP-FPM-Socket gefunden wird:{{{ php_admin_value[doc_root] = /htdocs php_admin_value[cgi.fix_pathinfo] = 0}}} * [[https://bugs.php.net/bug.php?id=69090|Sicherheitseinstellugnen]] für opcache:{{{ php_admin_value[opcache.validate_permission] = 1 php_admin_value[opcache.validate_root] = 1}}} * Angabe des Cert-Pfades, damit die Validierung klappt:{{{ php_admin_value[openssl.capath] = /etc/ssl/certs}}} * Alles in allem die vollständige FPM-Konfiguration:{{{ [katja] prefix = /data/wordpress/example.org user = $pool group = www-data listen = /run/php/php-fpm-$pool.sock listen.owner = $pool listen.group = www-data listen.mode = 0660 listen.allowed_clients = 127.0.0.1 pm = ondemand pm.max_children = 5 pm.start_servers = 2 pm.process_idle_timeout = 10s; pm.max_requests = 100 chroot = $prefix chdir = / security.limit_extensions = .php .php3 .php4 .php5 php_admin_value[doc_root] = /htdocs php_admin_value[cgi.fix_pathinfo] = 0 php_admin_value[opcache.validate_permission] = 1 php_admin_value[opcache.validate_root] = 1 php_admin_value[session.save_path] = /sessions php_admin_value[openssl.capath] = /etc/ssl/certs php_admin_value[disable_functions] = mail,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_exec,passthru,system,proc_get_status,proc_close,proc_nice,proc_terminate,proc_open,curl_ini,parse_ini_file,show_source,dl,symlink,system_exec,exec,shell_exec,phpinfo}}} = Abschließende Hinweise = * In der obigen Apachekonfiguration wird mittels rewrite die Installation von eigenen Themes und Plugins unterbunden. Ergänzend dazu kann die Bearbeitung vorhandener Themes und Plugins im Dashboard verhindert werden:{{{ # wp-config.php ... define('DISALLOW_FILE_EDIT', true)}}} * Einige Anleitung beschreiben das Mounten oder Kopieren von `sendmail` in das chroot für Mailfunktionalität. Das funktioniert so nicht. Sendmail hat weitere Abhängigkeiten hat und braucht Zugriff auf verschiedene Verzeichnisse. Alt Aternative wird [[https://www.acme.com/software/mini_sendmail/|mini_sendmail]] vorgeschlagen. Mittels der oben dargestellten PHP-FPM-Konfiguration wird `mail()` eh deaktiviert. Sendmail ist also in diesem Setup nicht notwendig. Der Mailversand aus Wordpress erfolgt mittels eines SMTP-Plugins. ---- '''Fussnoten und Hinweise''' * [[https://unix.wroclaw.pl/igort/en/php-fpm-chroot-fopen-url-problems.html|Validierung von Zertifikaten]] im chroot debuggen CategorySystemausfall |