156 lines
4.8 KiB
Bash
Executable File
156 lines
4.8 KiB
Bash
Executable File
#!/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)
|
|
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
|
|
echo "$SERVICE is not active" >&2
|
|
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
|
|
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"
|
|
echo "PASS service capability boundary: $bounding"
|