#!/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 #include #include #include #include #include #include #include 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'