Zwei Türen und ein Wächter

Verfasst von

in

Heute, an Tag 2 des Projekts, stand bei WP Umbrella ein Wort, das ich nicht lesen wollte: getrennt. Gestern hatte die Verbindung noch gestanden, nach einigem Probieren.

WP Umbrella ist mein Aufpasser für WordPress. Es prüft, ob die Seite läuft, und macht Sicherungen. Ohne Verbindung gibt es beides nicht.

Ich schaute ins Protokoll meines Türstehers Caddy. Dort klopfte WP Umbrella von der Adresse 212.83.175.107 an und bekam ein Nein. Freigegeben hatte ich gestern nur eine einzige Adresse: 212.83.142.5. Der Dienst nutzt mehrere. Auf der Webseite von WP Umbrella steht die vollständige Liste, vier Adressen. Ich habe alle vier eingetragen. Die Ausnahme gilt nur, wenn Adresse und Name des Dienstes zusammen stimmen.

Dann kam ein Gedanke: Adressen können sich ändern. Also habe ich mir einen Wächter gebaut. Er schaut jeden Morgen um 04:30 Uhr UTC in die Liste und vergleicht sie mit meiner Kopie. Er ändert nur etwas, wenn sich die Liste wirklich verändert hat. Eine leere oder schiefe Liste übernimmt er nicht.

Beim Lesen des Protokolls fiel noch etwas auf: Die geheimen Schlüssel von WP Umbrella standen dort im Klartext, Zeile für Zeile. Ich habe Caddy beigebracht, sie wegzulassen, und für die Seite einen neuen Secret Token erzeugt. Der API-Key meines Kontos, den ich für alle Seiten nutze, blieb unberührt. Es sind zwei verschiedene Schlüssel.

Die vier Adressen waren frei, und WP Umbrella blieb getrennt. Im Protokoll sah ich jetzt etwas anderes: Die Anfragen kamen an Caddy vorbei, und das Nein kam von Apache, dem Webserver hinter Caddy. Die Ablehnung kam aus WordPress selbst.

Claude suchte im Quelltext der Plugins nach dem Satz aus der Antwort, realm="Website", und fand ihn in Simply Static Pro. Das Plugin hat eine eigene Passwortabfrage, und bei mir war sie eingeschaltet. WP Umbrella kommt ohne Passwort durch Caddy, und direkt dahinter fragt das Plugin noch einmal. Zwei Türen hintereinander, und nur eine kannte die Ausnahme.

Ich habe den Schalter in Simply Static ausgeschaltet und einen Probe-Export gestartet. Er lief durch. Dann der Verbindungstest bei WP Umbrella: verbunden.

Ergebnis: WP Umbrella ist verbunden, die Zugangsliste hält sich täglich selbst aktuell, das Protokoll enthält keine Schlüssel mehr. Was ich mitnehme: erst nachsehen, wer „Nein“ sagt, bevor ich an der falschen Tür weiterbaue.

Zeig mir, wie es geht: IP-Liste und täglicher Wächter
  • Quelle der Liste: https://wp-umbrella.com/ips-v4/, eine Adresse pro Zeile. Stand 2026-10-06: 212.129.45.77, 212.83.142.5, 212.83.175.107, 62.4.26.31.
  • Skript umbrella-ips-check.sh (0.2) nach /usr/terruhn.it/sbin/, dazu Dienst und Timer (täglich 04:30 UTC, Persistent=true):
install -m 0755 umbrella-ips-check.sh /usr/terruhn.it/sbin/umbrella-ips-check.sh
install -m 0644 umbrella-ips-check.service umbrella-ips-check.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now umbrella-ips-check.timer
  • Ablauf des Skripts: laden, nur gültige IPv4-Zeilen behalten, sortieren, Anzahl prüfen (1 bis 50), mit der gespeicherten Liste vergleichen. Bei Änderung die Datei /srv/caddy/etc/umbrella-ips.caddy mit einer Zeile remote_ip … schreiben, caddy validate ausführen und erst dann caddy reload. Schlägt die Prüfung fehl, wird das alte Snippet wiederhergestellt.
  • Kontrolle im Journal:
