#!/usr/bin/env bash PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH LC_ALL=C export LC_ALL umask 077 set -Eeuo pipefail _k3slrf_initial_guard() { local candidate_euid="${1-}" shell_flags="${2-}" [[ "$candidate_euid" =~ ^[0-9]+$ ]] || return 1 (( candidate_euid != 0 )) || return 1 [[ "$shell_flags" != *x* ]] } if ! _k3slrf_initial_guard "$EUID" "$-"; then printf 'Local recovery feasibility refused\n' >&2 if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then return 1; else exit 1; fi fi readonly K3SLRF_KEEPASSXC='/usr/bin/keepassxc-cli' readonly K3SLRF_CRYPTSETUP='/usr/sbin/cryptsetup' readonly K3SLRF_KEEPASS_PACKAGE='keepassxc' readonly K3SLRF_KEEPASS_VERSION='2.7.6+dfsg.1-1build3' readonly K3SLRF_CRYPTSETUP_PACKAGE='cryptsetup-bin' readonly K3SLRF_CRYPTSETUP_VERSION='2:2.7.0-1ubuntu4.2' readonly K3SLRF_SCRIPT_DIR="$(cd -P -- "${BASH_SOURCE[0]%/*}" && pwd -P)" readonly K3SLRF_REPOSITORY_ROOT="$(cd -P -- "${K3SLRF_SCRIPT_DIR}/../.." && pwd -P)" readonly K3SLRF_LIBRARY_PATH="${K3SLRF_REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery.sh" # shellcheck source=../lib/k3s-local-recovery.sh builtin source -- "$K3SLRF_LIBRARY_PATH" || { printf 'Local recovery feasibility refused\n' >&2 if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then return 1; else exit 1; fi } _k3slrf_usage() { printf 'Usage: bash scripts/validate/k3s-local-recovery-feasibility.sh [--execute]\n' } _k3slrf_fail() { printf 'Local recovery feasibility failed\n' >&2 return 1 } _k3slrf_require_interactive_stdin() { [[ -t 0 ]] } _k3slrf_require_cached_sudo() { _k3slr_cached_sudo_is_available } _k3slrf_package_version_is_exact() { local package="$1" expected="$2" output output="$( _k3slr_command /usr/bin/dpkg-query --show --showformat='${db:Status-Abbrev}|${Version}\n' -- "$package" 2>/dev/null printf '\034%s' "$?" )" || return 1 [[ "$output" == "ii |${expected}"$'\n'$'\0340' ]] } _k3slrf_verify_prerequisites() { _k3slr_trusted_root_executable /usr/bin/dpkg-query || return 1 _k3slr_trusted_root_executable /usr/bin/mktemp || return 1 _k3slr_trusted_root_executable /usr/bin/rm || return 1 _k3slr_trusted_root_executable /usr/bin/cmp || return 1 _k3slr_trusted_root_executable /usr/bin/mawk || return 1 _k3slr_trusted_root_executable /usr/bin/od || return 1 _k3slr_trusted_root_executable /usr/bin/ps || return 1 _k3slr_trusted_root_executable /usr/bin/sleep || return 1 _k3slr_trusted_root_executable /usr/bin/sha256sum || return 1 _k3slr_trusted_root_executable "$K3SLRF_KEEPASSXC" || return 1 _k3slr_trusted_root_executable "$K3SLRF_CRYPTSETUP" || return 1 _k3slrf_package_version_is_exact "$K3SLRF_KEEPASS_PACKAGE" "$K3SLRF_KEEPASS_VERSION" || return 1 _k3slrf_package_version_is_exact "$K3SLRF_CRYPTSETUP_PACKAGE" "$K3SLRF_CRYPTSETUP_VERSION" } _k3slrf_create_fixture_dir() { local destination_name="$1" identity_name="$2" owned_name="$3" created physical metadata uid created="$(_k3slr_command /usr/bin/mktemp -d /tmp/k3slr-feasibility.XXXXXX)" || return 1 printf -v "$destination_name" '%s' "$created" printf -v "$owned_name" '%s' true [[ "$created" =~ ^/tmp/k3slr-feasibility\.[A-Za-z0-9]+$ ]] || return 1 _k3slr_command /usr/bin/test -d "$created" || return 1 _k3slr_command /usr/bin/test ! -L "$created" || return 1 physical="$(cd -P -- "$created" && pwd -P)" || return 1 [[ "$physical" == "$created" ]] || return 1 _k3slr_capture_one_line uid /usr/bin/id -u || return 1 _k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F|%d:%i' -- "$created" || return 1 [[ "$metadata" == "${uid}|700|directory|"* ]] || return 1 printf -v "$identity_name" '%s' "${metadata##*|}" } _k3slrf_cleanup_fixture_dir() { local fixture_dir="$1" expected_identity="$2" metadata uid [[ "$fixture_dir" =~ ^/tmp/k3slr-feasibility\.[A-Za-z0-9]+$ ]] || return 1 _k3slr_command /usr/bin/test -d "$fixture_dir" || return 1 _k3slr_command /usr/bin/test ! -L "$fixture_dir" || return 1 _k3slr_capture_one_line uid /usr/bin/id -u || return 1 _k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F|%d:%i' -- "$fixture_dir" 2>/dev/null || return 1 [[ "$metadata" == "${uid}|700|directory|"* ]] || return 1 [[ -z "$expected_identity" || "${metadata##*|}" == "$expected_identity" ]] || return 1 _k3slr_command /usr/bin/rm --recursive --one-file-system -- "$fixture_dir" } _k3slrf_synthetic_master_once_stdout() { printf '%s' 'k3slr-package-feasibility-master-v1' | _k3slr_command /usr/bin/sha256sum | _k3slr_command /usr/bin/mawk '{ print $1 }' } _k3slrf_synthetic_master_twice_stdout() { _k3slrf_synthetic_master_once_stdout _k3slrf_synthetic_master_once_stdout } _k3slrf_validate_generated_password_stdin() { _k3slr_command /usr/bin/od -An -v -tx1 | _k3slr_command /usr/bin/mawk ' { for (i=1; i<=NF; i++) { n++; byte=$i if (n == 41) { if (byte != "0a") bad=1 } else if (byte ~ /^3[0-9]$/) digit=1 else if (byte ~ /^(4[1-9a-f]|5[0-9a])$/) upper=1 else if (byte ~ /^(6[1-9a-f]|7[0-9a])$/) lower=1 else bad=1 } } END { exit !(n == 41 && !bad && lower && upper && digit) } ' } _k3slrf_file_identity() { local destination_name="$1" path="$2" metadata uid _k3slr_command /usr/bin/test -f "$path" || return 1 _k3slr_command /usr/bin/test ! -L "$path" || return 1 _k3slr_capture_one_line uid /usr/bin/id -u || return 1 _k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F|%d:%i' -- "$path" || return 1 [[ "$metadata" == "${uid}|600|regular file|"* || "$metadata" == "${uid}|600|regular empty file|"* ]] || return 1 printf -v "$destination_name" '%s' "${metadata##*|}" } _k3slrf_file_identity_matches() { local current='' _k3slrf_file_identity current "$1" || return 1 [[ "$current" == "$2" ]] } _k3slrf_directory_identity() { local destination_name="$1" path="$2" physical metadata uid [[ "$path" =~ ^/tmp/k3slr-feasibility\.[A-Za-z0-9]+$ ]] || return 1 _k3slr_command /usr/bin/test -d "$path" || return 1 _k3slr_command /usr/bin/test ! -L "$path" || return 1 physical="$(cd -P -- "$path" && pwd -P)" || return 1 [[ "$physical" == "$path" ]] || return 1 _k3slr_capture_one_line uid /usr/bin/id -u || return 1 _k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F|%d:%i' -- "$path" || return 1 [[ "$metadata" == "${uid}|700|directory|"* ]] || return 1 printf -v "$destination_name" '%s' "${metadata##*|}" } _k3slrf_directory_identity_matches() { local current='' _k3slrf_directory_identity current "$1" || return 1 [[ "$current" == "$2" ]] } _k3slrf_run_tracked() { local rc caller_group='' current_identity='' stopped_identity='' _k3slr_process_group_for_pid caller_group "$BASHPID" || return 1 K3SLRF_ACTIVE_CHILD_STATE='launching' K3SLRF_LAST_CLEANUP_OK=false _k3slr_lifecycle_boundary feasibility-launch-before _k3slrf_pending_checkpoint || return $? set -m ( tracked_cancel='' trap '[[ "$tracked_cancel" == TERM ]] || tracked_cancel=INT' INT trap 'tracked_cancel=TERM' TERM kill -STOP "$BASHPID" tracked_worker_rc=143 if [[ -z "$tracked_cancel" ]]; then if "$@"; then tracked_worker_rc=0; else tracked_worker_rc=$?; fi fi while [[ -n "$tracked_cancel" ]]; do _k3slr_command /usr/bin/sleep 0.01 || : done exit "$tracked_worker_rc" ) & K3SLRF_ACTIVE_CHILD_PID=$! K3SLRF_ACTIVE_CHILD_STATE='published' _k3slr_lifecycle_boundary feasibility-pid-published "$K3SLRF_ACTIVE_CHILD_PID" if ! _k3slr_process_identity K3SLRF_ACTIVE_CHILD_IDENTITY "$K3SLRF_ACTIVE_CHILD_PID"; then set +m _k3slrf_cleanup_active_child || return 1 if [[ -n "$K3SLRF_PENDING_SIGNAL" ]]; then return "$K3SLRF_PENDING_STATUS"; fi return 1 fi if ! _k3slr_wait_for_stopped_group K3SLRF_ACTIVE_CHILD_PGID stopped_identity "$K3SLRF_ACTIVE_CHILD_PID" || [[ "$stopped_identity" != "$K3SLRF_ACTIVE_CHILD_IDENTITY" ]]; then set +m _k3slrf_cleanup_active_child || return 1 if [[ -n "$K3SLRF_PENDING_SIGNAL" ]]; then return "$K3SLRF_PENDING_STATUS"; fi return 1 fi K3SLRF_ACTIVE_CHILD_STATE='stopped_pinned' _k3slr_lifecycle_boundary feasibility-stop-query-complete _k3slrf_pending_checkpoint || { rc=$?; set +m; return "$rc"; } if [[ "$K3SLRF_ACTIVE_CHILD_PGID" == "$caller_group" ]]; then set +m _k3slrf_cleanup_active_child || return 1 return 1 fi set +m _k3slr_lifecycle_boundary feasibility-cont-before _k3slrf_pending_checkpoint || return $? _k3slr_continue_owned_child "$K3SLRF_ACTIVE_CHILD_PID" "$K3SLRF_ACTIVE_CHILD_PGID" \ "$K3SLRF_ACTIVE_CHILD_IDENTITY" || return 1 K3SLRF_ACTIVE_CHILD_STATE='continued' _k3slr_lifecycle_boundary feasibility-cont-after _k3slrf_pending_checkpoint || return $? K3SLRF_ACTIVE_CHILD_STATE='waiting' if _k3slr_wait_child "$K3SLRF_ACTIVE_CHILD_PID"; then rc=0; else rc=$?; fi _k3slr_lifecycle_boundary feasibility-wait-reaped-before-clear if _k3slr_process_identity current_identity "$K3SLRF_ACTIVE_CHILD_PID" 2>/dev/null && [[ "$current_identity" == "$K3SLRF_ACTIVE_CHILD_IDENTITY" ]]; then _k3slrf_cleanup_active_child || return 1 fi K3SLRF_ACTIVE_CHILD_STATE='reaped' K3SLRF_ACTIVE_CHILD_PID='' \ K3SLRF_ACTIVE_CHILD_PGID='' K3SLRF_ACTIVE_CHILD_IDENTITY='' K3SLRF_LAST_CLEANUP_OK=true _k3slr_lifecycle_boundary feasibility-state-cleared if [[ -n "$K3SLRF_PENDING_SIGNAL" ]]; then rc="$K3SLRF_PENDING_STATUS" fi return "$rc" } _k3slrf_db_create() { _k3slrf_synthetic_master_twice_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" db-create --quiet --set-password "$1" >/dev/null 2>&1 } _k3slrf_entry_add() { _k3slrf_synthetic_master_once_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" add --quiet --generate --length 40 \ --lower --upper --numeric --every-group "$1" "$K3SLR_KEEPASS_ENTRY" >/dev/null 2>&1 } _k3slrf_password_check() { _k3slrf_synthetic_master_once_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" show --show-protected \ --attributes Password "$1" "$K3SLR_KEEPASS_ENTRY" 2>/dev/null | _k3slrf_validate_generated_password_stdin } _k3slrf_attachment_listing_stdout() { (( $# == 1 )) || return 1 _k3slrf_synthetic_master_once_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" show --quiet --attributes Title \ --show-attachments "$1" "$K3SLR_KEEPASS_ENTRY" } _k3slrf_attachment_state_is() { local database="${1-}" fixture_dir="${2-}" expected_state="${3-}" local capture_base='' record='' state='' (( $# == 3 )) || return 1 [[ "$expected_state" == absent || "$expected_state" == present ]] || return 1 capture_base="${fixture_dir}/keepass-listing.${BASHPID}" _k3slr_capture_keepass_attachment_listing record \ "${capture_base}.stdout" "${capture_base}.stderr" \ _k3slrf_attachment_listing_stdout "$database" || return 1 _k3slr_parse_keepass_attachment_listing "$record" fixture.bin state || return 1 [[ "$state" == "$expected_state" ]] } _k3slrf_attachment_import() { _k3slrf_synthetic_master_once_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" attachment-import --quiet "$1" "$K3SLR_KEEPASS_ENTRY" \ fixture.bin "$2" >/dev/null 2>&1 } _k3slrf_attachment_export() { _k3slrf_synthetic_master_once_stdout | _k3slr_command "$K3SLRF_KEEPASSXC" attachment-export --quiet "$1" "$K3SLR_KEEPASS_ENTRY" \ fixture.bin "$2" >/dev/null 2>&1 } _k3slrf_run_synthetic() { local fixture_dir="$1" database input_file output_file database_identity input_identity output_identity fixture_identity export_fd database="${fixture_dir}/synthetic.kdbx" input_file="${fixture_dir}/input.bin" output_file="${fixture_dir}/output.bin" [[ ! -e "$database" && ! -L "$database" && ! -e "$input_file" && ! -L "$input_file" && ! -e "$output_file" && ! -L "$output_file" ]] || return 1 _k3slrf_directory_identity fixture_identity "$fixture_dir" || return 1 ( set -o noclobber; printf '%s\n' 'k3slr-attachment-roundtrip-v1' >"$input_file" ) || return 1 _k3slrf_file_identity input_identity "$input_file" || return 1 _k3slrf_run_tracked _k3slrf_db_create "$database" || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity database_identity "$database" || return 1 _k3slrf_run_tracked _k3slrf_entry_add "$database" || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity database_identity "$database" || return 1 _k3slrf_run_tracked _k3slrf_password_check "$database" || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity_matches "$database" "$database_identity" || return 1 _k3slrf_file_identity_matches "$input_file" "$input_identity" || return 1 _k3slrf_run_tracked _k3slrf_attachment_state_is "$database" "$fixture_dir" absent || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity_matches "$database" "$database_identity" || return 1 _k3slrf_file_identity_matches "$input_file" "$input_identity" || return 1 _k3slrf_run_tracked _k3slrf_attachment_import "$database" "$input_file" || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity database_identity "$database" || return 1 _k3slrf_run_tracked _k3slrf_attachment_state_is "$database" "$fixture_dir" present || return 1 _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity_matches "$database" "$database_identity" || return 1 set -o noclobber if ! exec {export_fd}>"$output_file"; then set +o noclobber return 1 fi set +o noclobber _k3slrf_file_identity output_identity "$output_file" || return 1 _k3slrf_run_tracked _k3slrf_attachment_export "$database" "/proc/${BASHPID}/fd/${export_fd}" || return 1 exec {export_fd}>&- _k3slrf_directory_identity_matches "$fixture_dir" "$fixture_identity" || return 1 _k3slrf_file_identity_matches "$database" "$database_identity" || return 1 _k3slrf_file_identity_matches "$output_file" "$output_identity" || return 1 _k3slr_command /usr/bin/cmp --silent -- "$input_file" "$output_file" } k3slr_local_recovery_feasibility_main() ( local execute=false fixture_dir='' fixture_identity='' fixture_owned=false local K3SLRF_ACTIVE_CHILD_PID='' K3SLRF_ACTIVE_CHILD_PGID='' K3SLRF_ACTIVE_CHILD_IDENTITY='' local K3SLRF_ACTIVE_CHILD_STATE='empty' K3SLRF_PENDING_SIGNAL='' K3SLRF_PENDING_STATUS='' local K3SLRF_LAST_CLEANUP_OK=false case "$#" in 0) ;; 1) [[ "$1" == --execute ]] || { _k3slrf_usage >&2; return 2; }; execute=true ;; *) _k3slrf_usage >&2; return 2 ;; esac if "$execute"; then _k3slrf_require_interactive_stdin || { _k3slrf_fail; return 1; } fi _k3slrf_verify_prerequisites || { _k3slrf_fail; return 1; } if ! "$execute"; then printf 'Local recovery feasibility prerequisites: pass\n' return 0 fi _k3slrf_require_cached_sudo || { _k3slrf_fail; return 1; } _k3slrf_cleanup_active_child() { local cleanup_rc=0 if [[ -n "$K3SLRF_ACTIVE_CHILD_PID" ]]; then if [[ "$K3SLRF_ACTIVE_CHILD_STATE" == published ]]; then _k3slr_terminate_published_child "$K3SLRF_ACTIVE_CHILD_PID" || cleanup_rc=$? elif [[ -n "$K3SLRF_ACTIVE_CHILD_IDENTITY" ]]; then _k3slr_terminate_and_reap "$K3SLRF_ACTIVE_CHILD_PID" "$K3SLRF_ACTIVE_CHILD_PGID" \ "$K3SLRF_ACTIVE_CHILD_IDENTITY" || cleanup_rc=$? else cleanup_rc=1 fi if (( cleanup_rc == 0 )); then K3SLRF_ACTIVE_CHILD_PID='' K3SLRF_ACTIVE_CHILD_PGID='' K3SLRF_ACTIVE_CHILD_IDENTITY='' K3SLRF_ACTIVE_CHILD_STATE='reaped' K3SLRF_LAST_CLEANUP_OK=true fi fi return "$cleanup_rc" } _k3slrf_signal() { if [[ "$1" == TERM || "$K3SLRF_PENDING_SIGNAL" != TERM ]]; then K3SLRF_PENDING_SIGNAL="$1" K3SLRF_PENDING_STATUS="$2" fi } _k3slrf_pending_checkpoint() { local signal_status cleanup_rc=0 [[ -n "$K3SLRF_PENDING_SIGNAL" ]] || return 0 signal_status="$K3SLRF_PENDING_STATUS" if [[ -n "$K3SLRF_ACTIVE_CHILD_PID" ]]; then [[ -n "$K3SLRF_ACTIVE_CHILD_IDENTITY" ]] || return 1 _k3slrf_cleanup_active_child || cleanup_rc=$? else K3SLRF_LAST_CLEANUP_OK=true fi (( cleanup_rc == 0 )) || return 1 return "$signal_status" } _k3slrf_exit_cleanup() { local original_status="$1" cleanup_rc=0 trap - EXIT _k3slrf_cleanup_active_child || cleanup_rc=$? if "$fixture_owned"; then _k3slrf_cleanup_fixture_dir "$fixture_dir" "$fixture_identity" >/dev/null 2>&1 || cleanup_rc=1 fi (( cleanup_rc == 0 )) || exit 1 exit "$original_status" } trap '_k3slrf_exit_cleanup "$?"' EXIT trap '_k3slrf_signal INT 130' INT trap '_k3slrf_signal TERM 143' TERM _k3slrf_create_fixture_dir fixture_dir fixture_identity fixture_owned || { _k3slrf_fail; return 1; } if ! _k3slrf_run_synthetic "$fixture_dir"; then if [[ -n "$K3SLRF_PENDING_STATUS" && -z "$K3SLRF_ACTIVE_CHILD_PID" && "$K3SLRF_LAST_CLEANUP_OK" == true ]]; then _k3slrf_cleanup_fixture_dir "$fixture_dir" "$fixture_identity" || return 1 fixture_owned=false trap - EXIT INT TERM return "$K3SLRF_PENDING_STATUS" fi _k3slrf_fail return 1 fi _k3slrf_cleanup_fixture_dir "$fixture_dir" "$fixture_identity" || { _k3slrf_fail; return 1; } fixture_owned=false trap - EXIT INT TERM printf 'Local recovery feasibility: pass\n' ) if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then k3slr_local_recovery_feasibility_main "$@" fi