fix(PROJ-68): CapabilityBoundingSet blockiert sudo trotz korrekter sudoers-Regel
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).
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: PROJ-68
|
||||
title: sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett
|
||||
status: In Review
|
||||
status: In Progress
|
||||
created: 2026-07-05
|
||||
---
|
||||
|
||||
@@ -62,9 +62,11 @@ Teil des `firewall-security`-Skills und bewusst außerhalb dieses Tickets
|
||||
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
|
||||
- [ ] 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.
|
||||
@@ -109,7 +111,105 @@ Teil des `firewall-security`-Skills und bewusst außerhalb dieses Tickets
|
||||
_wird ergänzt._
|
||||
|
||||
## QA Test Results
|
||||
_wird ergänzt._
|
||||
|
||||
### 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).
|
||||
|
||||
## Deployment
|
||||
_wird ergänzt._
|
||||
|
||||
Reference in New Issue
Block a user