Bound agent service privileges
Debian Trixie package bundle / packages (push) Successful in 22m0s

This commit is contained in:
Mario Fetka
2026-07-22 18:01:25 +02:00
parent d002e6864b
commit cdbf93201d
5 changed files with 143 additions and 2 deletions
+3
View File
@@ -41,6 +41,9 @@ one-off files in `/tmp`:
- `manager-lifecycle-check.sh` exercises systemd reload/restart/stop/start,
one agent crash, and an unclean manager death which leaves a stale PID file.
It requires explicit live-test opt-in and restores an active service on exit.
- `agent-privilege-check.sh` verifies the live manager/agent process tree,
real/effective/saved UID and GID tuples, supplementary groups, and inherited,
permitted, effective, and ambient capabilities.
The complete mapping from the original one-off `/tmp` names to maintained
scripts is recorded in `tmp-script-map.md`.
+137
View File
@@ -0,0 +1,137 @@
#!/bin/sh
set -eu
SERVICE=${BONGO_TEST_SERVICE:-bongo.service}
EXPECTED=${BONGO_TEST_AGENTS:-"bongostore bongoqueue bongoantispam bongoavirus bongorules bongosieve bongocollector bongoworker bongosmtp bongosmtpc bongoimap bongopop3 bongo-web"}
if [ "${BONGO_ALLOW_LIVE_PRIVILEGE_TEST:-}" != 1 ]; then
echo "set BONGO_ALLOW_LIVE_PRIVILEGE_TEST=1 for the disposable host" >&2
exit 2
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)
if [ -z "$manager" ] || [ "$manager" -le 1 ] ||
! sudo -n /usr/bin/systemctl is-active "$SERVICE" >/dev/null; then
echo "$SERVICE is not active" >&2
exit 1
fi
status_field()
{
field=$1
pid=$2
sed -n "s/^${field}:[[:space:]]*//p" "/proc/$pid/status"
}
assert_zero_caps()
{
pid=$1
name=$2
for field in CapInh CapPrm CapEff CapAmb; do
value=$(status_field "$field" "$pid")
if [ "$value" != 0000000000000000 ]; then
echo "$name ($pid) retained $field=$value" >&2
exit 1
fi
done
}
assert_no_groups()
{
pid=$1
name=$2
groups=$(status_field Groups "$pid")
if [ -n "$groups" ]; then
echo "$name ($pid) retained supplementary groups: $groups" >&2
exit 1
fi
}
assert_permanent_drop()
{
pid=$1
name=$2
set -- $(status_field Uid "$pid")
if [ "$#" -ne 4 ] || [ "$1" != "$bongo_uid" ] ||
[ "$2" != "$bongo_uid" ] || [ "$3" != "$bongo_uid" ] ||
[ "$4" != "$bongo_uid" ]; then
echo "$name ($pid) did not permanently drop UID: $*" >&2
exit 1
fi
set -- $(status_field Gid "$pid")
if [ "$#" -ne 4 ] || [ "$1" != "$bongo_gid" ] ||
[ "$2" != "$bongo_gid" ] || [ "$3" != "$bongo_gid" ] ||
[ "$4" != "$bongo_gid" ]; then
echo "$name ($pid) did not permanently drop GID: $*" >&2
exit 1
fi
assert_no_groups "$pid" "$name"
assert_zero_caps "$pid" "$name"
}
find_child()
{
wanted=$1
for pid in $(/usr/bin/pgrep -P "$manager" 2>/dev/null || true); do
name=$(status_field Name "$pid")
if [ "$name" = "$wanted" ]; then
printf '%s\n' "$pid"
return 0
fi
done
return 1
}
# The supervisor must regain root only while forking a replacement which has
# to bind a privileged listener. It must otherwise run as bongo with no active
# or ambient capability and no supplementary group.
set -- $(status_field Uid "$manager")
if [ "$#" -ne 4 ] || [ "$1" != 0 ] || [ "$2" != "$bongo_uid" ] ||
[ "$4" != "$bongo_uid" ]; then
echo "manager has unexpected UID tuple: $*" >&2
exit 1
fi
assert_no_groups "$manager" bongo-manager
for field in CapInh CapEff CapAmb; do
value=$(status_field "$field" "$manager")
if [ "$value" != 0000000000000000 ]; then
echo "manager retained active $field=$value" >&2
exit 1
fi
done
for name in $EXPECTED; do
pid=$(find_child "$name") || {
echo "configured agent $name is not a child of manager $manager" >&2
exit 1
}
if [ "$name" = bongoworker ]; then
# ACME certificate deployment is the one scheduled operation which
# currently needs a short, explicit seteuid(0) window. The idle worker
# must still have bongo as effective/filesystem UID and no active or
# ambient capability. This exception is removed when deployment is
# split into a dedicated privileged helper.
set -- $(status_field Uid "$pid")
if [ "$#" -ne 4 ] || [ "$1" != 0 ] ||
[ "$2" != "$bongo_uid" ] || [ "$4" != "$bongo_uid" ]; then
echo "$name ($pid) has unexpected UID tuple: $*" >&2
exit 1
fi
assert_no_groups "$pid" "$name"
for field in CapInh CapEff CapAmb; do
value=$(status_field "$field" "$pid")
if [ "$value" != 0000000000000000 ]; then
echo "$name ($pid) retained active $field=$value" >&2
exit 1
fi
done
else
assert_permanent_drop "$pid" "$name"
fi
echo "PASS $name PID $pid privilege state"
done
echo "PASS all configured agents are manager children with bounded privilege"
+1
View File
@@ -36,6 +36,7 @@ TimeoutStartSec=60s
TimeoutStopSec=45s
KillMode=control-group
KillSignal=SIGTERM
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_SETGID CAP_SETUID CAP_NET_BIND_SERVICE
UMask=0077
LimitNOFILE=65536
TasksMax=4096
+1 -1
View File
@@ -241,7 +241,7 @@ XplServiceMain()
int ccode;
int startupOpts;
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
if (XplSetRealUser(MsgGetUnprivilegedUser()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s'", MsgGetUnprivilegedUser());
return(1);
}
+1 -1
View File
@@ -493,7 +493,7 @@ XplServiceMain(int argc, char *argv[])
int ccode;
int startupOpts;
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
if (XplSetRealUser(MsgGetUnprivilegedUser()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s'", MsgGetUnprivilegedUser());
return(1);
}