Files
platform-core/scripts/validate/test-k3s-secret-encryption-restore-evidence.sh

706 lines
30 KiB
Bash
Executable File

#!/usr/bin/env bash
# k3s Secret 복구 증거의 parser, 세 mode와 권한 경계를 검증한다.
# 이 파일은 source-safe 함수만 호출하며 live host나 Kubernetes API를 건드리지 않는다.
set -Eeuo pipefail
readonly TEST_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
readonly BOOTSTRAP_PATH="${TEST_ROOT}/scripts/bootstrap/apply-k3s-secret-encryption.sh"
readonly RESTORE_VALIDATOR_PATH="${TEST_ROOT}/scripts/validate/k3s-secret-encryption-restore-evidence.sh"
fail() {
printf 'RESTORE EVIDENCE TEST FAILURE: %s\n' "$*" >&2
exit 1
}
assert_eq() {
local expected="$1" actual="$2" description="$3"
[[ "$actual" == "$expected" ]] || fail "$description"
}
# 잘못된 legacy field를 쓰는 producer 변경을 잡는다. 기대값은 Task 5 계약에서
# 손으로 적었으며 production parser나 formatter로 만들지 않는다.
probe_canonical_bundle_metadata_producer() (
# shellcheck source=/dev/null
source "$BOOTSTRAP_PATH"
declare -F _kseb_render_bundle_metadata >/dev/null || return 1
local actual
actual="$(_kseb_render_bundle_metadata \
'01234567-89ab-4cde-8fab-0123456789ab' post embedded-etcd \
'2026-08-01T00:00:00Z' 7 'v1.36.2+k3s1')" || return 1
assert_eq $'schema=platform-k3s-bundle-v1\nbundle_id=01234567-89ab-4cde-8fab-0123456789ab\nphase=post\nk3s_version=v1.36.2+k3s1\ndatastore=embedded-etcd\ncreated_at_utc=2026-08-01T00:00:00Z\nsecret_count=7' \
"$actual" 'canonical bundle metadata fields/order'
)
probe_canonical_bundle_metadata_producer || fail 'canonical bundle metadata producer'
# 아래 production 변경이 깨질 때 잡는 결함은 allowlist parser 우회, mode 간
# 검증 누락, sudo 전 입력 검증 누락과 기존 evidence overwrite다.
[[ -f "$RESTORE_VALIDATOR_PATH" ]] || fail 'restore evidence validator is absent'
# shellcheck source=/dev/null
source "$RESTORE_VALIDATOR_PATH"
fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/k3s-restore-evidence-test.XXXXXX")"
cleanup() {
case "$fixture_root" in
/tmp/k3s-restore-evidence-test.*|"${TMPDIR:-/tmp}"/k3s-restore-evidence-test.*)
rm -rf -- "$fixture_root"
;;
*) fail 'unexpected fixture root' ;;
esac
}
trap cleanup EXIT
readonly FIXTURE_BUNDLE_ID='01234567-89ab-4cde-8fab-0123456789ab'
readonly FIXTURE_OTHER_ID='11234567-89ab-4cde-8fab-0123456789ab'
readonly FIXTURE_LOCAL_NODE_UID='aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
readonly FIXTURE_OTHER_NODE_UID='11111111-2222-4333-8444-555555555555'
readonly FIXTURE_TESTED_AT='2026-08-01T00:00:00Z'
readonly FIXTURE_RECORDED_AT='2026-08-01T01:00:00Z'
# Fix round 1 / finding 4: invalid compact timestamp는 production transaction의
# directory/backup/marker mutation 전에 거부해야 한다.
probe_invalid_timestamp_precedes_transaction_mutation() (
local parent="${fixture_root}/invalid-stamp" root data fake_k3s command_log rc=0
parent="$(mktemp -d "${fixture_root}/invalid-stamp.XXXXXX")"
root="$parent/recovery"; data="$parent/data"; fake_k3s="$parent/k3s"
command_log="$parent/commands.log"
mkdir -p "$root" "$data/server/cred"
chmod 0700 "$root"
printf 'fixture\n' >"$data/server/token"
printf '{}\n' >"$data/server/cred/encryption-config.json"
: >"$command_log"
printf '%s\n' '#!/usr/bin/env bash' \
'printf "called\n" >>"${KSEB_INVALID_STAMP_LOG:?}"' \
'exit 1' >"$fake_k3s"
chmod 0700 "$fake_k3s"
# shellcheck source=/dev/null
source "$BOOTSTRAP_PATH"
KSEB_BACKUP_ROOT_IDENTITY="$(stat --format='%d:%i' -- "$root")"
_kseb_run_privileged_shell() { /usr/bin/bash "$@"; }
_kseb_transaction_data_dir() { printf '%s\n' "$data"; }
_kseb_transaction_config_dir() { printf '%s\n' "$parent/no-config"; }
_kseb_transaction_systemd_paths() { :; }
_kseb_transaction_k3s_binary() { printf '%s\n' "$fake_k3s"; }
_kseb_transaction_post_marker() { printf '%s\n' "$parent/post-marker"; }
_kseb_transaction_restore_evidence() { printf '%s\n' "$parent/restore-evidence"; }
export KSEB_INVALID_STAMP_LOG="$command_log"
_kseb_pinned_phase_transaction "$root" bundle/pre embedded-etcd \
"$FIXTURE_BUNDLE_ID" pre 20261301T000000Z 7 v1.36.2+k3s1 \
>/dev/null 2>&1 || rc=$?
[[ "$rc" -ne 0 && ! -e "$root/bundle" && ! -s "$command_log" ]]
)
write_metadata() {
local path="$1" phase="${2:-post}" bundle_id="${3:-$FIXTURE_BUNDLE_ID}"
local version="${4:-v1.36.2+k3s1}" datastore="${5:-embedded-etcd}"
local created="${6:-2026-07-31T23:00:00Z}" count="${7:-7}"
printf 'schema=platform-k3s-bundle-v1\nbundle_id=%s\nphase=%s\nk3s_version=%s\ndatastore=%s\ncreated_at_utc=%s\nsecret_count=%s\n' \
"$bundle_id" "$phase" "$version" "$datastore" "$created" "$count" >"$path"
chmod 0600 "$path"
}
write_result() {
local path="$1" bundle_id="${2:-$FIXTURE_BUNDLE_ID}" version="${3:-v1.36.2+k3s1}"
local datastore="${4:-embedded-etcd}" node="${5:-pass}" isolation="${6:-pass}"
local integrity="${7:-match}" count="${8:-pass}" tested="${9:-$FIXTURE_TESTED_AT}"
printf 'schema=platform-k3s-restore-result-v1\nbundle_id=%s\nk3s_version=%s\ndatastore=%s\nstatus_class=enabled_stable\nrotation_stage=reencrypt_finished\nhashes=match\nlocal_integrity=%s\napi_ready=pass\nnode_ready=%s\nisolation=%s\nsecret_count_match=%s\ntested_at_utc=%s\n' \
"$bundle_id" "$version" "$datastore" "$integrity" "$node" "$isolation" "$count" "$tested" >"$path"
chmod 0600 "$path"
}
write_evidence() {
local path="$1" bundle_id="${2:-$FIXTURE_BUNDLE_ID}" version="${3:-v1.36.2+k3s1}"
local datastore="${4:-embedded-etcd}" integrity="${5:-match}" destroyed="${6:-confirmed}"
local tested="${7:-$FIXTURE_TESTED_AT}" recorded="${8:-$FIXTURE_RECORDED_AT}"
printf 'schema=platform-k3s-restore-evidence-v1\nbundle_id=%s\nk3s_version=%s\ndatastore=%s\nrotation_stage=reencrypt_finished\nlocal_integrity=%s\nnode_ready=pass\nisolation=pass\ntested_at_utc=%s\nrecorded_at_utc=%s\ndestroyed=%s\n' \
"$bundle_id" "$version" "$datastore" "$integrity" "$tested" "$recorded" "$destroyed" >"$path"
chmod 0600 "$path"
}
assert_fails() {
if "$@" >/dev/null 2>&1; then fail "expected failure: $*"; fi
}
assert_succeeds() {
"$@" || fail "expected success: $*"
}
assert_succeeds probe_invalid_timestamp_precedes_transaction_mutation
metadata="${fixture_root}/bundle.env"
result="${fixture_root}/result.env"
evidence_fixture="${fixture_root}/evidence.env"
write_metadata "$metadata"
write_result "$result"
write_evidence "$evidence_fixture"
probe_parse() (
local kind="$1" path="$2"
declare -A parsed=()
_ksre_parse_external_file "$kind" "$path" parsed
)
assert_succeeds probe_parse bundle "$metadata"
assert_succeeds probe_parse result "$result"
assert_succeeds probe_parse evidence "$evidence_fixture"
# Fix round 1 / finding 1: local restore validator와 current-user API가
# 서로 다른 cluster를 보면 production identity gate가 실패해야 한다.
matching_nodes='{"items":[{"metadata":{"name":"isolated-ready","uid":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"},"status":{"conditions":[{"type":"Ready","status":"True"}]}}]}'
mismatched_nodes='{"items":[{"metadata":{"name":"other-ready","uid":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"},"status":{"conditions":[{"type":"Ready","status":"True"}]}}]}'
multiple_ready_nodes='{"items":[{"metadata":{"name":"isolated-ready","uid":"aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"},"status":{"conditions":[{"type":"Ready","status":"True"}]}},{"metadata":{"name":"other-ready","uid":"11111111-2222-4333-8444-555555555555"},"status":{"conditions":[{"type":"Ready","status":"True"}]}}]}'
assert_succeeds _ksre_require_same_ready_node isolated-ready "$FIXTURE_LOCAL_NODE_UID" "$matching_nodes"
assert_fails _ksre_require_same_ready_node isolated-ready "$FIXTURE_LOCAL_NODE_UID" "$mismatched_nodes"
assert_fails _ksre_require_same_ready_node isolated-ready "$FIXTURE_LOCAL_NODE_UID" "$multiple_ready_nodes"
# Fix round 2 / finding 1: 같은 UID가 복제된 다른 API도 허용하지 않도록
# current-user context의 effective endpoint는 exact loopback이며 proxy가 없어야 한다.
assert_succeeds _ksre_validate_current_connection 'https://127.0.0.1:6443|'
assert_fails _ksre_validate_current_connection 'https://192.0.2.10:6443|'
assert_fails _ksre_validate_current_connection 'https://localhost:6443|'
assert_fails _ksre_validate_current_connection 'https://127.0.0.1:6443/|'
assert_fails _ksre_validate_current_connection 'https://127.0.0.1:6443|http://127.0.0.1:8080'
probe_live_inventory_cluster_binding() (
local api_node="$1" api_node_uid="$2" output
declare -F _ksre_current_connection >/dev/null || return 1
declare -F _ksre_require_same_connection >/dev/null || return 1
declare -F _ksre_run_restore_status_validator >/dev/null || return 1
declare -F _ksre_current_user_api_ready >/dev/null || return 1
declare -F _ksre_current_user_nodes_json >/dev/null || return 1
declare -F _ksre_current_user_secret_count >/dev/null || return 1
_ksre_run_restore_status_validator() {
local handoff="$1"
printf 'version=v1.36.2+k3s1\nserver_count=1\nnode_name=isolated-ready\nnode_uid=%s\nnode_ready=Ready\ndatastore=embedded-etcd\nencryption=Enabled\nrotation=reencrypt_finished\nserver_hashes=match\nintegrity=match\napi_readyz=pass\n' \
"$FIXTURE_LOCAL_NODE_UID" \
>"$handoff/inventory.env"
chmod 0600 "$handoff/inventory.env"
}
_ksre_current_connection() {
[[ "${1:-}" == fixture-context ]] || return 1
printf 'https://127.0.0.1:6443|\n'
}
_ksre_current_user_api_ready() {
[[ "${1:-}" == fixture-context && "${2:-}" == https://127.0.0.1:6443 ]]
}
_ksre_current_user_nodes_json() {
[[ "${1:-}" == fixture-context && "${2:-}" == https://127.0.0.1:6443 ]] || return 1
printf '{"items":[{"metadata":{"name":"%s","uid":"%s"},"status":{"conditions":[{"type":"Ready","status":"True"}]}}]}\n' \
"$api_node" "$api_node_uid"
}
_ksre_current_user_secret_count() {
[[ "${1:-}" == fixture-context && "${2:-}" == https://127.0.0.1:6443 ]] || return 1
printf '7\n'
}
output="$(_ksre_live_restore_inventory fixture-context 'https://127.0.0.1:6443|')" || return 1
[[ "$output" == 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|match|pass|pass|7' ]]
)
assert_succeeds probe_live_inventory_cluster_binding isolated-ready "$FIXTURE_LOCAL_NODE_UID"
assert_fails probe_live_inventory_cluster_binding isolated-ready "$FIXTURE_OTHER_NODE_UID"
assert_fails probe_live_inventory_cluster_binding other-ready "$FIXTURE_LOCAL_NODE_UID"
# Fix round 1 / finding 2: Bash read가 버리는 NUL도 raw byte 단계에서 거부한다.
nul_metadata="${fixture_root}/metadata-nul.env"
{
printf 'schema=platform-k3s-bundle-v1\n'
printf 'bundle_id=%s\n' "$FIXTURE_BUNDLE_ID"
printf 'phase=po\0st\n'
printf 'k3s_version=v1.36.2+k3s1\n'
printf 'datastore=embedded-etcd\n'
printf 'created_at_utc=2026-07-31T23:00:00Z\n'
printf 'secret_count=7\n'
} >"$nul_metadata"
chmod 0600 "$nul_metadata"
assert_fails probe_parse bundle "$nul_metadata"
# Fix round 1 / finding 3: pathname이 아니라 실제로 열린 FD identity를 검사한다.
probe_pinned_external_fd_survives_path_swap() (
local input="${fixture_root}/toctou-valid.env" moved="${fixture_root}/toctou-valid-opened.env"
local input_fd
write_metadata "$input"
exec {input_fd}<"$input"
/bin/mv -- "$input" "$moved"
printf 'unsafe replacement\n' >"$input"
chmod 0644 "$input"
declare -A parsed=()
_ksre_parse_external_fd bundle "$input_fd" parsed || return 1
exec {input_fd}<&-
[[ "${parsed[bundle_id]}" == "$FIXTURE_BUNDLE_ID" ]]
)
probe_unsafe_opened_fd_cannot_borrow_safe_path() (
local input="${fixture_root}/toctou-unsafe.env" moved="${fixture_root}/toctou-unsafe-opened.env"
local input_fd
write_metadata "$input"
chmod 0644 "$input"
exec {input_fd}<"$input"
/bin/mv -- "$input" "$moved"
write_metadata "$input"
declare -A parsed=()
if _ksre_parse_external_fd bundle "$input_fd" parsed; then return 1; fi
exec {input_fd}<&-
)
assert_succeeds probe_pinned_external_fd_survives_path_swap
assert_succeeds probe_unsafe_opened_fd_cannot_borrow_safe_path
# Fix round 2 / finding 2: production final-component open의 정확한 순간을
# test-only interposer로 멈춘다. precheck 뒤 symlink로 바꾸고 open 뒤 같은
# target inode의 hard link로 되돌려도 parser가 반드시 실패해야 한다.
race_interposer_source="${fixture_root}/nofollow-race.c"
race_interposer="${fixture_root}/nofollow-race.so"
cat >"$race_interposer_source" <<'RACE_INTERPOSER'
#define _GNU_SOURCE
#include <dlfcn.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int triggered;
static int (*real_open_fn)(const char *, int, ...);
static int (*real_open64_fn)(const char *, int, ...);
static int (*real_openat_fn)(int, const char *, int, ...);
static int (*real_openat64_fn)(int, const char *, int, ...);
static void resolve_symbols(void) {
if (!real_open_fn) real_open_fn = dlsym(RTLD_NEXT, "open");
if (!real_open64_fn) real_open64_fn = dlsym(RTLD_NEXT, "open64");
if (!real_openat_fn) real_openat_fn = dlsym(RTLD_NEXT, "openat");
if (!real_openat64_fn) real_openat64_fn = dlsym(RTLD_NEXT, "openat64");
}
static void marker_path(char *buffer, size_t size, const char *name) {
const char *directory = getenv("KSRE_RACE_SYNC");
if (!directory || snprintf(buffer, size, "%s/%s", directory, name) >= (int)size) _exit(125);
}
static void touch_marker(const char *name) {
char path[PATH_MAX];
int fd;
marker_path(path, sizeof(path), name);
resolve_symbols();
fd = real_open_fn(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) _exit(125);
dprintf(fd, "%ld\n", (long)getpid());
close(fd);
}
static void wait_for_marker(const char *name) {
char path[PATH_MAX];
marker_path(path, sizeof(path), name);
while (access(path, F_OK) != 0) usleep(1000);
}
static int should_race(const char *path) {
const char *target = getenv("KSRE_RACE_PATH");
if (!triggered && target && path && strcmp(path, target) == 0) {
triggered = 1;
touch_marker("entered-open");
wait_for_marker("continue-open");
return 1;
}
return 0;
}
static void after_open(void) {
touch_marker("opened");
wait_for_marker("continue-postcheck");
}
int open(const char *path, int flags, ...) {
mode_t mode = 0;
int raced, result;
va_list arguments;
resolve_symbols();
if ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE)) {
va_start(arguments, flags); mode = va_arg(arguments, mode_t); va_end(arguments);
}
raced = should_race(path);
result = ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE))
? real_open_fn(path, flags, mode) : real_open_fn(path, flags);
if (raced) after_open();
return result;
}
int open64(const char *path, int flags, ...) {
mode_t mode = 0;
int raced, result;
va_list arguments;
resolve_symbols();
if ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE)) {
va_start(arguments, flags); mode = va_arg(arguments, mode_t); va_end(arguments);
}
raced = should_race(path);
if (!real_open64_fn) real_open64_fn = real_open_fn;
result = ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE))
? real_open64_fn(path, flags, mode) : real_open64_fn(path, flags);
if (raced) after_open();
return result;
}
int openat(int directory_fd, const char *path, int flags, ...) {
mode_t mode = 0;
int raced, result;
va_list arguments;
resolve_symbols();
if ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE)) {
va_start(arguments, flags); mode = va_arg(arguments, mode_t); va_end(arguments);
}
raced = should_race(path);
result = ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE))
? real_openat_fn(directory_fd, path, flags, mode) : real_openat_fn(directory_fd, path, flags);
if (raced) after_open();
return result;
}
int openat64(int directory_fd, const char *path, int flags, ...) {
mode_t mode = 0;
int raced, result;
va_list arguments;
resolve_symbols();
if ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE)) {
va_start(arguments, flags); mode = va_arg(arguments, mode_t); va_end(arguments);
}
raced = should_race(path);
if (!real_openat64_fn) real_openat64_fn = real_openat_fn;
result = ((flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE))
? real_openat64_fn(directory_fd, path, flags, mode) : real_openat64_fn(directory_fd, path, flags);
if (raced) after_open();
return result;
}
RACE_INTERPOSER
/usr/bin/cc -shared -fPIC -O2 -o "$race_interposer" "$race_interposer_source" -ldl ||
fail 'compile no-follow race interposer'
wait_for_race_marker() {
local marker="$1" process_id="$2" attempt
for ((attempt = 0; attempt < 5000; attempt++)); do
[[ -e "$marker" ]] && return 0
/bin/kill -0 "$process_id" 2>/dev/null || return 1
/usr/bin/sleep 0.001
done
return 1
}
probe_open_instant_symlink_replacement_rejected() (
local target="${fixture_root}/race-target.env" input="${fixture_root}/race-input.env"
local original="${fixture_root}/race-input-original.env" sync="${fixture_root}/race-sync"
local process_id parse_rc=0
write_metadata "$target"
write_metadata "$input"
mkdir "$sync"
/usr/bin/env KSRE_RACE_PATH="$input" KSRE_RACE_SYNC="$sync" LD_PRELOAD="$race_interposer" \
/usr/bin/timeout --signal=TERM --kill-after=1s 10s \
/usr/bin/bash -c '
set -Eeuo pipefail
source "$1"
declare -A parsed=()
_ksre_parse_external_file bundle "$2" parsed
' bash "$RESTORE_VALIDATOR_PATH" "$input" &
process_id=$!
if ! wait_for_race_marker "$sync/entered-open" "$process_id"; then
: >"$sync/continue-open"; : >"$sync/continue-postcheck"
wait "$process_id" 2>/dev/null || :
return 1
fi
/bin/mv -- "$input" "$original"
/bin/ln -s -- "$target" "$input"
: >"$sync/continue-open"
if ! wait_for_race_marker "$sync/opened" "$process_id"; then
: >"$sync/continue-postcheck"
wait "$process_id" 2>/dev/null || :
return 1
fi
/bin/rm -- "$input"
/bin/ln -- "$target" "$input"
: >"$sync/continue-postcheck"
if wait "$process_id"; then parse_rc=0; else parse_rc=$?; fi
[[ "$parse_rc" -ne 0 ]]
)
assert_succeeds probe_open_instant_symlink_replacement_rejected
# Fix round 3: production broker가 readiness 전에 멈춰도 parent 자체 상한 안에
# nonzero로 끝나고 partial success output과 살아 있는 broker를 남기면 안 된다.
probe_broker_readiness_delay_is_bounded() (
local input="${fixture_root}/broker-delay.env" sync="${fixture_root}/broker-delay-sync"
local partial_output="${fixture_root}/broker-delay.out" broker_process='' parser_rc=0 attempt
write_metadata "$input"
mkdir "$sync"
/usr/bin/env KSRE_RACE_PATH="$input" KSRE_RACE_SYNC="$sync" LD_PRELOAD="$race_interposer" \
/usr/bin/timeout --signal=TERM --kill-after=0.5s 4s \
/usr/bin/bash -c '
set -Eeuo pipefail
source "$1"
declare -A parsed=()
if _ksre_parse_external_file bundle "$2" parsed; then
printf "unexpected-success\n"
exit 0
fi
exit 23
' bash "$RESTORE_VALIDATOR_PATH" "$input" >"$partial_output" 2>/dev/null || parser_rc=$?
[[ -s "$sync/entered-open" ]] || return 1
broker_process="$(<"$sync/entered-open")"
[[ "$broker_process" =~ ^[0-9]+$ ]] || return 1
: >"$sync/continue-open"
: >"$sync/continue-postcheck"
for ((attempt = 0; attempt < 1000; attempt++)); do
/bin/kill -0 "$broker_process" 2>/dev/null || break
/usr/bin/sleep 0.001
done
if [[ "$parser_rc" == 124 || "$parser_rc" == 137 ]]; then
printf 'broker readiness required the outer watchdog\n' >&2
return 1
fi
[[ "$parser_rc" -ne 0 && ! -s "$partial_output" ]]
! /bin/kill -0 "$broker_process" 2>/dev/null
)
assert_succeeds probe_broker_readiness_delay_is_bounded
# Fix round 2 pre-review concern: writer가 없는 FIFO는 regular-file 검사 전에
# block하지 않고 즉시 거부되어야 한다. timeout은 실패가 아니라 RED 증거다.
probe_fifo_rejected_without_blocking() (
local fifo="${fixture_root}/external-input.fifo" parser_rc=0
/usr/bin/mkfifo -- "$fifo"
/usr/bin/chmod 0600 "$fifo"
/usr/bin/timeout --signal=TERM --kill-after=0.25s 0.25s \
/usr/bin/bash -c '
set -Eeuo pipefail
source "$1"
declare -A parsed=()
_ksre_parse_external_file bundle "$2" parsed
' bash "$RESTORE_VALIDATOR_PATH" "$fifo" >/dev/null 2>&1 || parser_rc=$?
if [[ "$parser_rc" == 124 || "$parser_rc" == 137 ]]; then
/usr/bin/timeout --signal=TERM --kill-after=0.25s 1s \
/usr/bin/bash -c 'printf x >"$1"' bash "$fifo" >/dev/null 2>&1 || :
printf 'FIFO parser blocked before regular-file rejection\n' >&2
return 1
fi
[[ "$parser_rc" -ne 0 ]]
)
assert_succeeds probe_fifo_rejected_without_blocking
secret_stream="${fixture_root}/secret-stream.json"
secret_trace="${fixture_root}/secret-stream.trace"
printf '{"items":[{"data":{"value":"fixture-sensitive-payload"}}]}\n' >"$secret_stream"
probe_stream_secret_count() (
exec 9>"$secret_trace"
BASH_XTRACEFD=9
set -x
local count
count="$(_ksre_secret_count_from_stream <"$secret_stream")"
set +x
[[ "$count" == 1 ]]
)
assert_succeeds probe_stream_secret_count
[[ "$(<"$secret_trace")" != *fixture-sensitive-payload* ]] || fail 'Secret payload reached xtrace'
# Parser/security matrix. 각 fixture는 parser의 실제 branch를 지나며 privileged
# helper가 한 번이라도 호출되면 별도 marker를 남기도록 mode fixture에서 확인한다.
for attack in command_substitution backtick duplicate unknown empty control malformed trailing; do
candidate="${fixture_root}/metadata-${attack}.env"
write_metadata "$candidate"
case "$attack" in
command_substitution) /usr/bin/sed -i '$c\secret_count=$(id)' "$candidate" ;;
backtick) /usr/bin/sed -i '$c\secret_count=`id`' "$candidate" ;;
duplicate) printf 'phase=post\n' >>"$candidate" ;;
unknown) printf 'credential=forbidden\n' >>"$candidate" ;;
empty) /usr/bin/sed -i 's/^phase=.*/phase=/' "$candidate" ;;
control) /usr/bin/sed -i 's/^phase=.*/phase=post\r/' "$candidate" ;;
malformed) printf 'not-an-assignment\n' >>"$candidate" ;;
trailing) printf 'trailing-data\n' >>"$candidate" ;;
esac
assert_fails probe_parse bundle "$candidate"
done
empty_file="${fixture_root}/empty.env"; : >"$empty_file"; chmod 0600 "$empty_file"
assert_fails probe_parse bundle "$empty_file"
mode_file="${fixture_root}/mode.env"; write_metadata "$mode_file"; chmod 0644 "$mode_file"
assert_fails probe_parse bundle "$mode_file"
symlink_file="${fixture_root}/link.env"; ln -s -- "$metadata" "$symlink_file"
assert_fails probe_parse bundle "$symlink_file"
probe_mode_rejects_before_privilege() (
local path="$1" marker="${fixture_root}/unexpected-privilege"
_ksre_current_context() { printf 'default\n'; }
_ksre_read_authoritative_bundle() { : >"$marker"; return 1; }
_ksre_install_evidence() { : >"$marker"; return 1; }
_ksre_record "$path" "$result" <<<"DESTROYED default" >/dev/null 2>&1 && return 1
[[ ! -e "$marker" ]]
)
for attack in command_substitution backtick duplicate unknown control malformed trailing; do
assert_succeeds probe_mode_rejects_before_privilege "${fixture_root}/metadata-${attack}.env"
done
assert_succeeds probe_mode_rejects_before_privilege "$mode_file"
assert_succeeds probe_mode_rejects_before_privilege "$symlink_file"
mode_events="${fixture_root}/mode-events.log"
installed_evidence="${fixture_root}/installed-evidence.env"
install_mode_fakes() {
: >"$mode_events"
rm -f -- "$installed_evidence"
active_evidence_file="$evidence_fixture"
_ksre_current_context() { printf 'context\n' >>"$mode_events"; printf 'default\n'; }
_ksre_current_connection() { printf 'connection\n' >>"$mode_events"; printf 'https://127.0.0.1:6443|\n'; }
_ksre_require_same_context() { printf 'context-recheck\n' >>"$mode_events"; }
_ksre_now_rfc3339() { printf '%s\n' "$FIXTURE_RECORDED_AT"; }
_ksre_now_epoch() { printf '1785546000\n'; }
_ksre_live_restore_inventory() {
printf 'live\n' >>"$mode_events"
printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|match|pass|pass|7\n'
}
_ksre_isolation_attested() { printf 'isolation\n' >>"$mode_events"; }
_ksre_read_authoritative_bundle() {
local destination="$1"
printf 'authority\n' >>"$mode_events"
_ksre_parse_trusted_content bundle "$(<"$metadata")" "$destination"
}
_ksre_read_installed_evidence() {
local destination="$1"
printf 'evidence\n' >>"$mode_events"
_ksre_parse_trusted_content evidence "$(<"$active_evidence_file")" "$destination"
}
_ksre_evidence_target_absent() { printf 'target-check\n' >>"$mode_events"; }
_ksre_prompt_destroyed() { printf 'destroyed\n' >>"$mode_events"; }
_ksre_install_evidence() {
local content="$1"
printf 'install\n' >>"$mode_events"
printf '%s\n' "$content" >"$installed_evidence"
chmod 0600 "$installed_evidence"
}
}
probe_emit_success() (
install_mode_fakes
local output="${fixture_root}/emitted.env"
rm -f -- "$output"
_ksre_emit_result "$metadata" "$output" >/dev/null
declare -A parsed=()
_ksre_parse_external_file result "$output" parsed
[[ "${parsed[bundle_id]}" == "$FIXTURE_BUNDLE_ID" &&
"${parsed[secret_count_match]}" == pass && -z "${parsed[destroyed]+present}" &&
"$(<"$mode_events")" == $'context\nconnection\nlive\nisolation\nconnection\ncontext-recheck' ]]
)
assert_succeeds probe_emit_success
probe_emit_relative_output() (
install_mode_fakes
cd -- "$fixture_root"
rm -f -- relative-result.env
_ksre_emit_result "$metadata" relative-result.env >/dev/null
[[ -f relative-result.env ]]
)
assert_succeeds probe_emit_relative_output
probe_emit_existing_output() (
install_mode_fakes
local output="${fixture_root}/existing-output.env"
printf 'keep\n' >"$output"; chmod 0600 "$output"
_ksre_emit_result "$metadata" "$output" >/dev/null 2>&1 && return 1
[[ "$(<"$output")" == keep ]]
)
assert_succeeds probe_emit_existing_output
probe_emit_live_failure() (
local field="$1"
install_mode_fakes
local output="${fixture_root}/emit-failure-${field}.env"
_ksre_live_restore_inventory() {
case "$field" in
count) printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|match|pass|pass|6\n' ;;
node) printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|match|pass|fail|7\n' ;;
integrity) printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|mismatch|pass|pass|7\n' ;;
esac
}
if [[ "$field" == isolation ]]; then _ksre_isolation_attested() { return 1; }; fi
_ksre_emit_result "$metadata" "$output" >/dev/null 2>&1 && return 1
[[ ! -e "$output" ]]
)
for field in count node isolation integrity; do assert_succeeds probe_emit_live_failure "$field"; done
probe_record_success() (
install_mode_fakes
_ksre_record "$metadata" "$result" <<<"DESTROYED default" >/dev/null
declare -A parsed=()
_ksre_parse_external_file evidence "$installed_evidence" parsed
[[ "${parsed[destroyed]}" == confirmed && "$(head -n 1 "$mode_events")" == context ]]
)
assert_succeeds probe_record_success
probe_record_failure() (
local scenario="$1" local_metadata="${fixture_root}/record-${scenario}-metadata.env"
local local_result="${fixture_root}/record-${scenario}-result.env"
install_mode_fakes
write_metadata "$local_metadata"; write_result "$local_result"
case "$scenario" in
metadata_id) write_metadata "$local_metadata" post "$FIXTURE_OTHER_ID" ;;
result_id) write_result "$local_result" "$FIXTURE_OTHER_ID" ;;
result_version) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.1+k3s1 ;;
result_backend) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 sqlite ;;
pre) write_metadata "$local_metadata" pre ;;
stale) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd pass pass match pass 2026-07-30T00:00:00Z ;;
secret_count) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd pass pass match fail ;;
node) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd fail ;;
isolation) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd pass fail ;;
integrity) write_result "$local_result" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd pass pass mismatch ;;
authority_id)
_ksre_read_authoritative_bundle() {
local destination="$1" alternate="${fixture_root}/alternate-authority.env"
write_metadata "$alternate" post "$FIXTURE_OTHER_ID"
_ksre_parse_trusted_content bundle "$(<"$alternate")" "$destination"
}
;;
destroyed) _ksre_prompt_destroyed() { return 1; } ;;
target_exists) _ksre_evidence_target_absent() { return 1; } ;;
esac
_ksre_record "$local_metadata" "$local_result" <<<"DESTROYED default" >/dev/null 2>&1 && return 1
[[ ! -e "$installed_evidence" ]]
)
for scenario in metadata_id result_id result_version result_backend pre stale secret_count node isolation integrity authority_id destroyed target_exists; do
assert_succeeds probe_record_failure "$scenario"
done
probe_check_success() (
install_mode_fakes
_ksre_check >/dev/null
[[ "$(head -n 1 "$mode_events")" == context ]]
)
assert_succeeds probe_check_success
probe_check_failure() (
local scenario="$1" altered="${fixture_root}/check-${scenario}.env"
install_mode_fakes
case "$scenario" in
authority_id)
_ksre_read_authoritative_bundle() {
local destination="$1" alternate="${fixture_root}/check-authority.env"
write_metadata "$alternate" post "$FIXTURE_OTHER_ID"
_ksre_parse_trusted_content bundle "$(<"$alternate")" "$destination"
}
;;
version) write_evidence "$altered" "$FIXTURE_BUNDLE_ID" v1.36.1+k3s1; active_evidence_file="$altered" ;;
backend) write_evidence "$altered" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 sqlite; active_evidence_file="$altered" ;;
integrity) write_evidence "$altered" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd mismatch; active_evidence_file="$altered" ;;
destroyed) write_evidence "$altered" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd match pending; active_evidence_file="$altered" ;;
stale) write_evidence "$altered" "$FIXTURE_BUNDLE_ID" v1.36.2+k3s1 embedded-etcd match confirmed 2026-06-01T00:00:00Z 2026-06-01T01:00:00Z; active_evidence_file="$altered" ;;
live_stage) _ksre_live_restore_inventory() { printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|start|match|match|pass|pass|7\n'; } ;;
live_integrity) _ksre_live_restore_inventory() { printf 'v1.36.2+k3s1|embedded-etcd|enabled_stable|reencrypt_finished|match|mismatch|pass|pass|7\n'; } ;;
esac
_ksre_check >/dev/null 2>&1 && return 1
return 0
)
for scenario in authority_id version backend integrity destroyed stale live_stage live_integrity; do
assert_succeeds probe_check_failure "$scenario"
done
printf 'K3S SECRET ENCRYPTION RESTORE EVIDENCE TEST PASS\n'