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
|
id: PROJ-68
|
||||||
title: sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett
|
title: sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett
|
||||||
status: In Review
|
status: In Progress
|
||||||
created: 2026-07-05
|
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
|
die Datei schon existiert/aktuell ist), damit auch 132 (und jede
|
||||||
andere Bestandsinstallation) beim nächsten Deploy automatisch
|
andere Bestandsinstallation) beim nächsten Deploy automatisch
|
||||||
nachgerüstet wird — analog zum PROJ-67-Upgrade-Pfad-Muster.
|
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
|
`POST /api/admin/services/{name}` funktioniert für alle Dienste in
|
||||||
der Whitelist, verifiziert auf 132.
|
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,
|
- [ ] `archivmail-nft`-Fehlen ist dokumentiert (nicht Teil dieses Tickets,
|
||||||
siehe Non-Goals), damit es nicht als überraschender Folgefehler
|
siehe Non-Goals), damit es nicht als überraschender Folgefehler
|
||||||
auftaucht.
|
auftaucht.
|
||||||
@@ -109,7 +111,105 @@ Teil des `firewall-security`-Skills und bewusst außerhalb dieses Tickets
|
|||||||
_wird ergänzt._
|
_wird ergänzt._
|
||||||
|
|
||||||
## QA Test Results
|
## 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
|
## Deployment
|
||||||
_wird ergänzt._
|
_wird ergänzt._
|
||||||
|
|||||||
+8
-1
@@ -755,7 +755,14 @@ User=${AM_USER}
|
|||||||
Group=${AM_USER}
|
Group=${AM_USER}
|
||||||
# CAP_NET_ADMIN: required for the admin "enable firewall" action (nft -f /etc/nftables.conf)
|
# CAP_NET_ADMIN: required for the admin "enable firewall" action (nft -f /etc/nftables.conf)
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
||||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
# PROJ-68: CAP_SETUID/CAP_SETGID are NOT ambient (the Go daemon itself must
|
||||||
|
# never hold them) but must be in the bounding set so the setuid-root sudo
|
||||||
|
# child process (spawned by the admin "Dienste"-Tab for systemctl actions)
|
||||||
|
# can actually change to uid/gid 0. Without this, sudo aborts with
|
||||||
|
# "unable to change to root gid: Operation not permitted" even though the
|
||||||
|
# sudoers NOPASSWD rule is correct — verified 2026-07-05, see
|
||||||
|
# features/PROJ-68-sudo-provisionierung-dienststeuerung.md.
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID
|
||||||
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
||||||
ExecReload=/bin/kill -HUP \$MAINPID
|
ExecReload=/bin/kill -HUP \$MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@@ -341,7 +341,14 @@ User=${AM_USER}
|
|||||||
Group=${AM_USER}
|
Group=${AM_USER}
|
||||||
# CAP_NET_ADMIN: required for the admin "enable firewall" action (nft -f /etc/nftables.conf)
|
# CAP_NET_ADMIN: required for the admin "enable firewall" action (nft -f /etc/nftables.conf)
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
||||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
# PROJ-68: CAP_SETUID/CAP_SETGID are NOT ambient (the Go daemon itself must
|
||||||
|
# never hold them) but must be in the bounding set so the setuid-root sudo
|
||||||
|
# child process (spawned by the admin "Dienste"-Tab for systemctl actions)
|
||||||
|
# can actually change to uid/gid 0. Without this, sudo aborts with
|
||||||
|
# "unable to change to root gid: Operation not permitted" even though the
|
||||||
|
# sudoers NOPASSWD rule is correct — verified 2026-07-05, see
|
||||||
|
# features/PROJ-68-sudo-provisionierung-dienststeuerung.md.
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID
|
||||||
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
||||||
ExecReload=/bin/kill -HUP \$MAINPID
|
ExecReload=/bin/kill -HUP \$MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
Reference in New Issue
Block a user