QA-Re-Diagnose auf 132 fand einen zweiten, unabhängigen Blocker: die
archivmail.service-Unit setzt CapabilityBoundingSet=CAP_NET_BIND_SERVICE
CAP_NET_ADMIN. Das kappt das Capability-Set des GESAMTEN Prozessbaums
inklusive der von exec.Command("sudo", ...) geforkten Kindprozesse - sudo
braucht aber zusaetzlich CAP_SETUID/CAP_SETGID um auf uid/gid 0 zu
wechseln, sonst bricht es ab ("unable to change to root gid: Operation not
permitted"), selbst wenn sudoers korrekt konfiguriert ist.
Ein erster Verifikationsversuch per `runuser -u archivmail -- sudo ...`
hatte faelschlich Erfolg gemeldet, weil runuser den restriktiven
Bounding-Set der Unit nicht traegt - nur ein Test im echten
systemd-Unit-Kontext (systemd-run oder echter API-Call) ist aussagekraeftig.
Fix: CapabilityBoundingSet in update.sh und install.sh um CAP_SETUID
CAP_SETGID erweitert. AmbientCapabilities bleibt unveraendert (der Go-Daemon
selbst soll diese Rechte nie dauerhaft halten, nur der sudo-Kindprozess
braucht sie im Bounding-Set verfuegbar).
11 KiB
id, title, status, created
| id | title | status | created |
|---|---|---|---|
| PROJ-68 | sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett | In Progress | 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.shinstalliertsudo(falls nicht vorhanden) und legt/etc/sudoers.d/archivmailmitNOPASSWD-Regeln fürsystemctl {start,stop,restart,enable,disable}auf genau die Service-Whitelist ausinternal/api/admin_services_handlers.go(archivmail,archivmail-web,manticore,postgresql@17-main,postfix,nginx) an — keine allgemeineALL-Regel.- Sudoers-Datei wird vor dem Einspielen mit
visudo -c -fvalidiert (Syntaxfehler dürfen niemals eine funktionierende sudoers-Config kaputt machen). update.shzieht 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. NICHT erfüllt — durch systemd-CapabilityBoundingSet blockiert, sudo fehlen CAP_SETUID/CAP_SETGID. Siehe QA Test Results (BLOCKER). 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 -cVOR 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 desfirewall-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
allowedServicesininternal/api/admin_services_handlers.gosynchron gehalten werden (bei künftigen neuen Diensten beide Stellen anfassen — Risiko, dass das wieder auseinanderläuft, wie es beimmanticore-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:344undinstall.sh:758:CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGIDAmbientCapabilitiesbleibt 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 archivmailnö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).
Deployment
wird ergänzt.