diff --git a/contrib/testing/agent-privilege-check.sh b/contrib/testing/agent-privilege-check.sh index ae5c676..8821cb9 100755 --- a/contrib/testing/agent-privilege-check.sh +++ b/contrib/testing/agent-privilege-check.sh @@ -12,6 +12,7 @@ fi manager=$(sudo -n /usr/bin/systemctl show "$SERVICE" -p MainPID --value) bongo_uid=$(/usr/bin/id -u bongo) bongo_gid=$(/usr/bin/id -g bongo) +allowed_caps=" cap_chown cap_dac_override cap_fowner cap_setgid cap_setuid cap_net_bind_service " if [ -z "$manager" ] || [ "$manager" -le 1 ] || ! sudo -n /usr/bin/systemctl is-active "$SERVICE" >/dev/null; then @@ -19,6 +20,22 @@ if [ -z "$manager" ] || [ "$manager" -le 1 ] || exit 1 fi +bounding=$(sudo -n /usr/bin/systemctl show "$SERVICE" \ + -p CapabilityBoundingSet --value) +if [ -z "$bounding" ]; then + echo "$SERVICE has an empty or unavailable capability boundary" >&2 + exit 1 +fi +for capability in $bounding; do + case "$allowed_caps" in + *" $capability "*) ;; + *) + echo "$SERVICE permits unexpected capability $capability" >&2 + exit 1 + ;; + esac +done + status_field() { field=$1 @@ -135,3 +152,4 @@ for name in $EXPECTED; do done echo "PASS all configured agents are manager children with bounded privilege" +echo "PASS service capability boundary: $bounding" diff --git a/docs/architecture.md b/docs/architecture.md index a49015f..534c191 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -51,6 +51,17 @@ TLS, protocol I/O, Store access, and Queue access. GNU GSASL is shared by the mail protocols; its direct Bongo verification callback does not create or consult a second user database. +Agents which bind a privileged listener start with the manager's restricted +credentials, bind, and permanently change their real/effective/saved UID and +GID to `bongo` before accepting clients. Non-listener agents do the permanent +drop immediately. The manager itself normally has effective UID `bongo` but +retains a bounded supervisor identity so a crashed listener can be replaced. +`bongoworker` has the same inactive exception solely for the short atomic ACME +certificate deployment step. systemd restricts both to `CHOWN`, +`DAC_OVERRIDE`, `FOWNER`, `SETGID`, `SETUID`, and `NET_BIND_SERVICE`; active +and ambient capabilities are empty while idle. Moving ACME deployment into a +smaller dedicated privileged helper remains a future hardening improvement. + ## Internal interfaces The Store protocol on TCP 689 and Queue protocol on TCP 8670 are private diff --git a/docs/release-testing-0.7.md b/docs/release-testing-0.7.md index d528b55..2bf360b 100644 --- a/docs/release-testing-0.7.md +++ b/docs/release-testing-0.7.md @@ -97,7 +97,7 @@ inputs and outputs is absent from the ZIP. | CFG-10 | Existing certificate/key validation and safe rejection of broken pairs | [PASS](test-evidence/0.7-r1.md#cfg-10) | | | | CFG-11 | DKIM/SPF/DMARC discovery, key import/generation, DNS hints, and task | [PASS](test-evidence/0.7-r1.md#cfg-11) | | | | CFG-12 | Manager start, reload, restart, clean stop, stale PID, and crash recovery | [PASS](test-evidence/0.7-r1.md#cfg-12) | | | -| CFG-13 | Every agent starts as intended and drops privileges | | | | +| CFG-13 | Every agent starts as intended; permanent/effective privilege drops and supervisor exceptions are bounded | [PASS](test-evidence/0.7-r1.md#cfg-13) | | | | CFG-14 | tmpfiles creates runtime paths; package contains no runtime PID directories | | | | | CFG-15 | systemd ordering, hardening, scanner timer, and worker scheduling | | | | | CFG-16 | Timezone cache rebuild uses the installed checked-in snapshot | | | | diff --git a/docs/test-evidence/0.7-r1.md b/docs/test-evidence/0.7-r1.md index 3f4f129..b584ee3 100644 --- a/docs/test-evidence/0.7-r1.md +++ b/docs/test-evidence/0.7-r1.md @@ -1189,3 +1189,45 @@ Runtime configuration: the installed Gentoo live package and existing disposable Bongo configuration were used. The test intentionally interrupted the Queue and manager processes, restored the service automatically on every exit path, and left the service active. + +## CFG-13 + +Result: **PASS** + +The live process tree was audited after installing commit `cdbf9320` and +restarting the systemd service. `contrib/testing/agent-privilege-check.sh` +verified every UID, GID, supplementary-group, and capability tuple directly +from `/proc`, as well as parentage below the manager and systemd's capability +boundary. + +All 13 configured agents were present beneath the manager: Store, Queue, +Antispam, Antivirus, Rules, ManageSieve, Collector, Worker, SMTP receive, SMTP +client, IMAP, POP3, and Web. With the documented Worker exception, every +agent had real, effective, saved, and filesystem UID/GID 404 (`bongo`), no +supplementary groups, and zero inherited, permitted, effective, and ambient +capabilities. Antispam and Antivirus had previously retained real/saved root; +the repair changes them to the same permanent drop used by the other +non-privileged agents. + +The manager and scheduled Worker were separately checked: their effective and +filesystem UID was `bongo`, supplementary groups were empty, and inherited, +effective, and ambient capabilities were zero while idle. They retain only a +bounded permitted set for replacement of privileged listeners and the +Worker's atomic ACME certificate deployment. The installed systemd unit +reduced `CapabilityBoundingSet` to exactly `CHOWN`, `DAC_OVERRIDE`, `FOWNER`, +`SETGID`, `SETUID`, and `NET_BIND_SERVICE`; the earlier full kernel capability +boundary is no longer inherited. + +```sh +BONGO_ALLOW_LIVE_PRIVILEGE_TEST=1 \ + ./contrib/testing/agent-privilege-check.sh +``` + +- Source: commit `cdbf9320`. +- Complete rebuilt CTest result: 82 of 82 passed in 7.18 seconds. +- Live result: 13 of 13 configured agents passed; service remained + active/running with systemd result `success`. + +Runtime configuration: the installed Gentoo live package and existing +disposable Bongo configuration were used. The check was read-only and did not +alter process, configuration, mailbox, Queue, or credential state.