Adds clawdie_firstboot_rootpw, an rc.d gate ordered BEFORE sddm and colibri_daemon. On the text console (operator present at first boot) it runs a 15s countdown to engage; if engaged it forces a root AND operator (clawdie) password, echo-off, applied via 'pw usermod -h 0' over stdin (secret never in argv/ps, never near the agent). Idempotent via a persistent success marker /var/db/colibri/.secured (/var persists: varmfs=NO). Skipping leaves the node open and re-prompts next boot — never bricks an unattended/headless boot. Running before the daemon means the security decision is always made before any agent can autospawn/node_register, so no cross-component interlock is needed (rc ordering replaces it). The .secured marker is also the signal a future colibri change can read to label an unsecured node to mother. Tests: tests/firstboot-rootpw-test.sh proves marker skip, password validation, and that the secret is delivered on stdin and NEVER appears in argv (10/10). Console interactivity (read -t countdown, stty echo-off on /dev/console) must be verified by booting on osa/bhyve before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.5 KiB
Bash
Executable file
64 lines
2.5 KiB
Bash
Executable file
#!/bin/sh
|
|
# Logic test for the first-boot password gate (clawdie-firstboot-rootpw).
|
|
#
|
|
# Exercises the testable security logic on any POSIX host: marker skip,
|
|
# password validation, the secured-marker write, and — critically — that the
|
|
# password is applied to `pw usermod <user> -h 0` via STDIN (never argv).
|
|
# The interactive countdown (_start) needs a real console and is verified by
|
|
# booting on osa, not here.
|
|
set -u
|
|
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
GATE="${SCRIPT_DIR}/../live/operator-session/clawdie-firstboot-rootpw"
|
|
[ -r "${GATE}" ] || { echo "FATAL: gate not found at ${GATE}" >&2; exit 2; }
|
|
|
|
WORK=$(mktemp -d "${TMPDIR:-/tmp}/firstboot-rootpw.XXXXXX") || exit 2
|
|
trap 'rm -rf "${WORK}"' EXIT INT TERM
|
|
|
|
# Fake `pw`: records argv and the password read from stdin, so we can prove the
|
|
# secret arrives on stdin and never on the command line.
|
|
FAKEPW="${WORK}/fake-pw"
|
|
cat >"${FAKEPW}" <<EOF
|
|
#!/bin/sh
|
|
printf '%s\n' "ARGV: \$*" >> "${WORK}/pw.calls"
|
|
IFS= read -r _stdin_pw
|
|
printf '%s\n' "STDIN: \${_stdin_pw}" >> "${WORK}/pw.calls"
|
|
EOF
|
|
chmod +x "${FAKEPW}"
|
|
|
|
export CLAWDIE_ROOTPW_TEST=1
|
|
export SECURED_MARKER="${WORK}/secured"
|
|
export PW_BIN="${FAKEPW}"
|
|
export ROOTPW_MIN_LEN=8
|
|
|
|
# shellcheck disable=SC1090
|
|
. "${GATE}"
|
|
|
|
PASS=0; FAIL=0
|
|
ok() { PASS=$((PASS+1)); printf ' ok %s\n' "$1"; }
|
|
bad() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1"; }
|
|
chk() { [ "$1" -eq 0 ] && ok "$2" || bad "$2"; }
|
|
|
|
echo "== marker / idempotency =="
|
|
_rootpw_secured; chk "$([ $? -ne 0 ] && echo 0 || echo 1)" "not secured when marker absent"
|
|
_rootpw_mark_secured
|
|
[ -f "${SECURED_MARKER}" ]; chk $? "mark_secured creates the marker"
|
|
_rootpw_secured; chk $? "secured when marker present"
|
|
|
|
echo "== password validation =="
|
|
_rootpw_valid "goodpassword" "goodpassword" >/dev/null; chk $? "accepts matching >=8 char password"
|
|
{ ! _rootpw_valid "" "" >/dev/null; }; chk $? "rejects empty"
|
|
{ ! _rootpw_valid "abc" "abc" >/dev/null; }; chk $? "rejects too short"
|
|
{ ! _rootpw_valid "password1" "password2" >/dev/null; }; chk $? "rejects mismatch"
|
|
|
|
echo "== apply via stdin (secret never in argv) =="
|
|
: > "${WORK}/pw.calls"
|
|
printf '%s\n' "s3cret-on-stdin" | _rootpw_apply root
|
|
grep -q 'ARGV: usermod root -h 0' "${WORK}/pw.calls"; chk $? "pw called: usermod root -h 0"
|
|
grep -q 'STDIN: s3cret-on-stdin' "${WORK}/pw.calls"; chk $? "password delivered on STDIN"
|
|
{ ! grep -q 'ARGV:.*s3cret-on-stdin' "${WORK}/pw.calls"; }; chk $? "password NEVER appears in argv"
|
|
|
|
echo
|
|
echo "RESULT: ${PASS} passed, ${FAIL} failed"
|
|
[ "${FAIL}" -eq 0 ] || exit 1
|
|
exit 0
|