Files
archivmail/features/PROJ-68-sudo-provisionierung-dienststeuerung.md
sysopsandClaude Sonnet 5 8bd02facf5 docs(PROJ-67,PROJ-68): Deployment-Status auf Deployed setzen
Manticore-Upgrade (PROJ-67) und sudo-Provisionierung (PROJ-68) auf 131
und 132 verifiziert abgeschlossen; QA/Deployment-Notizen ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 11:47:33 +02:00

16 KiB

id, title, status, created
id title status created
PROJ-68 sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett Deployed (Produktiv 131, 2026-07-05) 2026-07-05

Kontext

Beim QA-Test der neuen Versions-Spalte im Dienste-Tab (siehe Commit 8564d7c) wurde ein Neustart-Versuch für manticore über POST /api/admin/services/manticore getestet — Ergebnis: HTTP 500, Dienst nicht neu gestartet.

Root Cause (verifiziert auf 192.168.1.132): sudo ist auf 132 gar nicht installiert (kein /usr/bin/sudo, kein /etc/sudoers//etc/sudoers.d/). internal/api/admin_services_handlers.go ruft aber für JEDE Dienst-Aktion exec.Command("sudo", "/usr/bin/systemctl", action, name+".service") auf — das schlägt fehl mit „executable file not found“, CombinedOutput() ist leer, daher writeError(500, "") (leerer Body, schwer zu diagnostizieren).

Betrifft nicht nur Manticore — jede Dienst-Aktion (Start/Stop/Restart/ Enable/Disable) für ALLE Dienste in der Whitelist war auf 132 schon vorher kaputt, das PROJ-67/Versions-Feature hat es nur erstmals sichtbar gemacht, weil vorher niemand manticore (das jetzt neu in der Liste ist) über die UI neugestartet hat — wahrscheinlich wurde die Dienststeuerung insgesamt nie über die UI getestet, seit sie gebaut wurde.

install.sh legt den archivmail-Systembenutzer an (useradd --system --shell /bin/false ...), installiert aber nie sudo und legt nie eine /etc/sudoers.d/archivmail-Regel an. Der Go-Code setzt eine Server-Provisionierung voraus, die im Repo nirgends existiert — das war vermutlich ein manueller, nie dokumentierter Schritt bei der Ersteinrichtung von 131, der bei 132 (und jeder künftigen Neuinstallation) fehlt.

Separater, verwandter Fund: /usr/local/sbin/archivmail-nft (für die "Extern sperren/freigeben"-Buttons beim archivmail-Dienst) fehlt auf 132 ebenfalls und wird von install.sh auch nicht deployt — das ist vermutlich Teil des firewall-security-Skills und bewusst außerhalb dieses Tickets (siehe Non-Goals).

User Stories

  • Als Superadmin möchte ich Dienste (inkl. Manticore) über die Web-UI starten/stoppen/neustarten können, ohne mich per SSH einzuloggen.
  • Als Betreiber möchte ich, dass eine frische Installation (install.sh) die Dienststeuerung sofort funktionsfähig macht, ohne manuelle Nacharbeit auf dem Server.

Acceptance Criteria

  • install.sh installiert sudo (falls nicht vorhanden) und legt /etc/sudoers.d/archivmail mit NOPASSWD-Regeln für systemctl {start,stop,restart,enable,disable} auf genau die Service-Whitelist aus internal/api/admin_services_handlers.go (archivmail, archivmail-web, manticore, postgresql@17-main, postfix, nginx) an — keine allgemeine ALL-Regel.
  • Sudoers-Datei wird vor dem Einspielen mit visudo -c -f validiert (Syntaxfehler dürfen niemals eine funktionierende sudoers-Config kaputt machen).
  • update.sh zieht dieselbe Provisionierung nach (idempotent, prüft ob die Datei schon existiert/aktuell ist), damit auch 132 (und jede andere Bestandsinstallation) beim nächsten Deploy automatisch nachgerüstet wird — analog zum PROJ-67-Upgrade-Pfad-Muster.
  • Nach der Provisionierung: Start/Stop/Restart über POST /api/admin/services/{name} funktioniert für alle Dienste in der Whitelist, verifiziert auf 132. ERFÜLLT (Re-QA 2026-07-05) — Bounding-Set um CAP_SETUID/CAP_SETGID erweitert; sudo-Restart im Unit-Kontext erfolgreich (manticore + archivmail-web, PID-Wechsel verifiziert). Siehe Re-QA Test Results.
  • archivmail-nft-Fehlen ist dokumentiert (nicht Teil dieses Tickets, siehe Non-Goals), damit es nicht als überraschender Folgefehler auftaucht.

Edge Cases

  • Sudoers-Syntaxfehler durch einen Bug im Provisionierungs-Code → darf niemals live installiert werden (visudo -c VOR dem Kopieren nach /etc/sudoers.d/, bei Fehler abbrechen statt eine korrupte Datei zu hinterlassen, die den gesamten sudo-Mechanismus auf dem Server lahmlegen könnte).
  • Datei existiert schon (z.B. von einem früheren manuellen Setup mit abweichendem Inhalt) → nicht blind überschreiben, sondern nur ergänzen falls Einträge fehlen, oder zumindest den bestehenden Inhalt vor dem Überschreiben sichern (.bak).
  • 131 (Produktiv) hat vermutlich schon eine funktionierende, manuell angelegte sudoers-Config (sonst wären Service-Aktionen dort auch nie gegangen) — Provisionierung muss idempotent/additiv sein, darf eine funktionierende Config nicht durch einen abweichenden Regelsatz ersetzen und brechen.

Non-Goals

  • archivmail-nft-Helper-Script (externe Zugriffskontrolle für Port 8080) wird NICHT in diesem Ticket gebaut — vermutlich Verantwortungsbereich des firewall-security-Skills, separates Ticket falls gewünscht.
  • Kein Umbau auf D-Bus/PolicyKit als Alternative zu sudo (größerer Architektur-Schnitt, nicht durch den akuten Bug gerechtfertigt).

Technical Requirements

  • Betroffene Dateien: install.sh, update.sh.
  • Whitelist im Sudoers-Generator muss mit allowedServices in internal/api/admin_services_handlers.go synchron gehalten werden (bei künftigen neuen Diensten beide Stellen anfassen — Risiko, dass das wieder auseinanderläuft, wie es beim manticore-Eintrag ja gerade passiert ist).

Implementation Notes

wird ergänzt.

QA Test Results

BLOCKER (offen) — systemd-Hardening blockiert sudo trotz korrekter sudoers-Regel

Status: NICHT gelöst. Die sudoers-Provisionierung (AC 1-3, 5) ist zwar auf 132 korrekt eingespielt (/etc/sudoers.d/archivmail, 2544 Bytes, visudo-valide, NOPASSWD für start/stop/restart/enable/disable auf die Whitelist), ABER die Dienststeuerung über die UI funktioniert trotzdem NICHT, weil ein zweites, unabhängiges Problem greift: das systemd-Capability-Hardening der archivmail.service-Unit.

Root Cause (verifiziert auf 192.168.1.132, 2026-07-05): Die Unit setzt CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN (update.sh:344, install.sh:758). Der Bounding-Set kappt das maximale Capability-Set des GESAMTEN Prozessbaums des Daemons — inklusive der von ihm geforkten exec.Command("sudo", ...)-Kindprozesse. sudo ist zwar setuid-root, benötigt aber zusätzlich CAP_SETUID und CAP_SETGID, um real/saved uid+gid auf 0 zu setzen. Da diese beiden Capabilities NICHT im Bounding-Set stehen, sind sie auch für den setuid-root-sudo-Prozess nicht im Permitted-Set → sudo bricht ab.

Reproduktion (systemd-run repliziert exakt den Daemon-Capability-Kontext):

# FEHLSCHLAG mit dem aktuell deployten Bounding-Set:
systemd-run --uid=archivmail --gid=archivmail \
  -p CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_NET_ADMIN" \
  -p AmbientCapabilities="CAP_NET_BIND_SERVICE CAP_NET_ADMIN" \
  -p NoNewPrivileges=no --wait --pipe --collect \
  /usr/bin/sudo -n /usr/bin/systemctl restart manticore.service
# → sudo: unable to change to root gid: Operation not permitted
# → sudo: error initializing audit plugin sudoers_audit  (exit 1)

# ERFOLG mit erweitertem Bounding-Set:
systemd-run ... -p CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID" ...
  /usr/bin/sudo -n /usr/bin/systemctl restart manticore.service
# → exit 0, manticore restartet, alle Dienste active

Hinweis: Ein naiver runuser -u archivmail -- sudo ...-Test aus einer root-Shell schlägt NICHT fehl (exit 0), weil runuser den restriktiven Bounding-Set der .service-Unit NICHT trägt — dieser Test ist irreführend und darf nicht als Nachweis der Funktionsfähigkeit verwendet werden. Nur der Test im echten Unit-/Capability-Kontext (systemd-run oder echter API-Call durch den Daemon) ist aussagekräftig.

Auswirkung auf AC 4: POST /api/admin/services/{name} bleibt für ALLE Dienste der Whitelist funktionslos, solange der Daemon mit dem aktuellen Bounding-Set läuft. AC 4 ist damit NICHT erfüllt — die sudoers-Regel allein reicht nicht.

Minimale nötige Änderung (NICHT von QA umgesetzt — Backend/DevOps): In beiden Unit-Templates den Bounding-Set erweitern:

  • update.sh:344 und install.sh:758: CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID
  • AmbientCapabilities bleibt unverändert (CAP_NET_BIND_SERVICE CAP_NET_ADMIN) — SETUID/SETGID dürfen NICHT ambient gesetzt werden, sonst hätte der Go-Daemon selbst dauerhaft diese Rechte; im Bounding-Set genügt die Erlaubnis, damit der setuid-root-sudo-Kindprozess sie nutzen darf.
  • Danach systemctl daemon-reload + systemctl restart archivmail nötig (Capability-Änderungen greifen erst nach Neustart des Dienstes).

Alternative Lösungswege (Design-Entscheidung Backend):

  • Bounding-Set erweitern (oben) — kleinster Eingriff, aber weicht das Hardening leicht auf (der Daemon-Prozessbaum darf uid/gid wechseln — praktisch nur relevant über den ohnehin durch sudoers eng begrenzten sudo-Pfad).
  • Alternativ: sudo ganz vermeiden und Dienststeuerung über systemd D-Bus/PolicyKit oder einen dedizierten, minimal privilegierten Helper realisieren (in PROJ-68 ausdrücklich als Non-Goal markiert — größerer Architektur-Schnitt).

Regression/Nebenwirkungen: Keine. Alle Dienste auf 132 nach den Tests stabil active (archivmail, archivmail-web, manticore, postgresql@17-main, nginx). Die mehrfachen Test-Restarts von manticore haben den Index-Dienst nicht beschädigt; manticore kam jedes Mal sauber wieder hoch. Es wurden keine Konfig- oder Passwort-Änderungen vorgenommen; die systemd-run-Testunits liefen mit --collect und wurden automatisch aufgeräumt.

Fazit AC-Status:

  • AC 1 (sudoers-Datei) — erfüllt/eingespielt
  • AC 2 (visudo-Validierung) — erfüllt
  • AC 3 (update.sh idempotent) — erfüllt
  • AC 4 (Service-Aktionen funktionieren über API) — NICHT erfüllt, durch Capability-Bounding-Set blockiert (dieser Zusatzbefund)
  • AC 5 (nft-Doku) — offen (unverändert)

QA-Urteil: NICHT bestanden — Ticket darf nicht auf „gelöst" gesetzt werden, solange das Capability-Bounding-Set nicht in beiden Unit-Templates erweitert (oder eine sudo-freie Alternative gebaut) und auf 132 verifiziert ist.

Fix (2026-07-05)

CapabilityBoundingSet in update.sh und install.sh (jeweils im archivmail.service-Unit-Template) um CAP_SETUID CAP_SETGID erweitert — genau der von der QA-Diagnose empfohlene minimale Eingriff. AmbientCapabilities bewusst unverändert gelassen (der Go-Daemon selbst soll diese Rechte nie dauerhaft halten, nur der kurzlebige sudo-Kindprozess braucht sie im Bounding-Set verfügbar). Beide Skripte schreiben die Unit-Datei bei jedem Deploy neu und rufen danach systemctl daemon-reload — kein separater Migrationsschritt nötig, der Fix greift beim nächsten regulären update.sh-Lauf (unter Berücksichtigung des bekannten Self-Update-Timing-Effekts, siehe PROJ-67).

Re-QA auf 132 steht noch aus (echter API-Call nach Deploy, nicht der irreführende runuser-Test).

Re-QA Test Results (2026-07-05) — PASS

Nach git-Fix (Commit af89794, origin/main) auf 192.168.1.132 verifiziert.

Deploy: bash /opt/archivmail/update.sh — wie erwartet zwei Läufe nötig (bekannter PROJ-67 Self-Update-Timing-Effekt): nach dem 1. Lauf zeigte systemctl show archivmail -p CapabilityBoundingSet noch den alten Set (cap_net_bind_service cap_net_admin); nach dem 2. Lauf korrekt erweitert.

AC 4 — Verifikation (nachgewiesen erfüllt):

  1. Bounding-Set nach Deploy: CapabilityBoundingSet=cap_setgid cap_setuid cap_net_bind_service cap_net_admin — CAP_SETUID/CAP_SETGID jetzt drin.

  2. Echter Test im Unit-/Capability-Kontext (NICHT runuser), repliziert exakt den Daemon-Prozesskontext via systemd-run mit dem jetzt deployten Set:

    systemd-run --uid=archivmail --gid=archivmail \
      -p CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID" \
      -p AmbientCapabilities="CAP_NET_BIND_SERVICE CAP_NET_ADMIN" \
      -p NoNewPrivileges=no --wait --pipe --collect \
      /usr/bin/sudo -n /usr/bin/systemctl restart manticore.service
    

    Ergebnis: result: success, sudo exit=0. Manticore ist TATSÄCHLICH neu gestartet (nicht nur exit-Status): MainPID 47613 → 48768, ActiveEnterTimestamp aktualisiert (18:00:29 → 18:13:02), danach active. Der frühere Fehler sudo: unable to change to root gid tritt nicht mehr auf.

  3. Zweiter Dienst aus der Whitelist zur Bestätigung (kein Einzelfall): gleicher systemd-run+sudo-Test für archivmail-web restart → exit=0, MainPID 48695 → 48834, active.

Zur echten API-Route (POST /api/admin/services/manticore): bewusst NICHT über eine gekaperte Superadmin-Session getestet — ein qa-superadmin-Account existiert zwar, dessen Passwort wurde aus Test-Hygiene-Gründen nicht zurückgesetzt/verändert. Der systemd-run-Test repliziert den Daemon-Capability-Kontext exakt (identischer uid/gid + Bounding-/Ambient-Set, identischer sudo -n systemctl-Aufruf wie in admin_services_handlers.go) und ist damit aussagekräftig für AC 4 — genau die Konstellation, die zuvor als BLOCKER fehlschlug, ist jetzt grün.

Nachwirkungen/Hygiene: Alle Dienste nach Test stabil active (archivmail, archivmail-web, manticore, postgresql@17-main, nginx). Keine Konfig-/Passwort-Änderungen. --collect-Testunits räumten sich selbst auf; zusätzlich 3 verwaiste, fehlgeschlagene run-*-Units aus einer VORIGEN QA-Runde (nginx is-active-Tests, PIDs 47429/47446/47447) via systemctl reset-failed entfernt — 0 verbleibende run-Units.

AC-Status final:

  • AC 1 (sudoers-Datei) — erfüllt
  • AC 2 (visudo-Validierung) — erfüllt
  • AC 3 (update.sh idempotent) — erfüllt
  • AC 4 (Service-Aktionen funktionieren im Unit-Kontext) — jetzt ERFÜLLT
  • AC 5 (nft-Doku) — dokumentiert (Kontext + Non-Goals)

QA-Urteil: BESTANDEN (PASS). Der Capability-Bounding-Set-Blocker ist behoben. Empfehlung: Deploy-Abschluss durch devops-deploy (Status → Deployed), inkl. Deploy auf Produktiv 131 (dort war sudo vermutlich schon manuell provisioniert; der erweiterte Bounding-Set sollte trotzdem regulär via update.sh nachgezogen werden, damit 131 und 132 identisch sind).

Deployment

Datum: 2026-07-05, Produktivserver 192.168.1.131.

Ablauf: bash /opt/archivmail/update.sh zweimal ausgeführt (bekannter PROJ-67 Self-Update-Timing-Effekt) — beide Läufe erfolgreich, Backend ✓ und Frontend ✓ liefen nach jedem Lauf.

Write-Then-Verify:

  1. systemctl show archivmail -p CapabilityBoundingSet nach dem 2. Lauf: cap_setgid cap_setuid cap_net_bind_service cap_net_admin — CAP_SETUID/CAP_SETGID zusätzlich zu CAP_NET_BIND_SERVICE/CAP_NET_ADMIN bestätigt vorhanden.
  2. /etc/sudoers.d/archivmail vorhanden (2544 Bytes, Modus r--r-----, root:root), visudo -c -f /etc/sudoers.d/archivmailparsed OK. Inhalt enthält NOPASSWD-Regeln für start/stop/restart/enable/disable auf genau die Whitelist (archivmail, archivmail-web, manticore, postgresql@17-main, postfix, nginx).
  3. Echter Funktionstest (nicht runuser — siehe QA-Doku oben) via:
    systemd-run --uid=archivmail --gid=archivmail \
      -p CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID" \
      -p AmbientCapabilities="CAP_NET_BIND_SERVICE CAP_NET_ADMIN" \
      -p NoNewPrivileges=no --wait --pipe --collect \
      /usr/bin/sudo -n /usr/bin/systemctl restart archivmail-web.service
    
    Ergebnis: Finished with result: success, exit=0. MainPID von archivmail-web wechselte 33531 → 33600, ActiveEnterTimestamp aktualisiert (18:17:02 → 18:17:14), Dienst danach active — PID-Wechsel als Beweis für einen echten Neustart (kein Fake-Erfolg). Manticore/PostgreSQL auf Produktiv bewusst NICHT für den Test angefasst (Live-Daten-Risiko), archivmail-web genügt als Nachweis im identischen Capability-Kontext.
  4. Alle Dienste nach dem Test active: archivmail, archivmail-web, manticore, postgresql@17-main, nginx. Health-Check: Backend HTTP 200 (/api/health), Frontend HTTP 200. Keine verwaisten run-*-Testunits zurückgeblieben (--collect hat automatisch aufgeräumt).

Ergebnis: AC 4 auf Produktiv verifiziert erfüllt, identisch zum Re-QA-PASS auf 132. Ticket wird auf Deployed gesetzt.