Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Session Wrapper Smoke and Integration Testing

This document describes local checks that verify the Linux session-wrapper path is operating as expected. The real process mediation backend is Linux x86_64 only. Other platforms build the portable CLI, allowlist, deny-message, and authorization decision logic over a mock PAL backend that returns an explicit unsupported-platform error instead of executing or mediating commands.

For architecture, CLI shape, and current implementation scope, see the session-wrapper README.

Current scope

The session wrapper currently verifies process lifecycle and seccomp user notification wiring. TACACS+ authorization decisioning is intentionally stubbed as allow-all for now, so these smoke tests do not require a running tacacsrs-agentd service or TACACS+ server.

The trailing COMMAND [ARGS]... is the process that is executed under supervision. Smoke tests use small temporary scripts as the wrapped command.

Demo scripts

Runnable allow-all demos live in executables/session_wrapper/demo/:

ScriptPurpose
allow-all-basic.shBuilds session-wrapper, runs a short wrapped script, and verifies the wrapped process wrote a marker file
allow-all-descendants.shRuns a wrapped script that exits while a descendant continues
allow-all-interactive-bash.shStarts an interactive Bash session under the current allow-all supervisor for manual exploration

Run them from anywhere inside a Linux x86_64 checkout:

executables/session_wrapper/demo/allow-all-basic.sh
executables/session_wrapper/demo/allow-all-descendants.sh
executables/session_wrapper/demo/allow-all-interactive-bash.sh

Prerequisites

On Debian or Ubuntu, install the native Linux dependencies:

sudo apt-get update
sudo apt-get install -y build-essential gperf libseccomp-dev linux-libc-dev musl-tools
rustup target add x86_64-unknown-linux-musl

GNU builds can use the distribution libseccomp-dev. Musl builds require a musl-targeted static libseccomp. In CI this is built and cached by the shared Rust setup action. Locally, point Cargo at a musl libseccomp install before running musl checks:

export LIBSECCOMP_LIB_PATH=/path/to/libseccomp-musl/lib
export LIBSECCOMP_LINK_TYPE=static
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_PATH=/path/to/libseccomp-musl/lib/pkgconfig

Native Alpine builds have one extra libseccomp/musl linking caveat. See the crate-local Alpine Linux technical note.

Compile-time integration checks

On non-Linux development machines, run the portable checks first:

cargo check -p session-wrapper
cargo test -p session-wrapper
cargo clippy -p session-wrapper --all-targets -- -D warnings

These checks exercise the portable modules and the mock PAL backend. They do not validate fork, seccomp, /proc, signal, or child-reaping behavior.

Run these on Linux x86_64:

cargo clippy -p session-wrapper --all-targets -- -D warnings
cargo test -p session-wrapper

Run these when validating the static musl path:

cargo clippy -p session-wrapper --target x86_64-unknown-linux-musl --all-targets -- -D warnings
cargo test -p session-wrapper --target x86_64-unknown-linux-musl

These tests validate CLI parsing, seccomp policy generation, file descriptor passing, child setup status reporting, and socket close handling. They do not prove that a real child process can execute under the wrapper, so run the smoke tests below as well.

Smoke test: child starts and exits

This verifies the parent receives the notification fd, starts the temporary allow-all supervisor, releases the child, handles the child's initial execve, and exits when the child exits.

cargo build -p session-wrapper

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

cat >"$tmp/exit-zero" <<'EOF'
#!/bin/sh
printf 'ok\n' > "$SESSION_WRAPPER_SMOKE_MARKER"
EOF
chmod +x "$tmp/exit-zero"

SESSION_WRAPPER_SMOKE_MARKER="$tmp/marker" \
timeout 10s target/debug/session-wrapper \
  --user "$(id -un)" \
  --user-uid "$(id -u)" \
  --user-gid "$(id -g)" \
  --fail-policy open \
  -- "$tmp/exit-zero"

test "$(cat "$tmp/marker")" = "ok"

Expected result: the command exits successfully and the marker contains ok. A timeout usually means the supervisor is not responding to seccomp notifications or the child was not released.

Smoke test: child setup failures are reported

This verifies the child reports setup errors back to the parent instead of hanging or silently exiting.

cargo build -p session-wrapper

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

if timeout 10s target/debug/session-wrapper \
  --user "$(id -un)" \
  --user-uid "$(id -u)" \
  --user-gid "$(id -g)" \
  --fail-policy open \
  -- "$tmp/does-not-exist" 2>"$tmp/error"; then
  echo "expected session-wrapper to fail for a missing command" >&2
  exit 1
fi

grep -E 'child setup failed|execv' "$tmp/error"

Expected result: the command fails quickly and stderr includes the child setup or execv failure.

Smoke test: descendant execution remains supervised

This verifies seccomp inheritance and subreaper lifecycle tracking. The initial child starts a background descendant and exits; the wrapper should stay alive until the descendant has run its own exec path and exited.

cargo build -p session-wrapper

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

cat >"$tmp/descendant" <<'EOF'
#!/bin/sh
(
  sleep 0.2
  /bin/sh -c 'printf "descendant-ok\n" > "$SESSION_WRAPPER_DESCENDANT_MARKER"'
) &
exit 0
EOF
chmod +x "$tmp/descendant"

SESSION_WRAPPER_DESCENDANT_MARKER="$tmp/descendant-marker" \
timeout 10s target/debug/session-wrapper \
  --user "$(id -un)" \
  --user-uid "$(id -u)" \
  --user-gid "$(id -g)" \
  --fail-policy open \
  -- "$tmp/descendant"

test "$(cat "$tmp/descendant-marker")" = "descendant-ok"

Expected result: the marker contains descendant-ok. A missing marker or timeout indicates the wrapper stopped supervising before descendants completed, or fork/exec notifications were not continued.

Optional smoke test: privileged identity drop

Run this when you need to verify the root-to-user path used by login integrations. It requires sudo.

cargo build -p session-wrapper

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

cat >"$tmp/identity" <<'EOF'
#!/bin/sh
printf '%s:%s\n' "$(id -u)" "$(id -g)" > "$SESSION_WRAPPER_IDENTITY_MARKER"
EOF
chmod +x "$tmp/identity"

target_user=$(id -un)
target_uid=$(id -u)
target_gid=$(id -g)

sudo env SESSION_WRAPPER_IDENTITY_MARKER="$tmp/identity-marker" \
  target/debug/session-wrapper \
  --user "$target_user" \
  --user-uid "$target_uid" \
  --user-gid "$target_gid" \
  --fail-policy open \
  -- "$tmp/identity"

test "$(cat "$tmp/identity-marker")" = "$target_uid:$target_gid"

Expected result: the marker contains the target user's UID and primary GID, not 0:0.

Automation candidates

These smoke tests are good candidates for a Linux-only integration test job once the wrapper behavior stabilizes:

CheckRequires rootPurpose
Compile-time integration checksNoValidate Rust code, seccomp policy construction, fd passing, and musl compatibility
Child starts and exitsNoValidate notification fd handoff, ready synchronization, and child exec
Missing shell failureNoValidate child-to-parent setup error reporting
Descendant executionNoValidate inherited seccomp coverage and subreaper lifecycle handling
Privileged identity dropYesValidate login-style root-to-user execution

When real IPC authorization is wired in, keep these smoke tests but replace the allow-all assumption with a test IPC service that records each authorization request and responds with the desired decision.