journalctl -u umbrella-ips-check.service --since today

Erwartet: OK: keine Änderung (4 IPs).

Zeig mir, wie es geht: Caddy mit Ausnahme und gefiltertem Protokoll
  • Im Matcher @geschuetzt des Site-Blocks steht der Import statt fester Adressen. Matcher innerhalb eines not { … }-Blocks bilden einen gemeinsamen Satz, die Ausnahme gilt also nur, wenn IP und User-Agent passen:
@geschuetzt not {
    import umbrella-ips.caddy
    header_regexp ua User-Agent ^WPUmbrella
}
  • Negativtest von einem Rechner außerhalb der Liste, erwartet 401:
curl -s -o /dev/null -w '%{http_code}\n' -A 'WPUmbrella+test' https://thekeepitsimpleproject.org/
  • Das Zugriffsprotokoll enthielt die Header X-Secret-Token, X-Authorization und die Signaturen. Filter im Site-Block:
log {
    format filter {
        wrap json
        request>headers>X-Secret-Token delete
        request>headers>X-Authorization delete
        request>headers>X-Umbrella-Signature delete
        request>headers>X-Umbrella-Signature-V2 delete
    }
}
  • Kontrolle, erwartet 0:
docker logs caddy --since 5m 2>&1 | grep -c -E 'X-Secret-Token|X-Umbrella-Signature'
  • Neuer Secret Token pro Seite: /wp-admin/options-general.php?page=wp-umbrella-settings&support=1, Schaltfläche „Regenerate Secret Token“, danach im Konto neu synchronisieren. Der API-Key des Kontos bleibt davon unberührt.
  • Beim Prüfen half, dass Caddy die echte Absender-IP sieht (remote_ip im Log). Der Kommentar „greift derzeit nicht“ im Caddyfile war veraltet.
Zeig mir, wie es geht: Die zweite Tür in Simply Static Pro
  • Befund im Caddy-Protokoll: Anfragen von 212.83.142.5 kamen mit Server: Apache an, Status 401, Header WWW-Authenticate: Basic realm="Website". Die Ablehnung kam also nicht von Caddy.
  • Fundstelle: wp-content/plugins/simply-static-pro/src/misc/class-ssp-basic-auth.php. Die Klasse prüft bei init, ob http_basic_auth_on gesetzt ist, und erwartet PHP_AUTH_USER und PHP_AUTH_PW aus den Simply-Static-Einstellungen. Ausgenommen sind nur die Pfade ssp-form, wp-json, graphql, freemius, wordpress.org und wp-cron.php. WP Umbrella ruft / und /index.php?rest_route=… auf, beides ist nicht ausgenommen.
  • Die Anfragen von WP Umbrella tragen ohne Caddy-Anmeldung keinen Authorization-Header. Die Prüfung im Plugin schlägt deshalb zu.
  • Lösung: In den Simply-Static-Einstellungen „HTTP Basic Auth“ ausschalten, Benutzername und Passwort stehen lassen. Der Export liest die Zugangsdaten getrennt davon (Util::get_basic_auth_header_for_url) und läuft weiter durch.
  • Verworfene Alternative: Ein Must-Use-Plugin mit dem Filter ssp_basic_auth_enabled, das die Prüfung nur für den User-Agent WPUmbrella abschaltet. Es hätte die Prüfung für alle anderen Zugriffe erhalten und braucht dafür eine zusätzliche Datei. Der Schalter ist die einfachere Variante und lässt sich jederzeit zurückstellen.
  • Kontrolle nach dem Verbindungstest, erwartet 200:
docker logs caddy --since 5m 2>&1 | grep -E '"remote_ip":"(212\.129\.45\.77|212\.83\.142\.5|212\.83\.175\.107|62\.4\.26\.31)"' | grep -oE '"remote_ip":"[^"]*"|"uri":"[^"]*"|"status":[0-9]+'