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>
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.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. 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 -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).
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):
-
Bounding-Set nach Deploy:
CapabilityBoundingSet=cap_setgid cap_setuid cap_net_bind_service cap_net_admin— CAP_SETUID/CAP_SETGID jetzt drin. -
Echter Test im Unit-/Capability-Kontext (NICHT
runuser), repliziert exakt den Daemon-Prozesskontext viasystemd-runmit 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.serviceErgebnis:
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), danachactive. Der frühere Fehlersudo: unable to change to root gidtritt nicht mehr auf. -
Zweiter Dienst aus der Whitelist zur Bestätigung (kein Einzelfall): gleicher
systemd-run+sudo-Test fürarchivmail-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:
systemctl show archivmail -p CapabilityBoundingSetnach 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./etc/sudoers.d/archivmailvorhanden (2544 Bytes, Modusr--r-----, root:root),visudo -c -f /etc/sudoers.d/archivmail→parsed 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).- Echter Funktionstest (nicht
runuser— siehe QA-Doku oben) via:Ergebnis: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.serviceFinished with result: success, exit=0. MainPID vonarchivmail-webwechselte 33531 → 33600,ActiveEnterTimestampaktualisiert (18:17:02 → 18:17:14), Dienst danachactive— 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. - Alle Dienste nach dem Test
active: archivmail, archivmail-web, manticore, postgresql@17-main, nginx. Health-Check: BackendHTTP 200(/api/health), FrontendHTTP 200. Keine verwaistenrun-*-Testunits zurückgeblieben (--collecthat automatisch aufgeräumt).
Ergebnis: AC 4 auf Produktiv verifiziert erfüllt, identisch zum Re-QA-PASS auf 132. Ticket wird auf Deployed gesetzt.