#!/bin/sh # Test: the MDM pkg postinstall script surfaces ctrld's provisioning # failure identifier in its output without leaking the provision token, # and still reports success once the plist exists. # # Self-contained and root-free: every path postinstall touches is # redirected into a throwaway temp directory via the # CTRLD_POSTINSTALL_{PLIST,CTRLD,PREFS} overrides, and 'defaults' is # stubbed on PATH so the profile-wait loop resolves on its first attempt. # # Out of scope: the upgrade path (plist already exists) calls the real # launchctl and is not exercised here. # # Run: sh test-postinstall-provision-failure.sh SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) POSTINSTALL="$SCRIPT_DIR/../../scripts/pkg/postinstall" WORKDIR=$(mktemp -d -t ctrld-postinstall-test) || { echo "FAIL: could not create test work directory" >&2 exit 1 } trap 'rm -rf "$WORKDIR"' EXIT FAKE_TOKEN="FAKE-PROVISION-TOKEN-DO-NOT-LEAK-93af0c" export FAKE_TOKEN STUBBIN="$WORKDIR/stubbin" mkdir -p "$STUBBIN" cat > "$STUBBIN/defaults" <<'STUB' #!/bin/sh # Stand-in for macOS 'defaults read ': answers ProvisionToken # immediately, like a profile that only sets that one key, so the # postinstall wait loop never has to sleep. if [ "$1" = "read" ] && [ "$3" = "ProvisionToken" ]; then echo "$FAKE_TOKEN" exit 0 fi exit 1 STUB chmod +x "$STUBBIN/defaults" FAILURES=0 fail() { echo "FAIL: $1" >&2 FAILURES=$((FAILURES + 1)) } assert_eq() { # assert_eq if [ "$1" != "$2" ]; then fail "$3 (expected '$2', got '$1')" fi } assert_contains() { # assert_contains case "$1" in *"$2"*) ;; *) fail "$3 (expected to find '$2')" ;; esac } assert_not_contains() { # assert_not_contains case "$1" in *"$2"*) fail "$3 (must not contain '$2')" ;; *) ;; esac } # run_postinstall runs postinstall with the given plist/ctrld overrides and # a private TMPDIR, so the caller can check that the postinstall's own # capture file (created inside that TMPDIR via mktemp) does not survive. run_postinstall() { plist_override=$1 ctrld_override=$2 capture_tmpdir=$3 output=$(PATH="$STUBBIN:$PATH" \ TMPDIR="$capture_tmpdir" \ CTRLD_POSTINSTALL_PLIST="$plist_override" \ CTRLD_POSTINSTALL_CTRLD="$ctrld_override" \ CTRLD_POSTINSTALL_PREFS="/does/not/matter" \ sh "$POSTINSTALL" 2>&1) exit_code=$? } # --- Failure case: ctrld reports a listener bind failure --------------- failure_dir="$WORKDIR/failure" failure_tmp="$failure_dir/tmp" mkdir -p "$failure_tmp" failure_plist="$failure_dir/ctrld.plist" failure_ctrld="$failure_dir/ctrld" cat > "$failure_ctrld" <<'STUB' #!/bin/sh # Stands in for a ctrld that fails to bind its listener: echoes the raw # token (as ctrld's own error output may) plus the fixed-format failure # identifier behind a log-style prefix, then exits with the stage code. echo "$FAKE_TOKEN" echo "2024-01-01T00:00:00Z ERR ctrld: provisioning failed: stage=listener code=LISTENER_BIND_FAILED (exit 41)" exit 41 STUB chmod +x "$failure_ctrld" run_postinstall "$failure_plist" "$failure_ctrld" "$failure_tmp" assert_eq "$exit_code" "1" "failure case: postinstall exit code" assert_contains "$output" "stage=listener" "failure case: output names the stage" assert_contains "$output" "LISTENER_BIND_FAILED" "failure case: output names the code" assert_contains "$output" "41" "failure case: output names the exit code" assert_not_contains "$output" "$FAKE_TOKEN" "failure case: output must not contain the provision token" leftover=$(ls -A "$failure_tmp" 2>/dev/null) assert_eq "$leftover" "" "failure case: capture temp file removed" # --- Success case: ctrld provisions and writes the plist ---------------- success_dir="$WORKDIR/success" success_tmp="$success_dir/tmp" mkdir -p "$success_tmp" success_plist="$success_dir/ctrld.plist" success_ctrld="$success_dir/ctrld" cat > "$success_ctrld" < "$success_plist" exit 0 STUB chmod +x "$success_ctrld" run_postinstall "$success_plist" "$success_ctrld" "$success_tmp" assert_eq "$exit_code" "0" "success case: postinstall exit code" assert_contains "$output" "provisioning complete" "success case: output mentions success" if [ "$FAILURES" -gt 0 ]; then echo "$FAILURES assertion(s) failed" >&2 exit 1 fi echo "OK: postinstall surfaces provisioning failure codes without leaking the token" exit 0