fix: prove Linux Agent sandbox boundary

This commit is contained in:
2026-07-10 17:59:56 +08:00
parent f4968d6657
commit f07f280b8f
19 changed files with 381 additions and 55 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
# Hub service installation
The initial production target is a supported Linux host with systemd,
PostgreSQL, Node.js 20 or newer, `pg_isready`, `runuser`, `setpriv`, bubblewrap
and `cph`. The Hub runs as the dedicated non-login `cph-hub` user; do not
PostgreSQL, Node.js 20 or newer, `pg_isready`, `runuser`, `setpriv`, bubblewrap,
`socat` and `cph`. The Hub runs as the dedicated non-login `cph-hub` user; do not
create or run the unit as root.
Build or deploy the Hub under `/srv/curriculum-project-hub`, then run:
@@ -18,13 +18,13 @@ On a clean host the first invocation creates
production key and exits with status 78. Fill every required blank, set
`CPH_BIN` to an executable compatible `cph`, keep the file mode at `0600`, and
run the same command again. No systemd unit is installed until configuration,
paths, Node, bubblewrap, `cph --version`, PostgreSQL connectivity and directory
paths, Node, bubblewrap, `socat`, `cph --version`, PostgreSQL connectivity and directory
ownership all pass preflight.
An existing service account is accepted only when its primary group, home and
non-login shell match the requested configuration and it has no supplementary
groups. After provisioning, the installer executes path-access, built Hub,
Prisma, an authenticated database query, `cph` and bubblewrap namespace probes
Prisma, an authenticated database query, `cph`, `socat` and bubblewrap namespace probes
as that account with `no_new_privs` set. Inaccessible parents, UID-specific
database/network policy, or a host that forbids unprivileged user namespaces
therefore fail before unit installation.
+1
View File
@@ -6,6 +6,7 @@ Wants=network-online.target
# after installation. Deployment preflight validates the executable itself;
# this start-time assertion keeps later package/host changes fail-closed.
AssertPathExists=__BWRAP_BIN__
AssertPathExists=__SOCAT_BIN__
[Service]
Type=simple
+1 -1
View File
@@ -9,7 +9,7 @@
# PLATFORM_DEPLOY_BASE optional, defaults to /srv/curriculum-project-hub
# PLATFORM_DEPLOY_HEALTH_URL optional, defaults to http://127.0.0.1:8788/api/healthz
# The target host must have Node.js 20+, npm, rsync, PostgreSQL client tools
# (`pg_isready`), systemd, bubblewrap (`bwrap`, for the ADR-0018 agent sandbox),
# (`pg_isready`), systemd, bubblewrap (`bwrap`) and `socat` (for the ADR-0018 agent sandbox),
# and a compatible `cph` binary named by platform.env. The service installer
# provisions the dedicated non-root identity and persistent directories.
# Use a dedicated `deploy` user that has passwordless sudo for systemctl and
+6 -1
View File
@@ -24,13 +24,14 @@ ENV_FILE="${ENV_FILE:-$BASE/.secrets/platform.env}"
SYSTEMD_UNIT_DIR="${SYSTEMD_UNIT_DIR:-/etc/systemd/system}"
NODE_BIN="${NODE_BIN:-$(command -v node || true)}"
BWRAP_BIN="${BWRAP_BIN:-$(command -v bwrap || true)}"
SOCAT_BIN="${SOCAT_BIN:-$(command -v socat || true)}"
PG_ISREADY_BIN="${PG_ISREADY_BIN:-$(command -v pg_isready || true)}"
RUNUSER_BIN="${RUNUSER_BIN:-$(command -v runuser || true)}"
SETPRIV_BIN="${SETPRIV_BIN:-$(command -v setpriv || true)}"
CPH_BIN_DEFAULT="${CPH_BIN_DEFAULT:-/usr/local/bin/cph}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PREFLIGHT_JS="$HUB_DIR/dist/deployment/preflight-cli.js"
RUNTIME_PATH="$(dirname "$NODE_BIN"):$(dirname "$BWRAP_BIN"):$(dirname "$SETPRIV_BIN"):/usr/local/bin:/usr/bin:/bin"
RUNTIME_PATH="$(dirname "$NODE_BIN"):$(dirname "$BWRAP_BIN"):$(dirname "$SOCAT_BIN"):$(dirname "$SETPRIV_BIN"):/usr/local/bin:/usr/bin:/bin"
fail() {
echo "[install] error: $*" >&2
@@ -68,6 +69,7 @@ done
for pair in \
"NODE_BIN:$NODE_BIN" \
"BWRAP_BIN:$BWRAP_BIN" \
"SOCAT_BIN:$SOCAT_BIN" \
"PG_ISREADY_BIN:$PG_ISREADY_BIN"; do
label="${pair%%:*}"
value="${pair#*:}"
@@ -98,6 +100,7 @@ for pair in \
"SYSTEMD_UNIT_DIR:$SYSTEMD_UNIT_DIR" \
"NODE_BIN:$NODE_BIN" \
"BWRAP_BIN:$BWRAP_BIN" \
"SOCAT_BIN:$SOCAT_BIN" \
"RUNUSER_BIN:$RUNUSER_BIN" \
"SETPRIV_BIN:$SETPRIV_BIN" \
"RUNTIME_PATH:$RUNTIME_PATH"; do
@@ -152,6 +155,7 @@ PREFLIGHT_ARGS=(
--cache-dir "$CACHE_DIR"
--workspace-root "$WORKSPACE_ROOT"
--bwrap-bin "$BWRAP_BIN"
--socat-bin "$SOCAT_BIN"
--pg-isready-bin "$PG_ISREADY_BIN"
)
@@ -247,6 +251,7 @@ sed \
-e "s|__ENV_FILE__|$ENV_FILE|g" \
-e "s|__NODE_BIN__|$NODE_BIN|g" \
-e "s|__BWRAP_BIN__|$BWRAP_BIN|g" \
-e "s|__SOCAT_BIN__|$SOCAT_BIN|g" \
-e "s|__RUNTIME_PATH__|$RUNTIME_PATH|g" \
"$SCRIPT_DIR/cph-hub.service" >"$TMP_UNIT"
install -o root -g root -m 0644 "$TMP_UNIT" "$UNIT"