SEC-1c-1 Plan-Review — HTTPS + Let's Encrypt + Domain-Split

Projekt: Steve-TradingBot · Phase: SEC-1c-1 · Author: claude-opus-4-7[1m]
Generated: 2026-05-13 19:29 UTC · master HEAD: 2fbc7b5 (SEC-1b-6 closed, SEC-1c-0 LIVE)
Status: NO CODE / NO RELOAD Pure Plan-Review — Operator-GO erforderlich vor jeder Implementation.
Empfehlung: 3-vhost-Split mit single multi-SAN certbot-Cert + minimal-Pflicht-Companions (TrustedProxies + SECURE_COOKIE)

0 — Executive Summary

DNS ist vollständig propagiert: alle drei Subdomains (steve. / gui. / files.gewerbespeicher-rechner.de) resolven via 1.1.1.1 und 8.8.8.8 auf 81.169.213.37. TTL ~3600s (1h Rollback). Registrar: webgo.de (NS ns1-4), separates Hosting bei Strato — Standard-Setup.

certbot ist NICHT installiert — Pflicht-Install in Sub-Step 1.0 (apt oder snap).

Bestehende nginx-Topologie: drei aktive vhosts (dashboard-ssl Port 443+80, shares-8088 Port 8088, wp-test-ssl Port 8443 orphan). Alle binden an server_name 81.169.213.37 (IP-only). dashboard-ssl proxiet `/` zu Bot-Dashboard 127.0.0.1:8050 mit SEC-1c-0-Basic-Auth; `/shares/` zu /srv/shares/. Filament-GUI auf 127.0.0.1:8090 ist aktuell nicht via nginx erreichbar.

Empfohlene Aufteilung:

Kritischer Pflicht-Companion: bei APP_URL=https://gui.…-Wechsel müssen gleichzeitig TrustedProxies-Middleware und SESSION_SECURE_COOKIE=true aktiviert werden — sonst verliert Laravel das Cookie-Secure-Flag bei nginx-proxy (HTTPS-Erkennung schlägt fehl ohne X-Forwarded-Proto-Trust). Operator wird sonst nach erstem Login ausgesperrt.


1 — Live-State (Recon-Ergebnisse)

ItemWertNote
DNS A steve.…81.169.213.37via 1.1.1.1 + 8.8.8.8 ✅
DNS A gui.…81.169.213.37via 1.1.1.1 + 8.8.8.8 ✅
DNS A files.…81.169.213.37via 1.1.1.1 + 8.8.8.8 ✅
AAAAleerkein IPv6 — OK für SEC-1c-1, IPv6-AAAA als BACKLOG-Item
NSns1-4.webgo.deRegistrar webgo (Strato hostet Server)
TTL~3600s (1h)moderat — bei DNS-Änderung 1h Rollback-Latenz
certbotNICHT INSTALLIERTapt install certbot python3-certbot-nginx in Sub-Step 1.0
Let's Encrypt-Zertifikatekeine/etc/letsencrypt/ existiert nicht
nginx Version1.18.0 (Ubuntu)modern; supports TLSv1.3, HTTP/2 (nicht aktiv)
nginx sites-enabled3: dashboard-ssl, shares-8088, wp-test-sslwp-test-ssl ist orphan (502); soll weg in SEC-1c-0.5
GUI APP_URLhttp://127.0.0.1:8090muss zu https://gui.…
GUI APP_ENVproductionOK
GUI APP_DEBUGfalseOK (SEC-1b-0 closure)
GUI SESSION_DRIVERdatabaseOK
GUI SESSION_LIFETIME120 min30 min Empfehlung erst in SEC-1c-2 Block A
GUI SESSION_ENCRYPTfalsewird zu true in SEC-1c-2
GUI SESSION_DOMAINnullmuss zu gui.… in SEC-1c-1
GUI SESSION_SECURE_COOKIEnicht gesetzt (default false)Pflicht-Companion: true setzen GLEICHZEITIG mit APP_URL-Wechsel
GUI TRUSTED_PROXIESnicht konfiguriertPflicht-Companion: trustProxies-Middleware in bootstrap/app.php
Filament-Login + MFAaktiv (SEC-1b-6)App-Authentication TOTP + Recovery-Codes; Admin-User admin@example.local noch ohne Setup (forced enrollment on next login)
Bot in-container PID363python3 main.py --paper unverändert
Worker in-container PID1command_worker unverändert
BINANCE_TESTNETtruevia whitelist-grep
UFWaktiv: 22/80/443/8088 allow, default denySEC-1c-0 LIVE
fail2ban3 jails: sshd + nginx-http-auth + nginx-limit-reqSEC-1c-0 LIVE

