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>
317 lines
16 KiB
Markdown
317 lines
16 KiB
Markdown
---
|
|
id: PROJ-68
|
|
title: sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett
|
|
status: Deployed (Produktiv 131, 2026-07-05)
|
|
created: 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
|
|
|
|
- [x] `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.
|
|
- [x] Sudoers-Datei wird vor dem Einspielen mit `visudo -c -f` validiert
|
|
(Syntaxfehler dürfen niemals eine funktionierende sudoers-Config
|
|
kaputt machen).
|
|
- [x] `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.
|
|
- [x] 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/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).
|
|
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.
|