#!/usr/bin/env bash # Source-only foundation for the standalone K3s LUKS header proof. readonly K3SLRH_PROOF_RELATIVE='HyeonworksRecovery/containers/k3s-recovery.header-proof.luks' readonly K3SLRH_PRE_KDBX_PREFIX='hyeonworks-recovery.header-proof-pre.' readonly K3SLRH_POST_KDBX_PREFIX='hyeonworks-recovery.header-proof-post.' readonly K3SLRH_HEADER_BACKUP_NAME='header-original.bin' readonly K3SLRH_HEADER_EXPORT_NAME='header-exported.bin' readonly K3SLRH_SQLITE_PACKAGE_VERSION='3.45.1-1ubuntu2.7' readonly K3SLRH_SHARED_LIBRARY_SHA256='a206d81539e6c37fa085457ba01cff9de57de50f7235e3a4085989780e99585d' readonly K3SLRH_A1_LIBRARY_SHA256='8b39fc46229ebd4c77ff20040f942c4ef7b756d564d344a8ecdd11a7428e2968' readonly K3SLRH_A1_HELPER_SHA256='06831fa35943a328ecd183d3c6f721b24c4607b2f349abf6201a12e690d08ebe' readonly K3SLRH_CONTRACT_SHA256='c045f42c2dc3eda09c37a123bcc05d88d94f275e61504a2d5816948237e516df' readonly K3SLRH_VALIDATOR_SHA256='6b869032fc325759ee25b219fad4ea6190a3389a561c496f0664a51b802f73e6' _k3slrh_command() { "$@" } _k3slrh_output_name_is_safe() { local _k3slrh_output_candidate="${1-}" (( $# == 1 )) || return 1 [[ "$_k3slrh_output_candidate" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || return 1 [[ "$_k3slrh_output_candidate" != _k3slrh_* ]] } _k3slrh_stderr_marker() { local _k3slrh_stderr_chunk='' _k3slrh_stderr_rc=0 if IFS= read -r -d '' -n 1 _k3slrh_stderr_chunk; then _k3slrh_stderr_rc=0 else _k3slrh_stderr_rc=$? fi if (( _k3slrh_stderr_rc == 0 )); then printf '\0STDERR\0' while IFS= read -r -d '' -n 4096 _k3slrh_stderr_chunk; do : done elif [[ -n "$_k3slrh_stderr_chunk" ]]; then printf '\0STDERR\0' fi } _k3slrh_command_packet() { local _k3slrh_packet_rc=0 (( $# > 0 )) || return 1 if _k3slrh_command "$@" 2> >(_k3slrh_stderr_marker); then _k3slrh_packet_rc=0 else _k3slrh_packet_rc=$? fi printf '\0RC=%s\0' "$_k3slrh_packet_rc" } _k3slrh_capture_command() { local _k3slrh_capture_output="${1-}" _k3slrh_capture_expected_rc="${2-}" local -a _k3slrh_capture_records=() shift 2 || return 1 (( $# > 0 )) || return 1 _k3slrh_output_name_is_safe "$_k3slrh_capture_output" || return 1 [[ "$_k3slrh_capture_expected_rc" =~ ^(0|[1-9][0-9]{0,2})$ ]] || return 1 (( 10#$_k3slrh_capture_expected_rc <= 255 )) || return 1 mapfile -d '' -t _k3slrh_capture_records < <(_k3slrh_command_packet "$@") (( ${#_k3slrh_capture_records[@]} == 2 )) || return 1 [[ "${_k3slrh_capture_records[1]}" == "RC=${_k3slrh_capture_expected_rc}" ]] || return 1 printf -v "$_k3slrh_capture_output" '%s' "${_k3slrh_capture_records[0]}" } _k3slrh_capture_one_line() { local _k3slrh_line_output="${1-}" _k3slrh_line_value='' shift || return 1 _k3slrh_output_name_is_safe "$_k3slrh_line_output" || return 1 _k3slrh_capture_command _k3slrh_line_value 0 "$@" || return 1 [[ "$_k3slrh_line_value" == *$'\n' ]] || return 1 [[ "$_k3slrh_line_value" != *$'\r'* ]] || return 1 _k3slrh_line_value="${_k3slrh_line_value%$'\n'}" [[ -n "$_k3slrh_line_value" && "$_k3slrh_line_value" != *$'\n'* ]] || return 1 printf -v "$_k3slrh_line_output" '%s' "$_k3slrh_line_value" } _k3slrh_parse_cli() { local _k3slrh_cli_output="${1-}" _k3slrh_cli_mode='' shift || return 1 _k3slrh_output_name_is_safe "$_k3slrh_cli_output" || return 1 case "$#" in 0) _k3slrh_cli_mode=dry-run ;; 1) [[ "$1" == --execute ]] || return 1 _k3slrh_cli_mode=execute ;; *) return 1 ;; esac printf -v "$_k3slrh_cli_output" '%s' "$_k3slrh_cli_mode" } _k3slrh_target_state_is_consistent() { local _k3slrh_target_utc="${1-}" _k3slrh_target_pre="${2-}" local _k3slrh_target_post="${3-}" _k3slrh_target_expected_pre='' local _k3slrh_target_expected_post='' (( $# == 3 )) || return 1 [[ "$_k3slrh_target_utc" =~ ^[0-9]{8}T[0-9]{6}Z$ ]] || return 1 _k3slrh_target_expected_pre="HyeonworksRecovery/vault/backups/${K3SLRH_PRE_KDBX_PREFIX}${_k3slrh_target_utc}.kdbx" _k3slrh_target_expected_post="HyeonworksRecovery/vault/backups/${K3SLRH_POST_KDBX_PREFIX}${_k3slrh_target_utc}.kdbx" [[ "$_k3slrh_target_pre" == "$_k3slrh_target_expected_pre" ]] || return 1 [[ "$_k3slrh_target_post" == "$_k3slrh_target_expected_post" ]] || return 1 [[ "$_k3slrh_target_pre" != "$_k3slrh_target_post" ]] } _k3slrh_derive_targets() { local _k3slrh_derive_utc="${1-}" _k3slrh_derive_pre='' _k3slrh_derive_post='' (( $# == 1 )) || return 1 [[ "$_k3slrh_derive_utc" =~ ^[0-9]{8}T[0-9]{6}Z$ ]] || return 1 [[ "${K3SLRH_RUN_UTC+x}" != x && "${K3SLRH_PRE_KDBX_RELATIVE+x}" != x && "${K3SLRH_POST_KDBX_RELATIVE+x}" != x ]] || return 1 _k3slrh_derive_pre="HyeonworksRecovery/vault/backups/${K3SLRH_PRE_KDBX_PREFIX}${_k3slrh_derive_utc}.kdbx" _k3slrh_derive_post="HyeonworksRecovery/vault/backups/${K3SLRH_POST_KDBX_PREFIX}${_k3slrh_derive_utc}.kdbx" _k3slrh_target_state_is_consistent "$_k3slrh_derive_utc" "$_k3slrh_derive_pre" "$_k3slrh_derive_post" || return 1 declare -gr K3SLRH_RUN_UTC="$_k3slrh_derive_utc" declare -gr K3SLRH_PRE_KDBX_RELATIVE="$_k3slrh_derive_pre" declare -gr K3SLRH_POST_KDBX_RELATIVE="$_k3slrh_derive_post" } _k3slrh_source_file_is_exact() { local _k3slrh_source_path="${1-}" _k3slrh_source_hash="${2-}" local _k3slrh_source_canonical='' _k3slrh_source_metadata='' _k3slrh_source_digest='' (( $# == 2 )) || return 1 [[ "$_k3slrh_source_path" == /* && "$_k3slrh_source_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 _k3slrh_command /usr/bin/test -f "$_k3slrh_source_path" >/dev/null 2>&1 || return 1 _k3slrh_command /usr/bin/test ! -L "$_k3slrh_source_path" >/dev/null 2>&1 || return 1 _k3slrh_capture_one_line _k3slrh_source_canonical /usr/bin/readlink -e -- "$_k3slrh_source_path" || return 1 [[ "$_k3slrh_source_canonical" == "$_k3slrh_source_path" ]] || return 1 _k3slrh_capture_one_line _k3slrh_source_metadata /usr/bin/stat '--format=%F|%u|%g|%a' -- "$_k3slrh_source_path" || return 1 [[ "$_k3slrh_source_metadata" == 'regular file|'* || "$_k3slrh_source_metadata" == 'regular empty file|'* ]] || return 1 _k3slrh_capture_one_line _k3slrh_source_digest /usr/bin/sha256sum -- "$_k3slrh_source_path" || return 1 [[ "$_k3slrh_source_digest" == "${_k3slrh_source_hash} ${_k3slrh_source_path}" ]] } _k3slrh_required_sources_are_exact() { local _k3slrh_source_root="${K3SLRH_REPOSITORY_ROOT:-}" (( $# == 0 )) || return 1 [[ "$_k3slrh_source_root" == /* ]] || return 1 _k3slrh_source_file_is_exact "${_k3slrh_source_root}/scripts/lib/k3s-local-recovery.sh" "$K3SLRH_SHARED_LIBRARY_SHA256" || return 1 _k3slrh_source_file_is_exact "${_k3slrh_source_root}/scripts/lib/k3s-local-recovery-a1.sh" "$K3SLRH_A1_LIBRARY_SHA256" || return 1 _k3slrh_source_file_is_exact "${_k3slrh_source_root}/scripts/libexec/k3s-local-recovery-a1-check.sh" "$K3SLRH_A1_HELPER_SHA256" || return 1 _k3slrh_source_file_is_exact "${_k3slrh_source_root}/infrastructure/security/k3s/local-recovery.env" "$K3SLRH_CONTRACT_SHA256" } _k3slrh_required_binaries_are_trusted() { local _k3slrh_binary='' local -a _k3slrh_binaries=( /usr/bin/bash /usr/bin/date /usr/bin/dpkg-query /usr/bin/env /usr/bin/find /usr/bin/findmnt /usr/bin/keepassxc-cli /usr/bin/readlink /usr/bin/sha256sum /usr/bin/sqlite3 /usr/bin/stat /usr/bin/sudo /usr/bin/systemctl /usr/bin/test /usr/bin/timeout /usr/local/bin/k3s /usr/sbin/cryptsetup /usr/sbin/losetup ) (( $# == 0 )) || return 1 for _k3slrh_binary in "${_k3slrh_binaries[@]}"; do _k3slr_trusted_root_executable "$_k3slrh_binary" || return 1 done } _k3slrh_package_is_exact() { local _k3slrh_package_name="${1-}" _k3slrh_package_expected="${2-}" local _k3slrh_package_record='' (( $# == 2 )) || return 1 case "$_k3slrh_package_name|$_k3slrh_package_expected" in 'keepassxc|2.7.6+dfsg.1-1build3'|'cryptsetup-bin|2:2.7.0-1ubuntu4.2'|"sqlite3|${K3SLRH_SQLITE_PACKAGE_VERSION}") ;; *) return 1 ;; esac _k3slrh_capture_command _k3slrh_package_record 0 /usr/bin/dpkg-query --show '--showformat=${Status}|${Version}\n' "$_k3slrh_package_name" || return 1 [[ "$_k3slrh_package_record" == "install ok installed|${_k3slrh_package_expected}"$'\n' ]] } _k3slrh_packages_are_exact() { (( $# == 0 )) || return 1 _k3slrh_package_is_exact keepassxc '2.7.6+dfsg.1-1build3' || return 1 _k3slrh_package_is_exact cryptsetup-bin '2:2.7.0-1ubuntu4.2' || return 1 _k3slrh_package_is_exact sqlite3 "$K3SLRH_SQLITE_PACKAGE_VERSION" } _k3slrh_cached_sudo_is_available() { (( $# == 0 )) || return 1 _k3slrh_command /usr/bin/sudo --non-interactive --validate >/dev/null 2>&1 } _k3slrh_k3s_health() { local _k3slrh_health_systemctl='' _k3slrh_health_readyz='' (( $# == 0 )) || return 1 _k3slrh_capture_command _k3slrh_health_systemctl 0 /usr/bin/systemctl is-active --quiet k3s || return 1 [[ -z "$_k3slrh_health_systemctl" ]] || return 1 _k3slrh_capture_command _k3slrh_health_readyz 0 \ /usr/bin/sudo --non-interactive -- \ /usr/bin/env -i 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' LC_ALL=C \ /usr/bin/timeout --signal=TERM --kill-after=1s 30s \ /usr/local/bin/k3s kubectl get --raw=/readyz || return 1 [[ "$_k3slrh_health_readyz" == $'ok\n' ]] } _k3slrh_run_closed_validator() { local _k3slrh_validator_path='' _k3slrh_validator_record='' local _k3slrh_validator_command_rc=0 _k3slrh_validator_post_rc=0 (( $# == 0 )) || return 1 [[ "${K3SLRH_REPOSITORY_ROOT:-}" == /* ]] || return 1 _k3slrh_validator_path="${K3SLRH_REPOSITORY_ROOT}/scripts/validate/k3s-local-recovery.sh" _k3slrh_source_file_is_exact "$_k3slrh_validator_path" "$K3SLRH_VALIDATOR_SHA256" || return 1 _k3slrh_capture_command _k3slrh_validator_record 0 \ /usr/bin/env -i 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' LC_ALL=C \ /usr/bin/bash --noprofile --norc -- "$_k3slrh_validator_path" --expect-closed || _k3slrh_validator_command_rc=1 _k3slrh_source_file_is_exact "$_k3slrh_validator_path" "$K3SLRH_VALIDATOR_SHA256" || _k3slrh_validator_post_rc=1 (( _k3slrh_validator_command_rc == 0 && _k3slrh_validator_post_rc == 0 )) || return 1 [[ "$_k3slrh_validator_record" == $'Recovery device: match\nRecovery state: closed\nLineage: match\nLatest bundle: not_checked\n' ]] } _k3slrh_mount_is_absent() { local _k3slrh_mount_path="${1-}" _k3slrh_mount_record='' (( $# == 1 )) || return 1 [[ "$_k3slrh_mount_path" == /* ]] || return 1 _k3slrh_capture_command _k3slrh_mount_record 1 /usr/bin/findmnt -rn -M "$_k3slrh_mount_path" -o TARGET || return 1 [[ -z "$_k3slrh_mount_record" ]] } _k3slrh_mapping_is_absent() { local _k3slrh_mapping_path="${1-}" (( $# == 1 )) || return 1 case "$_k3slrh_mapping_path" in "/dev/mapper/${K3SLR_MAPPING_NAME}"|"/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}") ;; *) return 1 ;; esac _k3slrh_command /usr/bin/test ! -e "$_k3slrh_mapping_path" >/dev/null 2>&1 || return 1 _k3slrh_command /usr/bin/test ! -L "$_k3slrh_mapping_path" >/dev/null 2>&1 } _k3slrh_related_loops_are_absent() { local _k3slrh_loop_record='' _k3slrh_loop_line='' local _k3slrh_loop_original="${K3SLR_OUTER_MOUNT}/${K3SLR_CONTAINER_RELATIVE}" local _k3slrh_loop_proof="${K3SLR_OUTER_MOUNT}/${K3SLRH_PROOF_RELATIVE}" (( $# == 0 )) || return 1 _k3slrh_capture_command _k3slrh_loop_record 0 /usr/sbin/losetup --list --noheadings --output NAME,BACK-FILE || return 1 while IFS= read -r _k3slrh_loop_line; do [[ -n "$_k3slrh_loop_line" ]] || continue case "$_k3slrh_loop_line" in *" ${_k3slrh_loop_original}"|*" ${_k3slrh_loop_proof}") return 1 ;; esac done <<<"$_k3slrh_loop_record" } _k3slrh_outer_mountpoint_is_clean() { local _k3slrh_outer_canonical='' _k3slrh_outer_metadata='' _k3slrh_outer_children='' (( $# == 0 )) || return 1 _k3slrh_command /usr/bin/test -d "$K3SLR_OUTER_MOUNT" >/dev/null 2>&1 || return 1 _k3slrh_command /usr/bin/test ! -L "$K3SLR_OUTER_MOUNT" >/dev/null 2>&1 || return 1 _k3slrh_capture_one_line _k3slrh_outer_canonical /usr/bin/readlink -e -- "$K3SLR_OUTER_MOUNT" || return 1 [[ "$_k3slrh_outer_canonical" == "$K3SLR_OUTER_MOUNT" ]] || return 1 _k3slrh_capture_one_line _k3slrh_outer_metadata /usr/bin/stat '--format=%F|%u|%g|%a' -- "$K3SLR_OUTER_MOUNT" || return 1 [[ "$_k3slrh_outer_metadata" == 'directory|0|0|755' ]] || return 1 _k3slrh_capture_command _k3slrh_outer_children 0 /usr/bin/find "$K3SLR_OUTER_MOUNT" -mindepth 1 -maxdepth 1 -print -quit || return 1 [[ -z "$_k3slrh_outer_children" ]] } _k3slrh_runtime_residue_is_absent() { local _k3slrh_runtime_record='' (( $# == 0 )) || return 1 _k3slrh_capture_command _k3slrh_runtime_record 0 /usr/bin/find /run -mindepth 1 -maxdepth 1 -name 'hyeonworks-k3s-recovery.*' -print -quit || return 1 [[ -z "$_k3slrh_runtime_record" ]] } _k3slrh_closed_resources_are_absent() { (( $# == 0 )) || return 1 _k3slrh_mount_is_absent "$K3SLR_OUTER_MOUNT" || return 1 _k3slrh_mount_is_absent "$K3SLR_INNER_MOUNT" || return 1 _k3slrh_mapping_is_absent "/dev/mapper/${K3SLR_MAPPING_NAME}" || return 1 _k3slrh_mapping_is_absent "/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}" || return 1 _k3slrh_related_loops_are_absent || return 1 _k3slrh_outer_mountpoint_is_clean || return 1 _k3slrh_runtime_residue_is_absent } _k3slrh_pre_mount_checks() { (( $# == 0 )) || return 1 _k3slrh_required_sources_are_exact || return 1 _k3slrh_required_binaries_are_trusted || return 1 _k3slrh_packages_are_exact || return 1 _k3slrh_cached_sudo_is_available || return 1 _k3slrh_k3s_health || return 1 _k3slrh_run_closed_validator || return 1 _k3slrh_closed_resources_are_absent } _k3slrh_pre_mount_capability() { (( $# == 0 )) || return 1 _k3slrh_pre_mount_checks || return 1 printf 'Header proof capability: pass\n' } _k3slrh_current_utc() { local _k3slrh_utc_output="${1-}" _k3slrh_utc_value='' (( $# == 1 )) || return 1 _k3slrh_output_name_is_safe "$_k3slrh_utc_output" || return 1 _k3slrh_capture_one_line _k3slrh_utc_value /usr/bin/date --utc +%Y%m%dT%H%M%SZ || return 1 [[ "$_k3slrh_utc_value" =~ ^[0-9]{8}T[0-9]{6}Z$ ]] || return 1 printf -v "$_k3slrh_utc_output" '%s' "$_k3slrh_utc_value" } _k3slrh_header_proof_main() { local _k3slrh_main_mode='' _k3slrh_main_utc='' _k3slrh_main_parse_rc=0 if _k3slrh_parse_cli _k3slrh_main_mode "$@"; then _k3slrh_main_parse_rc=0 else _k3slrh_main_parse_rc=$? fi (( _k3slrh_main_parse_rc == 0 )) || return 2 [[ "${EUID:-}" =~ ^[0-9]+$ && "${EUID:-}" != 0 ]] || return 1 _k3slr_xtrace_is_disabled || return 1 if [[ "$_k3slrh_main_mode" == execute ]]; then _k3slr_require_execute_tty || return 1 declare -F _k3slrh_prove_header_restore_a1 >/dev/null || return 1 return 1 fi _k3slrh_current_utc _k3slrh_main_utc || return 1 _k3slrh_derive_targets "$_k3slrh_main_utc" || return 1 _k3slrh_pre_mount_checks || return 1 printf '%s\n' \ 'Header proof state: closed' \ 'Header proof capability: pass' \ 'Execute-time checks: pending' \ 'DRY RUN PASS: no changes' }