2 — Domain-Aufteilung (Soll)

SubdomainFunktionInternal BackendAuth-LayerNotes
steve.gewerbespeicher-rechner.de Bot-Dashboard (Trading-State, Positions, Logs) 127.0.0.1:8050 nginx Basic-Auth (User bot-admin, SEC-1c-0 Step 3) /shares/ entfällt hier (wandert nach files.)
gui.gewerbespeicher-rechner.de Filament Admin (Trade-Logs, Config, Managed-Proposals) 127.0.0.1:8090 Filament-Login + SEC-1b-6 TOTP-MFA (NICHT zusätzlich Basic-Auth) einziger Pfad für Admin-Aktionen; MFA als sole-2nd-factor
files.gewerbespeicher-rechner.de PDF-Reports / Shares /srv/shares/ (alias) kein Auth (autoindex) Operator-Self-Service; backups/ bleibt durch dir-mode 0700 verborgen

Server-Block-Layout

# /etc/nginx/sites-available/steve.gewerbespeicher-rechner.de
server {
  listen 80;
  server_name steve.gewerbespeicher-rechner.de;
  location /.well-known/acme-challenge/ { root /var/www/letsencrypt; }
  location / { return 301 https://$host$request_uri; }
}
server {
  listen 443 ssl http2;
  server_name steve.gewerbespeicher-rechner.de;
  ssl_certificate     /etc/letsencrypt/live/<cert-name>/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/<cert-name>/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers on;
  # SEC-1c-1: HSTS staged-start 1 day (Phase 2 erhoeht auf 1 week / 1 month / 1 year)
  add_header Strict-Transport-Security "max-age=86400" always;

  location / {
    auth_basic           "Steve Bot Dashboard";
    auth_basic_user_file /etc/nginx/.htpasswd-bot-dashboard;
    proxy_pass http://127.0.0.1:8050;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300s;
  }
}

# /etc/nginx/sites-available/gui.gewerbespeicher-rechner.de
server {
  listen 80;
  server_name gui.gewerbespeicher-rechner.de;
  location /.well-known/acme-challenge/ { root /var/www/letsencrypt; }
  location / { return 301 https://$host$request_uri; }
}
server {
  listen 443 ssl http2;
  server_name gui.gewerbespeicher-rechner.de;
  ssl_certificate     /etc/letsencrypt/live/<cert-name>/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/<cert-name>/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  add_header Strict-Transport-Security "max-age=86400" always;

  client_max_body_size 5m;

  location / {
    # KEIN Basic-Auth — Filament hat eigene MFA (SEC-1b-6).
    proxy_pass http://127.0.0.1:8090;
    proxy_http_version 1.1;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300s;
  }
}

# /etc/nginx/sites-available/files.gewerbespeicher-rechner.de
server {
  listen 80;
  server_name files.gewerbespeicher-rechner.de;
  location /.well-known/acme-challenge/ { root /var/www/letsencrypt; }
  location / { return 301 https://$host$request_uri; }
}
server {
  listen 443 ssl http2;
  server_name files.gewerbespeicher-rechner.de;
  ssl_certificate     /etc/letsencrypt/live/<cert-name>/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/<cert-name>/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  add_header Strict-Transport-Security "max-age=86400" always;

  location / {
    alias /srv/shares/;
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;
    types {
      application/pdf  pdf;
      text/plain       txt log md;
      text/html        html htm;
      application/json json;
    }
    default_type application/octet-stream;
    add_header X-Content-Type-Options nosniff always;
  }
}

3 — Cert-Strategie

OptionVorNachEmpfehlung
(a) Single multi-SAN-Cert
certbot certonly --webroot -w /var/www/letsencrypt -d steve.… -d gui.… -d files.… --cert-name gewerbespeicher
1 Renewal-Job; alle Domains atomar; weniger Files in /etc/letsencrypt/live/ Bei einer Domain-Renewal-Failure wird alle 3 Domains betroffen EMPFOHLEN
(b) 3 separate Certs (operator-pin in GO: "Let's Encrypt Zertifikate pro Domain") Failure-Isolation pro Domain; cleaner conceptual separation 3 Renewal-Jobs; 3 Cert-Dirs; mehr Wartung Falls operator-pin strikt
(c) Wildcard *.gewerbespeicher-rechner.de 1 Cert für alles, auch für zukünftige Subdomains Braucht DNS-01 Challenge (kein HTTP-01); braucht DNS-API-Zugang bei webgo OVERKILL für 3 Domains

Operator-Decision

Operator-GO sagte „Let's Encrypt Zertifikate pro Domain“ (Plural). Lesart ambig:

Meine Empfehlung: (a) — weniger Operational-Overhead, atomic renewal, Standardpattern. Operator-Anpassung auf (b) jederzeit moeglich.

certbot-Plugin


4 — Konflikte mit aktuellem dashboard-ssl

4.1 Port-Clash

Aktueller dashboard-ssl bindet listen 443 ohne server_name-Filter (matched alles, weil server_name 81.169.213.37 nicht Host-Header-basiert greift; nginx liefert das als default_server bei IP-Zugriff). Die drei neuen vhosts mit server_name <subdomain> binden ebenfalls 443.

Lösungs-Optionen:

Empfehlung: (A) zusätzlich; dashboard-ssl bleibt vorerst aktiv als Sicherheitsnetz (IP-only Zugriff funktioniert weiter). Nach 1–7 Tage stabiler 3-vhost-Operation: Cleanup-Cut macht dashboard-ssl obsolet (steht in BACKLOG SEC-1c-1-FU-1).

4.2 default_server-Frage

Mit Subdomain-vhosts: was passiert bei IP-only-Zugriff (https://81.169.213.37/)? Drei Optionen:

  1. (i) dashboard-ssl bleibt default — IP-Zugriff geht zu Bot-Dashboard (Basic-Auth). Empfohlen für Übergangsphase.
  2. (ii) Neuer default_server-vhost gibt 444 (Connection-Close) zurück — signalisiert „IP-only nicht erlaubt“.
  3. (iii) Redirect IP → steve.…

Empfehlung: (i) für SEC-1c-1; (ii) später im Cleanup-Cut.


5 — APP_URL / SESSION_DOMAIN Auswirkungen + Pflicht-Companions

Sollwerte (SEC-1c-1)

APP_URL=https://gui.gewerbespeicher-rechner.de
SESSION_DOMAIN=gui.gewerbespeicher-rechner.de
SESSION_SECURE_COOKIE=true         # PFLICHT-COMPANION zu APP_URL=https
TRUSTED_PROXIES=*                  # in unserem Setup (localhost-nginx) sicher

Pflicht-Companion 1: TrustedProxies-Middleware

In gui/bootstrap/app.php im ->withMiddleware()-Block HINZUFÜGEN:

$middleware->trustProxies(
    at: '*',
    headers: \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR
           | \Illuminate\Http\Request::HEADER_X_FORWARDED_HOST
           | \Illuminate\Http\Request::HEADER_X_FORWARDED_PORT
           | \Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO,
);

Begründung: ohne diesen Block erkennt Laravel den Request als HTTP (nicht HTTPS), weil das eingehende Paket auf http://127.0.0.1:8090 ankommt. Folge: SESSION_SECURE_COOKIE=true wird ignoriert (Cookie wird nicht mit Secure-Flag gesetzt). Filament-Redirects gehen auf http://… statt https://…. Operator wird sonst aus dem Admin ausgesperrt nach erstem Redirect.

Pflicht-Companion 2: SESSION_SECURE_COOKIE=true

Sobald HTTPS aktiv ist, MUSS dieser Wert auf true, sonst:

Konsequenz: alle bestehenden Sessions invalidiert

SESSION_DOMAIN-Wechsel nullgui.… macht alle bestehenden Cookies ungültig. Operator (und alle Filament-User) müssen neu einloggen. Akzeptabel im 1-Admin-Setup.

Filament asset_url

Aktuell asset_url = NULL. Filament generiert Asset-Pfade aus APP_URL. Da APP_URL nach SEC-1c-1 HTTPS-canonical wird, sollten Assets dann mit HTTPS ausgeliefert werden. Mixed-Content-Pruefung im Browser nach Cutover.


6 — MFA / Passkey-Kompatibilität

SEC-1b-6 TOTP-MFAkompatibel: Authenticator-Code ist Domain-unabhängig; nach SEC-1c-1 funktioniert weiterhin. Operator-Hinweis: beim ersten Login auf https://gui.… wird das forced-enrollment-Setup-Page (SEC-1b-6) gestartet, da Admin admin@example.local noch app_authentication_secret IS NULL.
SEC-1d Passkey/WebAuthnBENOEHTIGT diese Phase — WebAuthn benötigt secure-context (HTTPS mit gültigem TLS auf einer stabilen Domain). SEC-1c-1 ist die Vorbedingung. Nach SEC-1c-1 ist SEC-1d entblockt.
SESSION_DOMAIN-KonfliktPasskey-RP-ID muss exakt die Domain matchen. SESSION_DOMAIN=gui.gewerbespeicher-rechner.de ist konsistent mit RP-ID=gui.gewerbespeicher-rechner.de.
Cross-subdomain-MFAnicht relevant — MFA gilt nur für Filament-Admin (gui.); Bot-Dashboard (steve.) hat eigenen Basic-Auth.

7 — CSP / HSTS-Vorbereitung (was schon jetzt, was in SEC-1c-2)

SEC-1c-1 MIN (Pflicht zusammen mit HTTPS)

SEC-1c-2 FOLLOW-UP (Phase 2, nicht jetzt)


8 — Operator-Decision-Points (Pflicht vor Code-Start)

  1. Cert-Strategie: (a) single multi-SAN-Cert (Empfehlung) ODER (b) 3 separate Certs?
  2. certbot-Plugin: (i) --webroot mit /var/www/letsencrypt (Empfehlung) ODER (ii) --nginx?
  3. Cleanup-Cut von dashboard-ssl: Beibehalten (Empfehlung — alte IP-URLs funktionieren weiter während Test-Phase) ODER sofort entfernen nach Cert-Setup?
  4. HTTP/2: aktivieren in den neuen vhosts (Empfehlung — Performance) ODER nur HTTP/1.1?
  5. HTTP/1.1 80-Port-Fallback: nur ACME-Challenge + Redirect (Empfehlung) ODER zusätzlich /shares/ über HTTP wie heute?
  6. files.… backup-shielding: aktuelle dir-mode 0700 reicht, oder zusätzlich nginx location ~ /backups mit internal; oder deny all;?
  7. SSH-IP-Whitelist gleichzeitig: hast du deine feste IP, sodass wir ufw allow from <IP> to any port 22 + delete allow 22/tcp beim Cutover machen?
  8. E-Mail für Let's Encrypt: certbot speichert eine Admin-E-Mail-Adresse für Renewal-Failure-Notifications. Welche E-Mail-Adresse verwenden? (Operator-Default: talk@kw-baustoffe.de?)

9 — Atomicity & Commit-Block-Plan

#StepAktionRisikoZeit
1.0Pre-flightLive-State-Snapshot (git, Bot/Worker PIDs, BINANCE_TESTNET, managed_proposals, ss -tlnp, ufw status, nginx -t)05 min
1.1Backupnginx-Config gesamt-Tarball + GUI .env-Copy + pg_dump GUI-DB (Pflicht, durable-rule)010 min
1.2certbot Installapt install -y certbot (kein nginx-plugin wenn webroot-Variante)LOW (Paket-Install, kein Service-Restart)5 min
1.3ACME-webrootmkdir -p /var/www/letsencrypt/.well-known/acme-challenge && chown -R www-data:www-dataLOW2 min
1.4nginx-Skeleton3 neue vhost-Configs als Skelett anlegen (NUR Port 80 + ACME-Challenge-Location + 301-Redirect; KEIN HTTPS-Block bis Cert da ist)LOW (validate via nginx -t)15 min
1.5nginx reloadnginx -t && systemctl reload nginx — alte vhosts (dashboard-ssl, shares-8088) bleiben aktivLOW2 min
1.6ACME-Testcurl http://steve.…/.well-known/acme-challenge/test → muss 404 (location existiert) statt 403LOW5 min
1.7Cert-Issuecertbot certonly --webroot -w /var/www/letsencrypt -d steve.… -d gui.… -d files.… --email <op> --agree-tos --no-eff-emailMEDIUM (Rate-Limits Let's Encrypt; STAGING-Test zuerst empfohlen)15 min
1.8HTTPS-Block hinzu3 vhost-Configs: HTTPS-Block (443) hinzufügen mit Cert-Pfaden; HSTS max-age=86400, no preloadMEDIUM (nginx-validate, atomic)30 min
1.9nginx reloadnginx -t && systemctl reload nginxLOW2 min
1.10Smoke-Testcurl all 3 https-domains; verify 301 von HTTP, 200/401 auf HTTPSLOW10 min
1.11GUI APP_URL + Session-Companiongui/.env ändern: APP_URL, SESSION_DOMAIN, SESSION_SECURE_COOKIE; gui/bootstrap/app.php mit TrustedProxies erweiternHIGH (Login-Cutover; alte Sessions ungueltig)20 min
1.12GUI-Container restartdocker exec gui php artisan config:clear && restart ODER GUI-Container restart (sauber).HIGH (Service-Downtime ~10s)2 min
1.13VerifikationBrowser-Test https://gui.… → Filament-Login → MFA-Setup-Forced-Page → Setup → LoginHIGH30 min
1.14Optional Cleanuporphan wp-test-ssl entfernen (separate Sub-Step, BACKLOG SEC-1c-0.5)LOW5 min
1.15Closure-PinMemory-Pin + PDF-Report020 min

Σ SEC-1c-1: ~3h über 15 atomic steps.


10 — Rollback-Pfade

Failure-ZeitpunktRollback-Step
nach Step 1.5 (nginx skelett-reload)rm /etc/nginx/sites-enabled/{steve,gui,files}.gewerbespeicher-rechner.de && nginx -s reload — 10 sec, alte IP-only-URLs funktionieren weiter
nach Step 1.7 (Cert-Issue failed)Cert wurde nicht ausgestellt; Skelett-vhosts bleiben (kein Schaden); Re-try mit --staging dann production
nach Step 1.9 (HTTPS-Block-reload bricht nginx)nginx -t verhindert reload bei Syntax-Fehler; bei semantic-Fehler: backup-tarball restore
nach Step 1.12 (Filament-Login broken)gui/.env aus backup; docker restart steve-tradingbot-gui; alte APP_URL=http://127.0.0.1:8090 funktioniert via SSH-tunnel weiter (Operator-Notfall-Access)
komplettes Rollbackbackup-tarball restore + certbot delete --cert-name <name> + GUI-Container restart — < 5 min

11 — Stop-Regeln (Pflicht)


12 — NO-GO-Bedingungen


13 — GO/NO-GO Empfehlung

GO-Empfehlung: SEC-1c-1 ist vertretbar als 1 atomarer Block mit den 15 Sub-Steps, sofern:

  1. Operator-Antworten auf Decision-Points 1, 2, 8 vorliegen (Cert-Strategie / Plugin / E-Mail)
  2. Operator akzeptiert Re-Enrollment-Flow auf erste https://gui.…-Login (Forced MFA-Setup für admin@example.local)
  3. Cert-Staging-Test vor Production-Cert

Alternativ: Split in 2 Bloecke:

Meine Empfehlung: Split (1c-1a + 1c-1b). Weniger HIGH-Risk-Steps in einem Cutover; klarer Rollback-Pfad pro Schritt.


14 — Boundaries (Plan-Review-End)

master HEAD2fbc7b5 unverändert
git statusunverändert
Bot in-container PID363 unverändert
Worker in-container PID1 unverändert
BINANCE_TESTNETtrue (whitelist-grep)
managed_proposals0
UFWactive
nginxactive
fail2ban3 jails active
docker / restart / reload0
certbot install0 (geplant in Step 1.2)
cert-issue0 (geplant in Step 1.7)
nginx config-edit0 (geplant in Step 1.4 + 1.8)
gui/.env edit0 (geplant in Step 1.11)
Mainnet0
Push0
Secret-Werte in Output0 (durable Hygiene-Regel)

Status: PLAN-REVIEW fertig. Warte auf:

Kein Code geschrieben. Kein git/docker/cert-Issue/nginx-Reload durchgeführt — pure analysis only.