This commit is contained in:
@@ -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`.
|
||||
|
||||
Executable
+137
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user