Unterschiede zwischen den Revisionen 8 und 9
Revision 8 vom 2020-12-27 23:39:23
Größe: 16545
Autor: anonym
Kommentar:
Revision 9 vom 2020-12-27 23:43:00
Größe: 16655
Autor: anonym
Kommentar:
Gelöschter Text ist auf diese Art markiert. Hinzugefügter Text ist auf diese Art markiert.
Zeile 285: Zeile 285:
 * Angabe des Cert-Pfades, damit die Validierung klappt:{{{
php_admin_value[openssl.capath] = /etc/ssl/certs}}}
Zeile 308: Zeile 310:
php_admin_value[openssl.capath] = /usr/lib/ssl/certs php_admin_value[openssl.capath] = /etc/ssl/certs

Einige Technik-Kollektive bieten Wordpress-Hosting auf Basis von Wordpress-Multisite an, bspw. blackblogs.org und 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 Macros die Seiteneinrichtung zu vereinfachen. Zur Absicherung verwenden wir ein chroot, basierend auf PHP-FPM.

Einführung

Dieser Anleitung basiert auf anderen sehr gut Howtos. Für ein grundlegendes Verständnis solltest du diese auch lesen:

Variablen, Versionen und Pfade

Zur Veranschaulichung werden die nachfolgenden Variablen und Werte in der Anleitung genutzt:

Beispiel-Domain

example.org

Benutzerkonto

katja

PHP-Version

7.3

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 Passwort1 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 werden2:

    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 aktivieren3:

    quotacheck -cum
    
    quotaon -v /data
  • Nun können für den neuen Nutzer die Quota-Regeln erstellt werden:

    edquota katja
  • Weitere Hinweise dazu im 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 Dashboard4. 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:

    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/lib/ssl/openssl.cnf \
            /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 hier.

  • Zudem ist 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 Apachekonfiguration und in der Verzeichnisstruktur verwenden werden.

  • Damit das PHP-FPM-Socket gefunden wird:

    php_admin_value[doc_root] = /htdocs
    php_admin_value[cgi.fix_pathinfo] = 0
  • 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)
  • Ein paar Abweichungen von den gängigen Anleitungen zur Einrichtung von PHP-FPM seien noch erwähnt:
    • Das Mounten oder Kopieren von sendmail in das chroot für Mailfunktionalität: Das funktioniert so nicht. Sendmail hat weitere Abhängigkeiten und braucht Zugriff auf verschiedene Verzeichnisse. Als Aternative wird 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.

    • Das Mounten oder Kopieren von /etc/ssl/certs in das chroot: Die Zertikate alleine reichen für die volle Funktionalität nicht aus. PHP benötigt zudem Zugriff auf /usr/lib/ssl/openssl.cnf.


Hinweise und Links

CategorySystemausfall

Fussnoten

  1. Die Anmeldung per ssh-Schlüssel ist weiterhin möglich (1)

  2. Damit die Quota-Kernelmodule verfügbar sind (2)

  3. Falls quotaon den Fehler Devive or ressource busy anzeigt, Quota deaktivieren und erneut aktivieren (3)

  4. Die Installation über das Wordpress Theme- und Pluginverzeichnis ist weiterhin möglich (4)


Creative Commons Lizenzvertrag
This page is licensed under a Creative Commons Attribution-ShareAlike 2.5 License.