Showcase nachgezogen: restliche Portfolio-Dateien auf aktuellen Stand

arnol 2026-08-07 19:05:46 +02:00
commit a9a755ac31

@ -223,7 +223,7 @@ Danach: beim ersten Login jedes Nutzers erscheint automatisch ein QR-Code zur Er
## 9. Caddy (öffentlicher Reverse Proxy mit Auto-HTTPS) ## 9. Caddy (öffentlicher Reverse Proxy mit Auto-HTTPS)
Voraussetzung: Domain bei STRATO konnektiert (dauert ggf., bis DENIC-Registrierung durch ist — separater Schritt im STRATO-Panel), A-Record `guac.<DOMAIN>``<VPS-öffentliche-IP>` im STRATO-DNS-Panel gesetzt (kein CLI-Schritt). Voraussetzung: Domain bei STRATO konnektiert (dauert ggf., bis DENIC-Registrierung durch ist — separater Schritt im STRATO-Panel), A-Record `guac.<DOMAIN>``<VPS_PUBLIC_IP>` im STRATO-DNS-Panel gesetzt (kein CLI-Schritt).
```bash ```bash
apt-get install -y caddy apt-get install -y caddy
@ -296,6 +296,137 @@ services:
Nach Änderung: `docker compose up -d` (Container-Neustart nötig, Limits gelten nur für neu erstellte Container, nicht rückwirkend). Nach Änderung: `docker compose up -d` (Container-Neustart nötig, Limits gelten nur für neu erstellte Container, nicht rückwirkend).
## 12. SSH: nur über Tailscale erreichbar
SSH (Port 22) muss nach Abschluss der Ersteinrichtung nicht mehr öffentlich erreichbar sein — alle künftigen Änderungen laufen ausschließlich von Tailscale-verbundenen Geräten aus. Trotz Key-only-Auth + fail2ban erzeugt ein offener Port dauerhaftes Scan-/Bot-Rauschen ohne Nutzen (Fund eines externen Security-Reviews).
```bash
cat > /etc/ssh/sshd_config.d/tailscale-only.conf <<'EOF'
ListenAddress <TS_IP_VPS>
ListenAddress 127.0.0.1
ListenAddress ::1
EOF
mkdir -p /etc/systemd/system/ssh.service.d
cat > /etc/systemd/system/ssh.service.d/after-tailscale.conf <<'EOF'
[Unit]
After=tailscaled.service
Wants=tailscaled.service
EOF
sshd -t && systemctl daemon-reload && systemctl restart ssh.service
```
`ListenAddress` bindet `sshd` nur noch an die Tailscale-IP + Loopback statt an `0.0.0.0`/`[::]:22` — Port 22 verschwindet komplett aus dem öffentlichen Internet, bleibt aber über Tailscale normal erreichbar.
⚠️ **NICHT TUN: `ListenAddress` auf die Tailscale-IP setzen, ohne `ip_nonlocal_bind` zu aktivieren.** Beim Booten startet `sshd`, **bevor** `tailscaled` die Adresse vergeben hat. Der Bind scheitert:
```
sshd[848]: Server listening on 127.0.0.1 port 22.
sshd[848]: error: Bind to port 22 on <TS_IP_VPS> failed: Cannot assign requested address.
```
**Tückisch daran: der Dienst bleibt `active (running)`.** Weil der Bind an Loopback gelingt, meldet systemd keinen Fehlschlag — von außen sieht es aus wie „SSH tot", intern ist alles grün. Ohne den Blick ins Journal sucht man am falschen Ende.
Ein Drop-In mit `After=tailscaled.service` reicht **nicht**: das garantiert nur, dass der Dienst *gestartet* wurde, nicht dass die IP schon zugewiesen ist. Korrekt ist:
```bash
echo 'net.ipv4.ip_nonlocal_bind=1' > /etc/sysctl.d/99-nonlocal-bind.conf
sysctl -p /etc/sysctl.d/99-nonlocal-bind.conf
```
Damit darf sich ein Prozess an eine Adresse binden, die es noch nicht gibt. Gegenprobe ohne Reboot — an eine nicht vergebene Adresse binden, das muss gelingen:
```bash
python3 -c "import socket; s=socket.socket(); s.bind(('<eine noch nicht vergebene Tailscale-IP>', 22222)); print('ok')"
```
⚠️ **Die STRATO-VNC-Konsole liegt auf en-US.** Ein deutsches Passwort mit `z`, `y` oder Sonderzeichen kommt dort anders an, als es getippt wird — das korrekte Passwort wirkt dann falsch. Beim Tippen die US-Belegung mitdenken. **Genau daran ist auch der Fix zunächst gescheitert:** in der Datei landete ein falscher, ähnlich klingender sysctl-Schlüssel statt `net.ipv4.ip_nonlocal_bind=1`. `sysctl` quittiert einen falschen Schlüssel mit `cannot stat ...` — wer die Meldung übersieht, hat eine Datei, die nach Lösung aussieht und nichts tut. **Nach jedem sysctl-Eingriff den Laufzeitwert prüfen**, nicht die Datei:
```bash
sysctl -n net.ipv4.ip_nonlocal_bind # muss 1 liefern
```
⚠️ **NICHT TUN:** Stattdessen eine eigene Firewall-Regel (iptables/nftables) einziehen, um Port 22 auf öffentlichen Interfaces zu blocken. Das VPS-Regelwerk wird bereits dicht von drei Stellen dynamisch verwaltet — Tailscale selbst (`ts-input`/`ts-forward`-Chains, `nft list ruleset` markiert sie explizit als „managed by iptables-nft, do not touch!"), Docker (eigene `DOCKER-*`-Chains) und fail2ban (`f2b-table`). Eine eigene Regel (z.B. über den bisher deaktivierten `nftables.service`, dessen Default-Config mit `flush ruleset` startet) riskiert, Tailscales/Dockers Regeln beim Boot zu überschreiben, abhängig von der Start-Reihenfolge der systemd-Units. Die `sshd`-Bindung ist der isolierte, leicht zu verifizierende/revertierende Weg.
⚠️ **NICHT TUN:** Den systemd-Override fürs Boot-Ordering vergessen. `sshd` versucht beim Start sofort an die Tailscale-IP zu binden — existiert das `tailscale0`-Interface zu dem Zeitpunkt noch nicht (z.B. direkt nach einem Reboot, bevor `tailscaled` hochgefahren ist), schlägt der Bind fehl und `sshd` startet gar nicht, auch nicht über Tailscale. `After=`/`Wants=tailscaled.service` in `ssh.service.d/` stellt die richtige Reihenfolge sicher.
**Verifikation:** `ss -tlnp | grep :22` zeigt nur noch die Tailscale-IP/`127.0.0.1:22`/`[::1]:22`, kein `0.0.0.0:22` mehr; SSH über die Tailscale-IP weiterhin normal getestet. Externe Nicht-Erreichbarkeit lässt sich nicht von innerhalb des Tailnets verifizieren — dafür ein Gerät ohne Tailscale-Verbindung (z.B. Handy mit Mobilfunk, Tailscale aus) oder einen Online-Port-Checker nutzen.
**Rollback:** beide neuen Dateien löschen, `systemctl daemon-reload && systemctl restart ssh.service` — SSH ist sofort wieder auf allen Interfaces erreichbar.
### `root`-`authorized_keys`: persönlicher Key jetzt über den Passwortmanager
Ein eingebautes SSH-Key-Feature des Passwortmanagers (Key-Erzeugung + SSH-Agent, privater
Schlüssel verlässt den Vault nie) ersetzt den bisherigen, lokal auf einem Windows-PC liegenden Key
(`<PRIMARY_USER>@PC1`) dagegen. Alter Key entfernt (lokal *und* aus `authorized_keys`), neuer Key
zunächst als geteilter Eintrag (`<PRIMARY_USER>@vaultwarden`) ergänzt — auf **VPS und PVE-Host identisch**.
⚠️ **Bewusst nur als Zwischenschritt dokumentiert, nicht als Zielzustand:** ein einzelner Key für
mehrere Systeme mit unterschiedlichem Risikoprofil (VPS öffentlich exponiert, PVE-Host nie) hebt die
Kompromiss-Eindämmung teilweise wieder auf, die getrennte Keys bringen würden. Vollständige
Individualisierung (ein dedizierter Key je System) ist der eigentliche Zielzustand — bewusst als
eigene, offene Übung dokumentiert statt vom KI-Agenten automatisiert.
**Zweiter, nicht eindeutig zugeordneter Altkey auf dem PVE-Host** — Herkunft nicht mehr
rekonstruierbar, bewusst nicht blind entfernt, sondern als offener Punkt dokumentiert.
Service-Keys bleiben davon unberührt (z. B. ein Backup-Pull-Key mit erzwungenem `restrict,command="rrsync
-ro /root/backups/"` — kein interaktiver Zugriff, eigener Zweck, kein Teil dieser Aufräumaktion).
## 13. Tailscale Exit-Node
Bewusst auf dem VPS statt dem PVE-Host eingerichtet — Speedtest zeigt einen deutlich höheren Upload auf dem VPS gegen die vergleichsweise langsame Heimleitung (DS-Lite). Der PVE-Host bleibt reiner Subnet-Router, beide Rollen laufen gleichzeitig.
```bash
cat > /etc/sysctl.d/99-tailscale.conf <<'EOF'
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sysctl -p /etc/sysctl.d/99-tailscale.conf
tailscale set --advertise-exit-node
```
⚠️ **NICHT TUN:** `tailscale up` mit neuen Flags absetzen, ohne alle bisherigen Flags (insbesondere `--accept-routes`) erneut mitzugeben — `tailscale up` ersetzt die komplette Flag-Menge, nicht nur die neu angegebenen. `tailscale set` ergänzt dagegen inkrementell, ohne Bestehendes zu verwerfen. Nach der Umstellung mit `tailscale debug prefs` prüfen, dass `AdvertiseRoutes` jetzt `0.0.0.0/0`/`::/0` enthält UND das Heimnetz weiterhin erreichbar ist (z.B. `ping <LAN_IP_NAS>`).
Danach in der Tailscale-Admin-Konsole (Machines → vps → „Edit route settings") den Exit-Node genehmigen — analog zur Subnet-Route-Genehmigung für den PVE-Host.
**Firewall-Check (Docker + Exit-Node auf derselben Maschine):** `FORWARD`-Chain hat `policy DROP`, `DOCKER-USER`/`DOCKER-FORWARD` laufen vor Tailscales eigener `ts-forward`-Chain. Kein Problem, solange `DOCKER-USER` leer ist und `DOCKER-FORWARD` nur die Docker-Bridge-Interfaces explizit behandelt (alles andere fällt durch zu `ts-forward`, das Tailscale selbst verwaltet) — mit `iptables -L DOCKER-USER -n -v` und `iptables -L DOCKER-FORWARD -n -v` verifizieren, bevor man vorschnell eine eigene FORWARD-Regel ergänzt (siehe auch die „NICHT TUN"-Warnung zu eigenen Firewall-Regeln in Abschnitt 12).
**Verifikation:** `tailscale debug prefs | grep AdvertiseRoutes` zeigt `0.0.0.0/0`/`::/0`. Auf einem Testgerät in der Tailscale-App den Exit-Node auswählen, dann öffentliche IP prüfen (z.B. `curl icanhazip.com`) — muss die `<VPS_PUBLIC_IP>` zeigen. Parallel dazu Heimnetz-Zugriff (NAS/PVE-Web) testen, um zu bestätigen, dass Subnet-Router und Exit-Node gleichzeitig funktionieren.
⚠️ **Geräteseitige Auswahl des Exit-Node ist kein Server-Schritt** — jedes Gerät wählt seinen Exit-Node selbst in der jeweiligen Tailscale-App (Einstellungen → Exit-Node), das lässt sich nicht zentral vom VPS oder PVE-Host aus vorkonfigurieren.
## 14. Projekt-Übersichtsseite
Statische Einzelseite am Apex, kein Backend, kein Build-Schritt — Inhalt liegt versioniert im Doku-Repo unter `website/index.html`.
```bash
mkdir -p /var/www/homepage
scp website/index.html root@<VPS_PUBLIC_IP>:/var/www/homepage/index.html
ssh root@<VPS_PUBLIC_IP> 'chown -R caddy:caddy /var/www/homepage'
```
```caddyfile
<DOMAIN> {
import security_headers
root * /var/www/homepage
encode gzip
file_server
}
www.<DOMAIN> {
redir https://<DOMAIN>{uri} permanent
}
```
DNS: A-Record auf die Apex-Domain → VPS-IP, optional CNAME `www` → Apex-Domain (folgt damit automatisch der Apex-IP, kein zweiter A-Record nötig).
⚠️ **NICHT TUN:** beim Setzen des Apex-A-Records aus Versehen die MX-Records mit anfassen — beide stehen im selben DNS-Panel-Bereich, sind aber komplett unabhängig. Falls ein Mail-Postfach an der Domain hängt, vorher gegenchecken (`dig MX`), dass sich daran nichts ändert.
**Update-Workflow:** Datei im Repo editieren → `scp` aufs VPS → fertig. Kein Caddy-Reload nötig für reine Inhaltsänderungen (nur bei neuen Vhost-Blöcken).
## Zugriffswege (Zusammenfassung) ## Zugriffswege (Zusammenfassung)
| Weg | Erreichbarkeit | Absicherung | | Weg | Erreichbarkeit | Absicherung |