Files
platform-core/scripts/validate/test-k3s-local-recovery-header-proof.sh
T

798 lines
34 KiB
Bash

#!/usr/bin/env bash
# Source-safe TDD coverage for the standalone K3s recovery LUKS header proof.
set -Eeuo pipefail
readonly K3SLRH_TEST_DIRECTORY="${BASH_SOURCE[0]%/*}"
readonly K3SLRH_REPOSITORY_ROOT="$(cd -- "${K3SLRH_TEST_DIRECTORY}/../.." && pwd -P)"
readonly K3SLRH_SHARED_LIBRARY="${K3SLRH_REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery.sh"
readonly K3SLRH_A1_LIBRARY="${K3SLRH_REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery-a1.sh"
readonly K3SLRH_A1_HELPER="${K3SLRH_REPOSITORY_ROOT}/scripts/libexec/k3s-local-recovery-a1-check.sh"
readonly K3SLRH_PROOF_LIBRARY="${K3SLRH_REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery-header-proof.sh"
readonly K3SLRH_WRAPPER="${K3SLRH_REPOSITORY_ROOT}/scripts/bootstrap/prove-k3s-local-recovery-header.sh"
readonly K3SLRH_CONTRACT="${K3SLRH_REPOSITORY_ROOT}/infrastructure/security/k3s/local-recovery.env"
readonly K3SLRH_VALIDATOR="${K3SLRH_REPOSITORY_ROOT}/scripts/validate/k3s-local-recovery.sh"
readonly K3SLRH_AUTHORITY_DESIGN='/home/donghyeon/workspace/docs/platform/plans/2026-08-09-k3s-luks-header-proof-design.md'
readonly K3SLRH_AUTHORITY_PLAN='/home/donghyeon/workspace/docs/platform/plans/2026-08-09-k3s-luks-header-proof-implementation.md'
readonly K3SLRH_AUTHORITY_DESIGN_SHA256='0d1546795da59490956ba2e260bf12df210083514ae55c0551de632d54e278ec'
readonly K3SLRH_AUTHORITY_PLAN_SHA256='126f7fa43fb857dd9ff753aac4ce6528f7a227d7151d688f8cce2b6e0ff8fbc9'
readonly K3SLRH_EXPECTED_SHARED_SHA256='a206d81539e6c37fa085457ba01cff9de57de50f7235e3a4085989780e99585d'
readonly K3SLRH_EXPECTED_A1_SHA256='8b39fc46229ebd4c77ff20040f942c4ef7b756d564d344a8ecdd11a7428e2968'
readonly K3SLRH_EXPECTED_A1_HELPER_SHA256='06831fa35943a328ecd183d3c6f721b24c4607b2f349abf6201a12e690d08ebe'
readonly K3SLRH_EXPECTED_CONTRACT_SHA256='c045f42c2dc3eda09c37a123bcc05d88d94f275e61504a2d5816948237e516df'
readonly K3SLRH_EXPECTED_VALIDATOR_SHA256='6b869032fc325759ee25b219fad4ea6190a3389a561c496f0664a51b802f73e6'
k3slrh_test_fail() {
printf 'TEST FAILURE: %s\n' "$*" >&2
exit 1
}
# This RED gate intentionally precedes authority hashing, source scanning,
# sourcing, mktemp, command doubles, and every capability or live path.
[[ -f "$K3SLRH_PROOF_LIBRARY" && ! -L "$K3SLRH_PROOF_LIBRARY" &&
-r "$K3SLRH_PROOF_LIBRARY" ]] || k3slrh_test_fail 'header-proof library missing'
[[ -f "$K3SLRH_WRAPPER" && ! -L "$K3SLRH_WRAPPER" &&
-r "$K3SLRH_WRAPPER" ]] || k3slrh_test_fail 'header-proof wrapper missing'
[[ "${K3SLRH_FOCUS:-entrypoint}" == entrypoint ]] ||
k3slrh_test_fail 'unsupported header-proof focus'
k3slrh_sha_is_exact() {
local path="${1-}" expected="${2-}" record
(( $# == 2 )) || return 97
record="$(/usr/bin/sha256sum -- "$path")" || return 1
[[ "$record" == "${expected} ${path}" ]]
}
for k3slrh_checkpoint in \
"$K3SLRH_AUTHORITY_DESIGN:$K3SLRH_AUTHORITY_DESIGN_SHA256" \
"$K3SLRH_AUTHORITY_PLAN:$K3SLRH_AUTHORITY_PLAN_SHA256" \
"$K3SLRH_SHARED_LIBRARY:$K3SLRH_EXPECTED_SHARED_SHA256" \
"$K3SLRH_A1_LIBRARY:$K3SLRH_EXPECTED_A1_SHA256" \
"$K3SLRH_A1_HELPER:$K3SLRH_EXPECTED_A1_HELPER_SHA256" \
"$K3SLRH_CONTRACT:$K3SLRH_EXPECTED_CONTRACT_SHA256" \
"$K3SLRH_VALIDATOR:$K3SLRH_EXPECTED_VALIDATOR_SHA256"; do
k3slrh_checkpoint_path="${k3slrh_checkpoint%:*}"
k3slrh_checkpoint_hash="${k3slrh_checkpoint##*:}"
k3slrh_sha_is_exact "$k3slrh_checkpoint_path" "$k3slrh_checkpoint_hash" ||
k3slrh_test_fail 'authority or frozen dependency drift'
done
unset k3slrh_checkpoint k3slrh_checkpoint_path k3slrh_checkpoint_hash
k3slrh_library_static_is_safe() {
local source_path="${1-}"
(( $# == 1 )) || return 97
[[ -f "$source_path" && ! -L "$source_path" ]] || return 1
/usr/bin/awk '
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function external_path(value) {
return value ~ /\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+/
}
function sensitive_bare(value) {
return value ~ /(^|(&&|\|\||;)[[:space:]]*)(sudo|mount|umount|losetup|cryptsetup|keepassxc-cli|sqlite3|systemctl|timeout|k3s|findmnt|lsblk|blockdev|dmsetup)([[:space:]]|$)/
}
{
raw = $0
value = trim(raw)
if (value == "" || value ~ /^#/) next
if (value ~ /<Task-[0-9]+-[^>]+>/) bad = 1
if (value ~ /_k3slr_collect_prepackage_snapshot|_k3slr_lifecycle_dispatch|_k3slr_prove_header_restore/) bad = 1
if (in_function) {
if (raw == "}") { in_function = 0; function_name = ""; next }
if (value ~ /`/ || value ~ /(^|[;[:space:]])(eval|command|exec)[[:space:]]/) bad = 1
if (sensitive_bare(value) && value !~ /_k3slrh_command[[:space:]]/) bad = 1
if (external_path(value) && value !~ /_k3slrh_command[[:space:]]/ &&
value !~ /_k3slr[a-z0-9_]+[[:space:]]/) bad = 1
next
}
if (value ~ /^[A-Za-z_][A-Za-z0-9_]*\(\)[[:space:]]*\{$/) {
in_function = 1
function_name = value
sub(/\(\)[[:space:]]*\{$/, "", function_name)
next
}
if (value ~ /^readonly[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=(\047[^\047]*\047|[A-Za-z0-9_.:+\/-]+)$/) next
bad = 1
}
END { if (in_function || bad) exit 1 }
' "$source_path"
}
k3slrh_wrapper_static_is_safe() {
local source_path="${1-}"
(( $# == 1 )) || return 97
[[ -f "$source_path" && ! -L "$source_path" ]] || return 1
/usr/bin/awk '
{
if ($0 ~ /_k3slr_collect_prepackage_snapshot|_k3slr_lifecycle_dispatch|_k3slr_prove_header_restore/) bad = 1
if ($0 ~ /(^|[[:space:]])(sudo|mount|umount|losetup|cryptsetup|keepassxc-cli|sqlite3)([[:space:]]|$)/) bad = 1
if ($0 ~ /scripts\/lib\/k3s-local-recovery\.sh/) shared = NR
if ($0 ~ /scripts\/lib\/k3s-local-recovery-a1\.sh/) a1 = NR
if ($0 ~ /scripts\/lib\/k3s-local-recovery-header-proof\.sh/) proof = NR
if ($0 ~ /^if \[\[ "\$\{BASH_SOURCE\[0\]\}" == "\$0" \]\]; then$/) direct_guard++
if ($0 ~ /_k3slrh_header_proof_main "\$@"/) direct_main++
if ($0 ~ /_k3slrh_wrapper_initial_guard/) initial_guard++
if ($0 ~ /unset -f[[:space:]]+_k3slrh_/) bad = 1
if ($0 ~ /_k3slrh_wrapper_environment_is_clean/) inherited_guard = NR
}
END {
if (bad || shared == 0 || a1 == 0 || proof == 0 ||
!(shared < a1 && a1 < proof) || direct_guard != 1 ||
direct_main != 1 || initial_guard < 2 || inherited_guard == 0 ||
inherited_guard >= shared) exit 1
}
' "$source_path"
}
# No production source is evaluated until both conservative scans pass.
k3slrh_library_static_is_safe "$K3SLRH_PROOF_LIBRARY" ||
k3slrh_test_fail 'header-proof library static preflight failed'
k3slrh_wrapper_static_is_safe "$K3SLRH_WRAPPER" ||
k3slrh_test_fail 'header-proof wrapper static preflight failed'
# shellcheck source=/dev/null
source "$K3SLRH_SHARED_LIBRARY" || k3slrh_test_fail 'shared library source failed'
# shellcheck source=/dev/null
source "$K3SLRH_A1_LIBRARY" || k3slrh_test_fail 'A1 library source failed'
# shellcheck source=/dev/null
source "$K3SLRH_PROOF_LIBRARY" || k3slrh_test_fail 'header-proof library source failed'
_k3slr_load_contract "$K3SLRH_CONTRACT" || k3slrh_test_fail 'strict contract load failed'
k3slrh_require_function() {
local name="${1-}"
(( $# == 1 )) || return 97
declare -F "$name" >/dev/null || k3slrh_test_fail "header-proof API missing: ${name}"
}
for k3slrh_api in \
_k3slrh_parse_cli \
_k3slrh_derive_targets \
_k3slrh_run_closed_validator \
_k3slrh_k3s_health \
_k3slrh_pre_mount_capability \
_k3slrh_header_proof_main \
_k3slrh_target_state_is_consistent; do
k3slrh_require_function "$k3slrh_api"
done
unset k3slrh_api
k3slrh_assert_readonly_literal() {
local name="${1-}" expected="${2-}" declaration
(( $# == 2 )) || return 97
declaration="$(declare -p "$name" 2>/dev/null)" || return 1
[[ "$declaration" == 'declare -r '* || "$declaration" == 'declare -rx '* ]] || return 1
[[ "${!name}" == "$expected" ]]
}
k3slrh_assert_readonly_literal K3SLRH_PROOF_RELATIVE \
'HyeonworksRecovery/containers/k3s-recovery.header-proof.luks' ||
k3slrh_test_fail 'proof relative name is not immutable'
k3slrh_assert_readonly_literal K3SLRH_PRE_KDBX_PREFIX \
'hyeonworks-recovery.header-proof-pre.' ||
k3slrh_test_fail 'pre KDBX prefix is not immutable'
k3slrh_assert_readonly_literal K3SLRH_POST_KDBX_PREFIX \
'hyeonworks-recovery.header-proof-post.' ||
k3slrh_test_fail 'post KDBX prefix is not immutable'
k3slrh_assert_readonly_literal K3SLRH_HEADER_BACKUP_NAME 'header-original.bin' ||
k3slrh_test_fail 'header backup name is not immutable'
k3slrh_assert_readonly_literal K3SLRH_HEADER_EXPORT_NAME 'header-exported.bin' ||
k3slrh_test_fail 'header export name is not immutable'
k3slrh_assert_readonly_literal K3SLRH_SQLITE_PACKAGE_VERSION '3.45.1-1ubuntu2.7' ||
k3slrh_test_fail 'sqlite package version is not immutable'
k3slrh_assert_readonly_literal K3SLRH_SHARED_LIBRARY_SHA256 "$K3SLRH_EXPECTED_SHARED_SHA256" ||
k3slrh_test_fail 'shared source checkpoint placeholder or drift'
k3slrh_assert_readonly_literal K3SLRH_A1_LIBRARY_SHA256 "$K3SLRH_EXPECTED_A1_SHA256" ||
k3slrh_test_fail 'A1 source checkpoint placeholder or drift'
k3slrh_assert_readonly_literal K3SLRH_A1_HELPER_SHA256 "$K3SLRH_EXPECTED_A1_HELPER_SHA256" ||
k3slrh_test_fail 'A1 helper checkpoint placeholder or drift'
k3slrh_assert_readonly_literal K3SLRH_CONTRACT_SHA256 "$K3SLRH_EXPECTED_CONTRACT_SHA256" ||
k3slrh_test_fail 'contract checkpoint placeholder or drift'
k3slrh_assert_readonly_literal K3SLRH_VALIDATOR_SHA256 "$K3SLRH_EXPECTED_VALIDATOR_SHA256" ||
k3slrh_test_fail 'validator checkpoint placeholder or drift'
readonly K3SLRH_EXPECTED_UTC='20260809T120304Z'
readonly K3SLRH_RAW_IDENTIFIER_SENTINEL='RAW_IDENTIFIER_SENTINEL_DO_NOT_PRINT'
readonly K3SLRH_EXPECTED_CLOSED_RECORD=$'Recovery device: match\nRecovery state: closed\nLineage: match\nLatest bundle: not_checked\n'
readonly K3SLRH_EXPECTED_DRY_RUN=$'Header proof state: closed\nHeader proof capability: pass\nExecute-time checks: pending\nDRY RUN PASS: no changes\n'
k3slrh_fixture_root="$(/usr/bin/mktemp -d /tmp/k3slrh-entrypoint-test.XXXXXXXX)" ||
k3slrh_test_fail 'cannot allocate fixture root'
readonly k3slrh_fixture_root
readonly k3slrh_stdout="${k3slrh_fixture_root}/stdout"
readonly k3slrh_stderr="${k3slrh_fixture_root}/stderr"
readonly k3slrh_command_log="${k3slrh_fixture_root}/commands"
readonly k3slrh_mutation_log="${k3slrh_fixture_root}/mutations"
readonly k3slrh_validator_call_log="${k3slrh_fixture_root}/validator-calls"
readonly k3slrh_validator_hash_log="${k3slrh_fixture_root}/validator-hashes"
readonly k3slrh_sudo_validate_log="${k3slrh_fixture_root}/sudo-validates"
readonly k3slrh_readyz_log="${k3slrh_fixture_root}/readyz-calls"
readonly k3slrh_validator_file_log="${k3slrh_fixture_root}/validator-file-checks"
readonly k3slrh_validator_symlink_log="${k3slrh_fixture_root}/validator-symlink-checks"
readonly k3slrh_validator_stat_log="${k3slrh_fixture_root}/validator-stat-checks"
readonly k3slrh_binary_stat_log="${k3slrh_fixture_root}/binary-stat-checks"
k3slrh_cleanup() {
case "$k3slrh_fixture_root" in
/tmp/k3slrh-entrypoint-test.[A-Za-z0-9]*) /usr/bin/rm -rf -- "$k3slrh_fixture_root" ;;
*) return 1 ;;
esac
}
trap k3slrh_cleanup EXIT
k3slrh_assert_eq() {
local expected="${1-}" actual="${2-}" label="${3-}"
(( $# == 3 )) || k3slrh_test_fail 'invalid assert_eq invocation'
[[ "$actual" == "$expected" ]] || k3slrh_test_fail "$label"
}
k3slrh_assert_rc() {
local expected="${1-}" label="${2-}" rc=0
shift 2 || k3slrh_test_fail 'invalid assert_rc invocation'
if "$@"; then rc=0; else rc=$?; fi
[[ "$rc" == "$expected" ]] || k3slrh_test_fail "${label}: rc=${rc}, expected=${expected}"
}
k3slrh_assert_succeeds() {
local label="${1-}"
shift || k3slrh_test_fail 'invalid assert_succeeds invocation'
"$@" || k3slrh_test_fail "$label"
}
k3slrh_wrapper_clean_composition() {
local rc=0
: >"$k3slrh_stdout"
: >"$k3slrh_stderr"
if /usr/bin/env -i \
PATH=/usr/sbin:/usr/bin:/sbin:/bin LC_ALL=C \
/usr/bin/bash --noprofile --norc -c '
set -Eeuo pipefail
source "$1"
declare -F _k3slrh_header_proof_main >/dev/null
declare -F _k3slrh_wrapper_initial_guard >/dev/null
if _k3slrh_wrapper_initial_guard 0 hB; then exit 41; fi
if _k3slrh_wrapper_initial_guard 1000 hxB; then exit 42; fi
' k3slrh-wrapper-probe "$K3SLRH_WRAPPER" \
>"$k3slrh_stdout" 2>"$k3slrh_stderr"; then
rc=0
else
rc=$?
fi
[[ "$rc" == 0 && ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" ]]
}
k3slrh_wrapper_rejects_inherited_route() {
local rc=0 marker="${k3slrh_fixture_root}/inherited-route-marker"
: >"$k3slrh_stdout"
: >"$k3slrh_stderr"
if /usr/bin/env -i \
PATH=/usr/sbin:/usr/bin:/sbin:/bin LC_ALL=C \
/usr/bin/bash --noprofile --norc -c '
set -Eeuo pipefail
K3SLRH_PROOF_RELATIVE=attacker-controlled
test_marker=$2
_k3slrh_header_proof_main() { : >"$test_marker"; }
set +e
source "$1" >/dev/null 2>&1
wrapper_rc=$?
set -e
[[ "$wrapper_rc" == 1 && ! -e "$test_marker" ]]
' k3slrh-wrapper-probe "$K3SLRH_WRAPPER" "$marker" \
>"$k3slrh_stdout" 2>"$k3slrh_stderr"; then
rc=0
else
rc=$?
fi
[[ "$rc" == 0 && ! -e "$marker" && ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" ]]
}
k3slrh_run_parse() {
local parsed='' rc=0
if _k3slrh_parse_cli parsed "$@"; then rc=0; else rc=$?; fi
(( rc == 0 )) || return "$rc"
printf '%s' "$parsed"
}
k3slrh_run_timestamp() (
_k3slrh_derive_targets "$@"
)
k3slrh_targets_are_exact() (
_k3slrh_derive_targets "$K3SLRH_EXPECTED_UTC" || return 1
[[ "${K3SLRH_RUN_UTC:-}" == "$K3SLRH_EXPECTED_UTC" &&
"${K3SLRH_PRE_KDBX_RELATIVE:-}" == \
"HyeonworksRecovery/vault/backups/${K3SLRH_PRE_KDBX_PREFIX}${K3SLRH_EXPECTED_UTC}.kdbx" &&
"${K3SLRH_POST_KDBX_RELATIVE:-}" == \
"HyeonworksRecovery/vault/backups/${K3SLRH_POST_KDBX_PREFIX}${K3SLRH_EXPECTED_UTC}.kdbx" ]] || return 1
_k3slrh_target_state_is_consistent \
"$K3SLRH_RUN_UTC" "$K3SLRH_PRE_KDBX_RELATIVE" "$K3SLRH_POST_KDBX_RELATIVE"
)
k3slrh_run_targets_same_second_collision() (
_k3slrh_derive_targets "$K3SLRH_EXPECTED_UTC" || return 1
_k3slrh_derive_targets "$K3SLRH_EXPECTED_UTC"
)
k3slrh_run_targets_pre_post_timestamp_drift() (
local drifted_post
_k3slrh_derive_targets "$K3SLRH_EXPECTED_UTC" || return 1
drifted_post="HyeonworksRecovery/vault/backups/${K3SLRH_POST_KDBX_PREFIX}20260809T120305Z.kdbx"
_k3slrh_target_state_is_consistent \
"$K3SLRH_RUN_UTC" "$K3SLRH_PRE_KDBX_RELATIVE" "$drifted_post"
)
k3slrh_model_case=valid
k3slrh_validator_count=0
k3slrh_mutation_count=0
k3slrh_log_command() {
local argument
for argument in "$@"; do printf '%s\0' "$argument" >>"$k3slrh_command_log"; done
printf '\0' >>"$k3slrh_command_log"
}
k3slrh_path_is_known_binary() {
local candidate
for candidate in \
/usr/bin/awk /usr/bin/bash /usr/bin/chmod /usr/bin/chown /usr/bin/cmp \
/usr/bin/date /usr/bin/dd /usr/bin/dpkg-query /usr/bin/env /usr/bin/find \
/usr/bin/findmnt /usr/bin/grep /usr/bin/keepassxc-cli /usr/bin/lsblk \
/usr/bin/mkdir /usr/bin/mktemp /usr/bin/mount /usr/bin/ntfsinfo \
/usr/bin/od /usr/bin/readlink /usr/bin/rm /usr/bin/rmdir /usr/bin/sha256sum \
/usr/bin/sqlite3 /usr/bin/stat /usr/bin/sudo /usr/bin/sync /usr/bin/systemctl \
/usr/bin/test /usr/bin/timeout /usr/bin/umount /usr/bin/unlink /usr/bin/wc \
/usr/local/bin/k3s /usr/sbin/blockdev /usr/sbin/cryptsetup /usr/sbin/dmsetup \
/usr/sbin/losetup; do
[[ "${1-}" != "$candidate" ]] || return 0
done
return 1
}
_k3slrh_command() {
local package='' path='' expected_backing='' hash_count=0
k3slrh_log_command "$@"
case "${1-}" in
/usr/bin/sudo)
if (( $# == 3 )) && [[ "$2" == --non-interactive && "$3" == --validate ]]; then
printf x >>"$k3slrh_sudo_validate_log"
if [[ "$k3slrh_model_case" == uncached_sudo ]]; then
printf 'SUDO_PROMPT_SENTINEL\n' >&2
return 1
fi
return 0
fi
if (( $# == 15 )) && [[ "$2" == --non-interactive && "$3" == -- &&
"$4" == /usr/bin/env && "$5" == -i &&
"$6" == 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' && "$7" == LC_ALL=C &&
"$8" == /usr/bin/timeout && "$9" == --signal=TERM &&
"${10}" == --kill-after=1s && "${11}" == 30s &&
"${12}" == /usr/local/bin/k3s && "${13}" == kubectl &&
"${14}" == get && "${15}" == --raw=/readyz ]]; then
printf x >>"$k3slrh_readyz_log"
case "$k3slrh_model_case" in
readyz_extra) printf 'ok\nextra\n' ;;
readyz_missing_lf) printf 'ok' ;;
readyz_cr) printf 'ok\r\n' ;;
readyz_nul) printf 'ok\0\n' ;;
readyz_empty) : ;;
readyz_rc1) return 1 ;;
readyz_rc124) return 124 ;;
readyz_rc137) return 137 ;;
readyz_stderr) printf 'READYZ_STDERR_SENTINEL\n' >&2; printf 'ok\n' ;;
*) printf 'ok\n' ;;
esac
return 0
fi
printf 'mutation:sudo\n' >>"$k3slrh_mutation_log"
return 97
;;
/usr/bin/systemctl)
[[ "$#" == 4 && "$2" == is-active && "$3" == --quiet && "$4" == k3s ]] || return 97
[[ "$k3slrh_model_case" != k3s_inactive ]] || return 3
;;
/usr/bin/timeout)
# Direct non-root readyz is forbidden by the authority contract.
return 97
;;
/usr/bin/env)
[[ "$#" == 10 && "$2" == -i && "$3" == 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' &&
"$4" == LC_ALL=C && "$5" == /usr/bin/bash && "$6" == --noprofile &&
"$7" == --norc && "$8" == -- && "$9" == "$K3SLRH_VALIDATOR" &&
"${10}" == --expect-closed ]] || return 97
k3slrh_validator_count=$((k3slrh_validator_count + 1))
printf x >>"$k3slrh_validator_call_log"
case "$k3slrh_model_case" in
validator_cr) printf '%s\r\n' 'Recovery device: match' 'Recovery state: closed' 'Lineage: match' 'Latest bundle: not_checked' ;;
validator_nul) printf 'Recovery device: match\0Recovery state: closed\nLineage: match\nLatest bundle: not_checked\n' ;;
validator_missing_lf) printf '%s' "${K3SLRH_EXPECTED_CLOSED_RECORD%$'\n'}" ;;
validator_extra) printf '%sextra\n' "$K3SLRH_EXPECTED_CLOSED_RECORD" ;;
validator_raw_id) printf '%s%s\n' "$K3SLRH_EXPECTED_CLOSED_RECORD" "$K3SLRH_RAW_IDENTIFIER_SENTINEL" ;;
validator_rc1) return 1 ;;
validator_rc124) return 124 ;;
validator_rc137) return 137 ;;
validator_stderr) printf 'x' >&2; printf '%s' "$K3SLRH_EXPECTED_CLOSED_RECORD" ;;
*) printf '%s' "$K3SLRH_EXPECTED_CLOSED_RECORD" ;;
esac
;;
/usr/bin/dpkg-query)
[[ "$#" == 4 && "$2" == --show &&
"$3" == '--showformat=${Status}|${Version}\n' ]] || return 97
package="${!#}"
case "$package" in
keepassxc) path='2.7.6+dfsg.1-1build3'; [[ "$k3slrh_model_case" != keepass_package_drift ]] || path='0.invalid' ;;
cryptsetup-bin) path='2:2.7.0-1ubuntu4.2'; [[ "$k3slrh_model_case" != cryptsetup_package_drift ]] || path='0.invalid' ;;
sqlite3) path='3.45.1-1ubuntu2.7'; [[ "$k3slrh_model_case" != sqlite_package_drift ]] || path='0.invalid' ;;
*) return 97 ;;
esac
printf 'install ok installed|%s\n' "$path"
;;
/usr/bin/date)
[[ "$#" == 3 && "$2" == --utc && "$3" == +%Y%m%dT%H%M%SZ ]] || return 97
printf '%s\n' "$K3SLRH_EXPECTED_UTC"
;;
/usr/bin/findmnt)
[[ "$#" == 6 && "$2" == -rn && "$3" == -M && "$5" == -o && "$6" == TARGET ]] || return 97
case "$4|$k3slrh_model_case" in
"$K3SLR_OUTER_MOUNT|outer_mount_collision"|"$K3SLR_INNER_MOUNT|inner_mount_collision")
printf '%s\n' "$4"
return 0
;;
"$K3SLR_OUTER_MOUNT|"*|"$K3SLR_INNER_MOUNT|"*) return 1 ;;
*) return 97 ;;
esac
;;
/usr/sbin/losetup)
[[ "$#" == 5 && "$2" == --list && "$3" == --noheadings &&
"$4" == --output && "$5" == NAME,BACK-FILE ]] || {
printf 'mutation:losetup\n' >>"$k3slrh_mutation_log"
return 97
}
case "$k3slrh_model_case" in
main_loop_residue)
expected_backing="${K3SLR_OUTER_MOUNT}/${K3SLR_CONTAINER_RELATIVE}"
printf '/dev/loop91 %s\n' "$expected_backing"
;;
proof_loop_residue)
expected_backing="${K3SLR_OUTER_MOUNT}/${K3SLRH_PROOF_RELATIVE}"
printf '/dev/loop92 %s\n' "$expected_backing"
;;
esac
;;
/usr/sbin/dmsetup)
return 97
;;
/usr/bin/find)
if (( $# == 8 )) && [[ "$2" == "$K3SLR_OUTER_MOUNT" &&
"$3" == -mindepth && "$4" == 1 && "$5" == -maxdepth &&
"$6" == 1 && "$7" == -print && "$8" == -quit ]]; then
[[ "$k3slrh_model_case" != outer_mountpoint_not_empty ]] ||
printf '%s/unexpected\n' "$K3SLR_OUTER_MOUNT"
return 0
fi
if (( $# == 10 )) && [[ "$2" == /run && "$3" == -mindepth &&
"$4" == 1 && "$5" == -maxdepth && "$6" == 1 && "$7" == -name &&
"$8" == 'hyeonworks-k3s-recovery.*' && "$9" == -print &&
"${10}" == -quit ]]; then
[[ "$k3slrh_model_case" != tmpfs_residue ]] ||
printf '/run/hyeonworks-k3s-recovery.ABCDEFGH\n'
return 0
fi
return 97
;;
/usr/bin/test)
if (( $# == 3 )) && [[ "$2" == ! && "$3" == -L ]]; then return 97; fi
if (( $# == 4 )) && [[ "$2" == ! && "$3" == -e ]]; then
case "$4" in
"/dev/mapper/${K3SLR_MAPPING_NAME}")
[[ "$k3slrh_model_case" != main_mapping_residue ]]
return
;;
"/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}")
[[ "$k3slrh_model_case" != proof_mapping_residue ]]
return
;;
*) return 97 ;;
esac
fi
if (( $# == 3 )); then
case "$2" in
-d) [[ "$3" == "$K3SLR_OUTER_MOUNT" ]] || return 97 ;;
-f|-x|-r)
case "$3" in
"$K3SLRH_SHARED_LIBRARY"|"$K3SLRH_A1_LIBRARY"|"$K3SLRH_A1_HELPER"|"$K3SLRH_CONTRACT"|"$K3SLRH_VALIDATOR") ;;
*) k3slrh_path_is_known_binary "$3" || return 97 ;;
esac
if [[ "$2" == -f && "$3" == "$K3SLRH_VALIDATOR" ]]; then
printf x >>"$k3slrh_validator_file_log"
fi
;;
*) return 97 ;;
esac
return 0
fi
if (( $# == 4 )) && [[ "$2" == ! && "$3" == -L ]]; then
case "$4" in
"/dev/mapper/${K3SLR_MAPPING_NAME}")
[[ "$k3slrh_model_case" != main_mapping_dangling ]]
return
;;
"/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}")
[[ "$k3slrh_model_case" != proof_mapping_dangling ]]
return
;;
"$K3SLRH_VALIDATOR") printf x >>"$k3slrh_validator_symlink_log"; return 0 ;;
"$K3SLR_OUTER_MOUNT"|"$K3SLRH_SHARED_LIBRARY"|"$K3SLRH_A1_LIBRARY"|"$K3SLRH_A1_HELPER"|"$K3SLRH_CONTRACT") return 0 ;;
*) k3slrh_path_is_known_binary "$4" || return 97; return 0 ;;
esac
fi
return 97
;;
/usr/bin/stat)
path="${!#}"
if (( $# == 4 )) && [[ "$2" == "--format=%u|%a|%F" && "$3" == -- ]]; then
k3slrh_path_is_known_binary "$path" || return 97
printf '%s\n' "$path" >>"$k3slrh_binary_stat_log"
if [[ "$k3slrh_model_case" == binary_untrusted && "$path" == /usr/bin/keepassxc-cli ]]; then
printf '0|775|regular file\n'
else
printf '0|755|regular file\n'
fi
return 0
fi
if (( $# == 4 )) && [[ "$2" == "--format=%F|%u|%g|%a" && "$3" == -- ]]; then
case "$path" in
"$K3SLR_OUTER_MOUNT") printf 'directory|0|0|755\n' ;;
"$K3SLRH_SHARED_LIBRARY"|"$K3SLRH_A1_LIBRARY"|"$K3SLRH_A1_HELPER"|"$K3SLRH_CONTRACT"|"$K3SLRH_VALIDATOR")
[[ "$path" != "$K3SLRH_VALIDATOR" ]] || printf x >>"$k3slrh_validator_stat_log"
printf 'regular file|1000|1000|664\n'
;;
*) return 97 ;;
esac
return 0
fi
return 97
;;
/usr/bin/readlink)
[[ "$#" == 4 && "$2" == -e && "$3" == -- ]] || return 97
path="$4"
case "$path" in
"$K3SLR_OUTER_MOUNT"|"$K3SLRH_SHARED_LIBRARY"|"$K3SLRH_A1_LIBRARY"|"$K3SLRH_A1_HELPER"|"$K3SLRH_CONTRACT"|"$K3SLRH_VALIDATOR") printf '%s\n' "$path" ;;
*) return 97 ;;
esac
;;
/usr/bin/sha256sum)
[[ "$#" == 3 && "$2" == -- ]] || return 97
path="${!#}"
case "$path" in
"$K3SLRH_SHARED_LIBRARY") printf '%s %s\n' "$K3SLRH_EXPECTED_SHARED_SHA256" "$path" ;;
"$K3SLRH_A1_LIBRARY") printf '%s %s\n' "$K3SLRH_EXPECTED_A1_SHA256" "$path" ;;
"$K3SLRH_A1_HELPER") printf '%s %s\n' "$K3SLRH_EXPECTED_A1_HELPER_SHA256" "$path" ;;
"$K3SLRH_CONTRACT") printf '%s %s\n' "$K3SLRH_EXPECTED_CONTRACT_SHA256" "$path" ;;
"$K3SLRH_VALIDATOR")
printf x >>"$k3slrh_validator_hash_log"
hash_count="$(/usr/bin/wc -c <"$k3slrh_validator_hash_log")" || return 97
if [[ "$k3slrh_model_case" == validator_hash_pre_drift ||
( "$k3slrh_model_case" == validator_hash_post_drift && "$hash_count" -ge 2 ) ]]; then
printf '%064d %s\n' 0 "$path"
else
printf '%s %s\n' "$K3SLRH_EXPECTED_VALIDATOR_SHA256" "$path"
fi
;;
*) return 97 ;;
esac
;;
/usr/bin/mount|/usr/bin/umount|/usr/sbin/cryptsetup)
k3slrh_mutation_count=$((k3slrh_mutation_count + 1))
printf 'mutation\n' >>"$k3slrh_mutation_log"
return 97
;;
*) return 97 ;;
esac
}
_k3slr_command() { _k3slrh_command "$@"; }
_k3slr_collect_prepackage_snapshot() { printf 'legacy-collector\n' >>"$k3slrh_mutation_log"; return 97; }
_k3slr_prove_header_restore() { printf 'legacy-marker\n' >>"$k3slrh_mutation_log"; return 97; }
_k3slr_lifecycle_dispatch() { printf 'prepare-dispatch\n' >>"$k3slrh_mutation_log"; return 97; }
k3slrh_reset_model_logs() {
: >"$k3slrh_command_log"
: >"$k3slrh_mutation_log"
: >"$k3slrh_validator_call_log"
: >"$k3slrh_validator_hash_log"
: >"$k3slrh_sudo_validate_log"
: >"$k3slrh_readyz_log"
: >"$k3slrh_validator_file_log"
: >"$k3slrh_validator_symlink_log"
: >"$k3slrh_validator_stat_log"
: >"$k3slrh_binary_stat_log"
}
k3slrh_capture_call() {
: >"$k3slrh_stdout"
: >"$k3slrh_stderr"
"$@" >"$k3slrh_stdout" 2>"$k3slrh_stderr"
}
k3slrh_validator_case() {
local case_name="${1-}" expected_rc="${2-}" expected_calls="${3-}"
local expected_hashes="${4-}" rc=0 expected_physical=2
(( $# == 4 )) || return 97
k3slrh_reset_model_logs
k3slrh_model_case="$case_name"
[[ "$case_name" != validator_hash_pre_drift ]] || expected_physical=1
if k3slrh_capture_call _k3slrh_run_closed_validator; then rc=0; else rc=$?; fi
[[ "$rc" == "$expected_rc" &&
"$(/usr/bin/wc -c <"$k3slrh_validator_call_log")" == "$expected_calls" &&
"$(/usr/bin/wc -c <"$k3slrh_validator_hash_log")" == "$expected_hashes" &&
"$(/usr/bin/wc -c <"$k3slrh_validator_file_log")" == "$expected_physical" &&
"$(/usr/bin/wc -c <"$k3slrh_validator_symlink_log")" == "$expected_physical" &&
"$(/usr/bin/wc -c <"$k3slrh_validator_stat_log")" == "$expected_physical" ]] || return 1
[[ ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" ]] || return 1
! /usr/bin/grep -aFq -- "$K3SLRH_RAW_IDENTIFIER_SENTINEL" "$k3slrh_stdout" "$k3slrh_stderr"
}
k3slrh_health_case() {
local case_name="${1-}" expected_rc="${2-}" expected_readyz="${3-}" rc=0
(( $# == 3 )) || return 97
k3slrh_reset_model_logs
k3slrh_model_case="$case_name"
if k3slrh_capture_call _k3slrh_k3s_health; then rc=0; else rc=$?; fi
[[ "$rc" == "$expected_rc" && ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" &&
"$(/usr/bin/wc -c <"$k3slrh_readyz_log")" == "$expected_readyz" ]]
}
k3slrh_capability_case() {
local case_name="${1-}" expected_rc="${2-}" rc=0
k3slrh_reset_model_logs
k3slrh_model_case="$case_name"
if k3slrh_capture_call _k3slrh_pre_mount_capability; then rc=0; else rc=$?; fi
[[ "$rc" == "$expected_rc" ]] || return 1
if [[ "$expected_rc" == 0 ]]; then
[[ "$(<"$k3slrh_stdout")" == 'Header proof capability: pass' && ! -s "$k3slrh_stderr" ]] || return 1
else
[[ ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" ]] || return 1
fi
[[ ! -s "$k3slrh_mutation_log" ]] || return 1
if [[ "$case_name" == uncached_sudo ]]; then
[[ "$(/usr/bin/wc -c <"$k3slrh_sudo_validate_log")" == 1 &&
! -s "$k3slrh_readyz_log" && ! -s "$k3slrh_validator_call_log" &&
! -s "$k3slrh_validator_hash_log" ]]
fi
}
k3slrh_command_model_is_closed() {
local rc=0 k3slrh_bad_command
local -a k3slrh_bad_argv=()
k3slrh_reset_model_logs
for k3slrh_bad_command in \
'/usr/bin/timeout --signal=TERM 30s /usr/local/bin/k3s kubectl get --raw=/readyz' \
'/usr/bin/findmnt --target /fixture' \
'/usr/sbin/losetup --find --show /fixture' \
'/usr/bin/stat --format=%F -- /dev/loop0' \
'/usr/bin/test -e /dev/mapper/k3s-recovery'; do
read -r -a k3slrh_bad_argv <<<"$k3slrh_bad_command"
if _k3slrh_command "${k3slrh_bad_argv[@]}" >/dev/null 2>&1; then rc=0; else rc=$?; fi
[[ "$rc" == 97 ]] || return 1
done
[[ -s "$k3slrh_mutation_log" ]]
}
k3slrh_invalid_main() {
local rc=0
: >"$k3slrh_stdout"
: >"$k3slrh_stderr"
if _k3slrh_header_proof_main "$@" >"$k3slrh_stdout" 2>"$k3slrh_stderr"; then
rc=0
else
rc=$?
fi
! /usr/bin/grep -aFq -- "$K3SLRH_RAW_IDENTIFIER_SENTINEL" \
"$k3slrh_stdout" "$k3slrh_stderr" || return 97
return "$rc"
}
k3slrh_execute_without_tty() (
local rc=0
_k3slrh_tty_capability() { return 1; }
_k3slr_require_execute_tty() { return 1; }
: >"$k3slrh_stdout"
: >"$k3slrh_stderr"
if _k3slrh_header_proof_main --execute >"$k3slrh_stdout" 2>"$k3slrh_stderr"; then
rc=0
else
rc=$?
fi
[[ ! -s "$k3slrh_stdout" && ! -s "$k3slrh_stderr" ]] || return 97
return "$rc"
)
k3slrh_dry_run_closed() {
local rc=0 validator_calls validator_hashes sudo_validates readyz_calls
local validator_files validator_symlinks validator_stats binary_count k3slrh_required_binary
k3slrh_model_case=valid
k3slrh_validator_count=0
k3slrh_mutation_count=0
k3slrh_reset_model_logs
if k3slrh_capture_call _k3slrh_header_proof_main; then rc=0; else rc=$?; fi
validator_calls="$(/usr/bin/wc -c <"$k3slrh_validator_call_log")" || return 1
validator_hashes="$(/usr/bin/wc -c <"$k3slrh_validator_hash_log")" || return 1
sudo_validates="$(/usr/bin/wc -c <"$k3slrh_sudo_validate_log")" || return 1
readyz_calls="$(/usr/bin/wc -c <"$k3slrh_readyz_log")" || return 1
validator_files="$(/usr/bin/wc -c <"$k3slrh_validator_file_log")" || return 1
validator_symlinks="$(/usr/bin/wc -c <"$k3slrh_validator_symlink_log")" || return 1
validator_stats="$(/usr/bin/wc -c <"$k3slrh_validator_stat_log")" || return 1
for k3slrh_required_binary in /usr/bin/keepassxc-cli /usr/sbin/cryptsetup /usr/bin/sqlite3; do
binary_count="$(/usr/bin/grep -Fxc -- "$k3slrh_required_binary" "$k3slrh_binary_stat_log")" || return 1
[[ "$binary_count" == 1 ]] || return 1
done
[[ "$rc" == 0 && "$(<"$k3slrh_stdout")"$'\n' == "$K3SLRH_EXPECTED_DRY_RUN" &&
! -s "$k3slrh_stderr" && ! -s "$k3slrh_mutation_log" &&
"$validator_calls" == 1 && "$validator_hashes" == 2 &&
"$validator_files" == 2 && "$validator_symlinks" == 2 && "$validator_stats" == 2 &&
"$sudo_validates" == 1 && "$readyz_calls" == 1 &&
"$k3slrh_mutation_count" == 0 ]]
}
k3slrh_assert_eq dry-run "$(k3slrh_run_parse)" 'no argument must select dry-run'
k3slrh_assert_eq execute "$(k3slrh_run_parse --execute)" 'one execute must be accepted'
k3slrh_assert_rc 1 'duplicate parser execute must fail safely' k3slrh_run_parse --execute --execute
k3slrh_assert_rc 1 'caller parser path must fail safely' k3slrh_run_parse --container /fixture
k3slrh_assert_rc 2 'duplicate main execute must be usage rc2' k3slrh_invalid_main --execute --execute
k3slrh_assert_rc 2 'caller main path must be usage rc2' k3slrh_invalid_main --container /fixture
k3slrh_assert_succeeds 'actual clean wrapper composition and root/xtrace guards must pass' \
k3slrh_wrapper_clean_composition
k3slrh_assert_succeeds 'actual wrapper must reject inherited route and constants' \
k3slrh_wrapper_rejects_inherited_route
k3slrh_assert_rc 1 'execute without TTY must be safety rc1' k3slrh_execute_without_tty
k3slrh_assert_rc 1 'malformed timestamp must be safety rc1' k3slrh_run_timestamp malformed
k3slrh_assert_succeeds 'valid target names must derive exactly once' k3slrh_targets_are_exact
k3slrh_assert_rc 1 'same-second target reservation must be safety rc1' \
k3slrh_run_targets_same_second_collision
k3slrh_assert_rc 1 'pre/post timestamp drift must be safety rc1' \
k3slrh_run_targets_pre_post_timestamp_drift
k3slrh_assert_succeeds 'closed external command model must reject unknown argv' \
k3slrh_command_model_is_closed
for k3slrh_bad_health in k3s_inactive readyz_extra readyz_missing_lf readyz_cr \
readyz_nul readyz_empty readyz_rc1 readyz_rc124 readyz_rc137 readyz_stderr; do
if [[ "$k3slrh_bad_health" == k3s_inactive ]]; then k3slrh_expected_readyz=0; else k3slrh_expected_readyz=1; fi
k3slrh_assert_succeeds "K3s health must reject ${k3slrh_bad_health}" \
k3slrh_health_case "$k3slrh_bad_health" 1 "$k3slrh_expected_readyz"
done
unset k3slrh_bad_health k3slrh_expected_readyz
k3slrh_assert_succeeds 'exact cached-sudo readyz must pass silently' \
k3slrh_health_case valid 0 1
for k3slrh_bad_validator in validator_cr validator_nul validator_missing_lf \
validator_extra validator_raw_id validator_rc1 validator_rc124 validator_rc137 \
validator_stderr; do
k3slrh_assert_succeeds "closed validator must reject ${k3slrh_bad_validator}" \
k3slrh_validator_case "$k3slrh_bad_validator" 1 1 2
done
unset k3slrh_bad_validator
k3slrh_assert_succeeds 'validator pre-call hash drift must stop before dispatch' \
k3slrh_validator_case validator_hash_pre_drift 1 0 1
k3slrh_assert_succeeds 'validator post-call hash drift must fail closed' \
k3slrh_validator_case validator_hash_post_drift 1 1 2
k3slrh_assert_succeeds 'exact pinned closed validator record must pass' \
k3slrh_validator_case valid 0 1 2
for k3slrh_bad_capability in uncached_sudo k3s_inactive readyz_extra \
keepass_package_drift cryptsetup_package_drift sqlite_package_drift \
binary_untrusted \
outer_mount_collision inner_mount_collision outer_mountpoint_not_empty tmpfs_residue \
main_mapping_residue proof_mapping_residue main_mapping_dangling proof_mapping_dangling \
main_loop_residue proof_loop_residue; do
k3slrh_assert_succeeds "capability must reject ${k3slrh_bad_capability}" \
k3slrh_capability_case "$k3slrh_bad_capability" 1
done
unset k3slrh_bad_capability
k3slrh_assert_succeeds 'closed dry-run must pass without mutation' k3slrh_dry_run_closed
printf 'PASS: header-proof entrypoint and closed capability fixtures\n'