Add platform infrastructure configuration
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,344 @@
|
||||
#!/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'
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,569 @@
|
||||
#!/usr/bin/env bash
|
||||
# Source-only helpers for k3s Secret-encryption validation. They deliberately
|
||||
# return classifications instead of printing service, config, or key material.
|
||||
|
||||
_k3s_status_schema_valid() {
|
||||
jq -e -s '
|
||||
length == 1 and
|
||||
(.[0] | type == "object" and
|
||||
(.stage | type == "string") and
|
||||
(.activekey | type == "string") and
|
||||
((has("enable") | not) or (.enable | type == "boolean")) and
|
||||
((has("hashmatch") | not) or (.hashmatch | type == "boolean")) and
|
||||
((has("hasherror") | not) or (.hasherror | type == "string")) and
|
||||
((has("inactivekeys") | not) or (.inactivekeys | type == "array")))
|
||||
' >/dev/null 2>&1 <<<"$1"
|
||||
}
|
||||
|
||||
classify_encryption_status() {
|
||||
local status_json="$1"
|
||||
|
||||
if ! _k3s_status_schema_valid "$status_json"; then
|
||||
printf 'invalid\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if jq -e '
|
||||
(has("enable") | not) and .stage == "" and .activekey == "" and
|
||||
(has("hasherror") | not)
|
||||
' >/dev/null <<<"$status_json"; then
|
||||
printf 'disabled_no_config\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if jq -e '
|
||||
has("enable") and
|
||||
((.hashmatch != true) or (has("hasherror") and .hasherror != ""))
|
||||
' >/dev/null <<<"$status_json"; then
|
||||
printf 'hash_mismatch\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if jq -e '
|
||||
.enable == false and .stage == "start" and .activekey == "" and
|
||||
.hashmatch == true and ((has("inactivekeys") | not) or (.inactivekeys | length == 0))
|
||||
' >/dev/null <<<"$status_json"; then
|
||||
printf 'transition_start\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if jq -e '
|
||||
.enable == true and (.activekey | length > 0) and .hashmatch == true and
|
||||
(.stage == "start" or .stage == "reencrypt_finished")
|
||||
' >/dev/null <<<"$status_json"; then
|
||||
printf 'enabled_stable\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if jq -e '.enable == true and .hashmatch == true' >/dev/null <<<"$status_json"; then
|
||||
printf 'unsafe_transition\n'
|
||||
else
|
||||
printf 'invalid\n'
|
||||
fi
|
||||
}
|
||||
|
||||
classify_encryption_provider() {
|
||||
local status_json="$1"
|
||||
local active_key
|
||||
|
||||
if ! _k3s_status_schema_valid "$status_json"; then
|
||||
printf 'invalid\n'
|
||||
return 0
|
||||
fi
|
||||
active_key="$(jq -r '.activekey' <<<"$status_json")"
|
||||
case "$active_key" in
|
||||
'AES-CBC '*)
|
||||
[[ "${active_key#AES-CBC }" != "$active_key" && -n "${active_key#AES-CBC }" ]] && printf 'aescbc\n' || printf 'invalid\n'
|
||||
;;
|
||||
'XSalsa20-POLY1305 '*)
|
||||
[[ "${active_key#XSalsa20-POLY1305 }" != "$active_key" && -n "${active_key#XSalsa20-POLY1305 }" ]] && printf 'secretbox\n' || printf 'invalid\n'
|
||||
;;
|
||||
*)
|
||||
printf 'invalid\n'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
version_supports_late_enable() {
|
||||
local version="$1"
|
||||
local minor patch
|
||||
|
||||
[[ "$version" =~ ^v1\.([0-9]+)\.([0-9]+)\+k3s[0-9]+$ ]] || return 1
|
||||
minor="${BASH_REMATCH[1]}"
|
||||
patch="${BASH_REMATCH[2]}"
|
||||
case "$minor" in
|
||||
33) (( patch >= 10 )) ;;
|
||||
34) (( patch >= 6 )) ;;
|
||||
35) (( patch >= 3 )) ;;
|
||||
*) (( minor >= 36 )) ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_exact_encryption_state() {
|
||||
local expected="$1"
|
||||
local status_json="$2"
|
||||
[[ "$(classify_encryption_status "$status_json")" == "$expected" ]]
|
||||
}
|
||||
|
||||
_k3s_secure_regular_file() {
|
||||
local path="$1"
|
||||
local metadata
|
||||
|
||||
[[ -f "$path" && ! -L "$path" ]] || return 1
|
||||
metadata="$(stat --format='%u:%a:%F' -- "$path" 2>/dev/null)" || return 1
|
||||
[[ "$metadata" == '0:600:regular file' ]]
|
||||
}
|
||||
|
||||
_k3s_read_file() {
|
||||
local path="$1"
|
||||
[[ -f "$path" && ! -L "$path" ]] || return 1
|
||||
command cat -- "$path"
|
||||
}
|
||||
|
||||
_k3s_list_yaml_files() {
|
||||
local config="$1" file
|
||||
[[ -f "$config" && ! -L "$config" ]] && printf '%s\n' "$config"
|
||||
if [[ -d "${config}.d" && ! -L "${config}.d" ]]; then
|
||||
while IFS= read -r file; do printf '%s\n' "$file"; done < <(
|
||||
find "${config}.d" -maxdepth 1 -type f -name '*.yaml' -print | sort
|
||||
)
|
||||
fi
|
||||
}
|
||||
|
||||
verify_local_encryption_config_integrity() {
|
||||
local config_path="$1"
|
||||
local state_path="$2"
|
||||
local annotation_value="$3"
|
||||
local status_stage="$4"
|
||||
local state_value config_hash config_metadata state_metadata
|
||||
|
||||
if ! _k3s_secure_regular_file "$config_path" || ! _k3s_secure_regular_file "$state_path"; then
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
fi
|
||||
[[ "$status_stage" =~ ^[a-z_]+$ ]] || {
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
}
|
||||
state_value="$(<"$state_path")"
|
||||
[[ "$(wc -c <"$state_path" | tr -d '[:space:]')" == "${#state_value}" ]] || {
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
}
|
||||
[[ "$state_value" =~ ^([a-z_]+)-([0-9a-f]{64})$ ]] || {
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
}
|
||||
config_hash="$(sha256sum -- "$config_path" | awk '{print $1}')" || {
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
}
|
||||
config_metadata="$(stat --format='%u:%a:%F' -- "$config_path" 2>/dev/null)" || config_metadata=''
|
||||
state_metadata="$(stat --format='%u:%a:%F' -- "$state_path" 2>/dev/null)" || state_metadata=''
|
||||
verify_local_encryption_config_integrity_evidence \
|
||||
"$config_metadata" "$state_metadata" "$config_hash" "$state_value" \
|
||||
"$annotation_value" "$status_stage"
|
||||
}
|
||||
|
||||
verify_local_encryption_config_integrity_evidence() {
|
||||
local config_metadata="$1" state_metadata="$2" config_hash="$3"
|
||||
local state_value="$4" annotation_value="$5" status_stage="$6" expected
|
||||
|
||||
if [[ "$config_metadata" != '0:600:regular file' ||
|
||||
"$state_metadata" != '0:600:regular file' ||
|
||||
! "$config_hash" =~ ^[0-9a-f]{64}$ ||
|
||||
! "$status_stage" =~ ^[a-z_]+$ ||
|
||||
! "$state_value" =~ ^[a-z_]+-[0-9a-f]{64}$ ]]; then
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
fi
|
||||
expected="${status_stage}-${config_hash}"
|
||||
if [[ "$state_value" == "$expected" && "$annotation_value" == "$expected" ]]; then
|
||||
printf 'match\n'
|
||||
return 0
|
||||
fi
|
||||
printf 'mismatch\n'
|
||||
return 1
|
||||
}
|
||||
|
||||
_k3s_systemctl_show() {
|
||||
systemctl show k3s \
|
||||
--property=ExecStart \
|
||||
--property=Environment \
|
||||
--property=EnvironmentFiles 2>/dev/null
|
||||
}
|
||||
|
||||
_k3s_systemd_dropin_lines() {
|
||||
local dropin_dir="${K3S_SYSTEMD_DROPIN_DIR:-/etc/systemd/system/k3s.service.d}"
|
||||
local file
|
||||
|
||||
[[ -e "$dropin_dir" ]] || return 0
|
||||
[[ -d "$dropin_dir" && ! -L "$dropin_dir" ]] || return 1
|
||||
while IFS= read -r file; do
|
||||
[[ -r "$file" && ! -L "$file" ]] || return 1
|
||||
sed -n -E '/^[[:space:]]*(Environment|EnvironmentFile)=/p' "$file"
|
||||
done < <(find "$dropin_dir" -maxdepth 1 -type f -name '*.conf' -print | sort)
|
||||
}
|
||||
|
||||
_k3s_is_dynamic_value() {
|
||||
local value="$1"
|
||||
[[ "$value" == *'$'* || "$value" == *'`'* || "$value" == *'\\'* ]]
|
||||
}
|
||||
|
||||
# Strict, non-evaluating readers for systemd's already-resolved show output and
|
||||
# EnvironmentFile syntax. Unsupported quoting, resets, or expansions fail
|
||||
# closed instead of being interpreted by a shell.
|
||||
_k3s_plain_value() {
|
||||
local value="$1" quote="'"
|
||||
[[ -n "$value" && "$value" != *' '* && "$value" != *'"'* && "$value" != *"$quote"* && "$value" != *'$'* && "$value" != *'`'* && "$value" != *'\\'* ]]
|
||||
}
|
||||
|
||||
_k3s_safe_payload() {
|
||||
local value="$1" quote="'"
|
||||
[[ -n "$value" && "$value" != *'"'* && "$value" != *"$quote"* && "$value" != *'$'* && "$value" != *'`'* && "$value" != *'\\'* ]]
|
||||
}
|
||||
|
||||
_k3s_effective_service_show() {
|
||||
local show dropins line has_environment=false
|
||||
show="$(_k3s_systemctl_show)" || return 2
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" == Environment=* || "$line" == EnvironmentFiles=* ]] || continue
|
||||
[[ -n "${line#*=}" ]] && has_environment=true
|
||||
done <<<"$show"
|
||||
if ! "$has_environment"; then
|
||||
dropins="$(_k3s_systemd_dropin_lines)" || return 2
|
||||
[[ -z "$dropins" ]] || show+=$'\n'"$dropins"
|
||||
fi
|
||||
printf '%s\n' "$show"
|
||||
}
|
||||
|
||||
_k3s_envfile_values() {
|
||||
local file="$1" key="$2" line trimmed name value text char quote='' content
|
||||
local in_record=false target_record=false continued=false
|
||||
local index length
|
||||
content="$(_k3s_read_file "$file")" || return 2
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if ! "$in_record"; then
|
||||
trimmed="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ -z "$trimmed" || "$trimmed" == \#* || "$trimmed" == \;* ]] && continue
|
||||
[[ "$trimmed" == *=* ]] || { [[ "$trimmed" == "$key" ]] && return 2; continue; }
|
||||
name="${trimmed%%=*}"
|
||||
value="${trimmed#*=}"
|
||||
target_record=false
|
||||
[[ "$name" == "$key" ]] && target_record=true
|
||||
text="$value"
|
||||
quote=''
|
||||
else
|
||||
text="$line"
|
||||
fi
|
||||
|
||||
continued=false
|
||||
length=${#text}
|
||||
for ((index=0; index<length; index++)); do
|
||||
char="${text:index:1}"
|
||||
case "$quote" in
|
||||
"'")
|
||||
[[ "$char" == "'" ]] && quote=''
|
||||
;;
|
||||
'"')
|
||||
if [[ "$char" == "\\" ]]; then
|
||||
if (( index + 1 < length )); then ((index++)); else continued=true; fi
|
||||
elif [[ "$char" == '"' ]]; then
|
||||
quote=''
|
||||
fi
|
||||
;;
|
||||
'')
|
||||
if [[ "$char" == "\\" ]]; then
|
||||
if (( index + 1 < length )); then ((index++)); else continued=true; fi
|
||||
elif [[ "$char" == "'" || "$char" == '"' ]]; then
|
||||
quote="$char"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$quote" ]] || "$continued"; then
|
||||
in_record=true
|
||||
continue
|
||||
fi
|
||||
|
||||
if "$target_record"; then
|
||||
_k3s_plain_value "$value" || return 2
|
||||
printf '%s\n' "$value"
|
||||
fi
|
||||
in_record=false
|
||||
target_record=false
|
||||
done <<<"$content"
|
||||
"$in_record" && return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
_k3s_service_env_values() {
|
||||
local show="$1" key="$2" line payload token name value candidate file result policy
|
||||
local index
|
||||
local -a words=()
|
||||
candidate=''
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" == Environment=* ]] || continue
|
||||
payload="${line#Environment=}"
|
||||
[[ -n "$payload" ]] || continue
|
||||
_k3s_safe_payload "$payload" || return 2
|
||||
for token in $payload; do
|
||||
[[ "$token" == *=* ]] || return 2
|
||||
name="${token%%=*}"; value="${token#*=}"
|
||||
[[ "$name" == "$key" ]] || continue
|
||||
[[ -n "$value" ]] || return 2
|
||||
candidate="$value"
|
||||
done
|
||||
done <<<"$show"
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" == EnvironmentFiles=* ]] || continue
|
||||
payload="${line#EnvironmentFiles=}"
|
||||
[[ -n "$payload" ]] || continue
|
||||
_k3s_safe_payload "$payload" || return 2
|
||||
read -r -a words <<<"$payload"
|
||||
for ((index=0; index<${#words[@]};)); do
|
||||
file="${words[index]}"; ((index++))
|
||||
policy=no
|
||||
if (( index < ${#words[@]} )) && [[ "${words[index]}" =~ ^\(ignore_errors=(yes|no)\)$ ]]; then
|
||||
policy="${BASH_REMATCH[1]}"; ((index++))
|
||||
fi
|
||||
[[ "$file" != \(* ]] || return 2
|
||||
file="${file#-}"
|
||||
if [[ ! -r "$file" || -L "$file" ]]; then
|
||||
[[ "$policy" == yes ]] && continue
|
||||
return 2
|
||||
fi
|
||||
result="$(_k3s_envfile_values "$file" "$key")" || return $?
|
||||
while IFS= read -r token; do [[ -n "$token" ]] && candidate="$token"; done <<<"$result"
|
||||
done
|
||||
done <<<"$show"
|
||||
[[ -n "$candidate" ]] && printf '%s\n' "$candidate"
|
||||
return 0
|
||||
}
|
||||
|
||||
_k3s_cli_values() {
|
||||
local show="$1" option="$2" line payload option_value exec_count=0
|
||||
local serialized_path serialized_argv serialized_body serialized_tail
|
||||
local serialized_prefix='{ path=' serialized_separator=' ; argv[]='
|
||||
local serialized_short_suffix=' ; ignore_errors=no ; }'
|
||||
local serialized_full_marker=' ; ignore_errors=no ; start_time='
|
||||
local serialized_full_pattern='^ ; ignore_errors=no ; start_time=\[[^][;={}]+\] ; stop_time=\[[^][;={}]+\] ; pid=[0-9]+ ; code=(\(null\)|exited|killed|dumped) ; status=[0-9]+/[0-9]+ \}$'
|
||||
local -a words=()
|
||||
local index token
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" == ExecStart=* ]] || continue
|
||||
((exec_count+=1))
|
||||
payload="${line#ExecStart=}"
|
||||
[[ -n "$payload" ]] || return 2
|
||||
_k3s_safe_payload "$payload" || return 2
|
||||
if [[ "$payload" == "$serialized_prefix"* ]]; then
|
||||
serialized_body="${payload#"$serialized_prefix"}"
|
||||
[[ "$serialized_body" == *"$serialized_separator"* ]] || return 2
|
||||
serialized_path="${serialized_body%%"$serialized_separator"*}"
|
||||
serialized_body="${serialized_body#"${serialized_path}${serialized_separator}"}"
|
||||
if [[ "$serialized_body" == *"$serialized_short_suffix" ]]; then
|
||||
serialized_argv="${serialized_body%"$serialized_short_suffix"}"
|
||||
serialized_tail="$serialized_short_suffix"
|
||||
elif [[ "$serialized_body" == *"$serialized_full_marker"* ]]; then
|
||||
serialized_argv="${serialized_body%%"$serialized_full_marker"*}"
|
||||
serialized_tail="${serialized_body#"$serialized_argv"}"
|
||||
[[ "$serialized_tail" =~ $serialized_full_pattern ]] || return 2
|
||||
else
|
||||
return 2
|
||||
fi
|
||||
[[ "$payload" == "${serialized_prefix}${serialized_path}${serialized_separator}${serialized_argv}${serialized_tail}" ]] || return 2
|
||||
[[ "$serialized_path" == /usr/local/bin/k3s ]] || return 2
|
||||
[[ -n "$serialized_argv" && "$serialized_argv" != *'argv[]='* && "$serialized_argv" != *' ; '* && "$serialized_argv" != *'{'* && "$serialized_argv" != *'}'* ]] || return 2
|
||||
payload="$serialized_argv"
|
||||
elif [[ "$payload" == *'argv[]='* || "$payload" == *'path='* || "$payload" == *'ignore_errors='* || "$payload" == *'{'* || "$payload" == *'}'* || "$payload" == *';'* ]]; then
|
||||
return 2
|
||||
fi
|
||||
read -r -a words <<<"$payload"
|
||||
(( ${#words[@]} >= 2 )) || return 2
|
||||
[[ "${words[0]##*/}" == k3s && "${words[1]}" == server ]] || return 2
|
||||
for ((index=0; index<${#words[@]}; index++)); do
|
||||
token="${words[index]}"
|
||||
case "$token" in
|
||||
"${option}" )
|
||||
if [[ "$option" == '--secrets-encryption' ]]; then printf 'true\n';
|
||||
elif (( index + 1 < ${#words[@]} )); then option_value="${words[index + 1]}"; _k3s_plain_value "$option_value" || return 2; printf '%s\n' "$option_value"; else return 2; fi
|
||||
;;
|
||||
"${option}"=*) option_value="${token#*=}"; _k3s_plain_value "$option_value" || return 2; printf '%s\n' "$option_value" ;;
|
||||
esac
|
||||
done
|
||||
done <<<"$show"
|
||||
(( exec_count == 1 )) || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
_k3s_unique_value() {
|
||||
local values="$1" value count=0 chosen=''
|
||||
while IFS= read -r value; do
|
||||
[[ -n "$value" ]] || continue
|
||||
((count++)); chosen="$value"
|
||||
done <<<"$values"
|
||||
(( count <= 1 )) || return 2
|
||||
[[ -n "$chosen" ]] && printf '%s\n' "$chosen"
|
||||
return 0
|
||||
}
|
||||
|
||||
_k3s_selected_config() {
|
||||
local show="$1" cli env default_root raw
|
||||
raw="$(_k3s_cli_values "$show" --config)" || return 2
|
||||
cli="$(_k3s_unique_value "$raw")" || return 2
|
||||
raw="$(_k3s_service_env_values "$show" K3S_CONFIG_FILE)" || return 2
|
||||
env="$(_k3s_unique_value "$raw")" || return 2
|
||||
if _k3s_is_dynamic_value "$cli" || _k3s_is_dynamic_value "$env"; then
|
||||
return 2
|
||||
fi
|
||||
default_root="${K3S_CONFIG_DIR:-/etc/rancher/k3s}"
|
||||
if [[ -n "$cli" ]]; then printf '%s\n' "$cli"; elif [[ -n "$env" ]]; then printf '%s\n' "$env"; else printf '%s\n' "${default_root}/config.yaml"; fi
|
||||
}
|
||||
|
||||
_k3s_yaml_value() {
|
||||
local config="$1" key="$2" file line raw value selected='' selected_file='' content files
|
||||
files="$(_k3s_list_yaml_files "$config")" || return 2
|
||||
while IFS= read -r file; do
|
||||
[[ -n "$file" ]] || continue
|
||||
content="$(_k3s_read_file "$file")" || return 2
|
||||
raw=''
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ "$line" == "${key}:"* ]] || continue
|
||||
value="${line#"${key}:"}"
|
||||
value="$(sed -E 's/[[:space:]]+#.*$//; s/^[[:space:]]+//; s/[[:space:]]+$//;' <<<"$value")"
|
||||
_k3s_plain_value "$value" || return 2
|
||||
[[ -z "$raw" || "$raw" == "$value" ]] || return 2
|
||||
raw="$value"
|
||||
done <<<"$content"
|
||||
if [[ -n "$raw" ]]; then selected="$raw"; selected_file="$file"; fi
|
||||
done <<<"$files"
|
||||
[[ -n "$selected" ]] && printf '%s|%s\n' "$selected_file" "$selected"
|
||||
return 0
|
||||
}
|
||||
|
||||
_k3s_resolve_key() {
|
||||
local show="$1" cli_option="$2" env_key="$3" yaml_key="$4" cli env config yaml raw
|
||||
raw="$(_k3s_cli_values "$show" "$cli_option")" || return 2
|
||||
cli="$(_k3s_unique_value "$raw")" || return 2
|
||||
[[ -z "$cli" ]] || { printf 'command-line|%s\n' "$cli"; return 0; }
|
||||
raw="$(_k3s_service_env_values "$show" "$env_key")" || return 2
|
||||
env="$(_k3s_unique_value "$raw")" || return 2
|
||||
[[ -z "$env" ]] || { printf 'environment|%s\n' "$env"; return 0; }
|
||||
config="$(_k3s_selected_config "$show")" || return 2
|
||||
yaml="$(_k3s_yaml_value "$config" "$yaml_key")" || return 2
|
||||
[[ -z "$yaml" ]] || printf '%s\n' "$yaml"
|
||||
}
|
||||
|
||||
detect_effective_encryption_config_owner() {
|
||||
local show enable provider enable_source enable_value provider_source provider_value
|
||||
show="$(_k3s_effective_service_show)" || { printf 'ambiguous\n'; return 0; }
|
||||
enable="$(_k3s_resolve_key "$show" --secrets-encryption K3S_SECRETS_ENCRYPTION secrets-encryption)" || { printf 'ambiguous\n'; return 0; }
|
||||
provider="$(_k3s_resolve_key "$show" --secrets-encryption-provider K3S_SECRETS_ENCRYPTION_PROVIDER secrets-encryption-provider)" || { printf 'ambiguous\n'; return 0; }
|
||||
[[ -n "$enable" ]] || { printf 'ambiguous\n'; return 0; }
|
||||
IFS='|' read -r enable_source enable_value <<<"$enable"
|
||||
[[ "$enable_value" == true ]] || { printf 'ambiguous\n'; return 0; }
|
||||
if [[ -n "$provider" ]]; then IFS='|' read -r provider_source provider_value <<<"$provider"; [[ "$provider_value" == aescbc || "$provider_value" == secretbox ]] || { printf 'ambiguous\n'; return 0; }; printf '%s/%s\n' "$provider_value" "$provider_source"; else printf 'aescbc/implicit-default\n'; fi
|
||||
}
|
||||
|
||||
_k3s_datastore_local_evidence() {
|
||||
local data_dir="$1"
|
||||
local state_db="${data_dir}/server/db/state.db"
|
||||
local etcd_dir="${data_dir}/server/db/etcd"
|
||||
if [[ -f "$state_db" && -d "$etcd_dir" ]]; then printf 'ambiguous\n';
|
||||
elif [[ -f "$state_db" ]]; then printf 'sqlite\n';
|
||||
elif [[ -d "$etcd_dir" ]]; then printf 'embedded-etcd\n';
|
||||
else printf 'none\n'; fi
|
||||
}
|
||||
|
||||
detect_k3s_datastore() {
|
||||
local show data endpoint source value evidence
|
||||
show="$(_k3s_effective_service_show)" || { printf 'ambiguous\n'; return 0; }
|
||||
data="$(_k3s_resolve_key "$show" --data-dir K3S_DATA_DIR data-dir)" || { printf 'ambiguous\n'; return 0; }
|
||||
endpoint="$(_k3s_resolve_key "$show" --datastore-endpoint K3S_DATASTORE_ENDPOINT datastore-endpoint)" || { printf 'ambiguous\n'; return 0; }
|
||||
if [[ -n "$data" ]]; then IFS='|' read -r source value <<<"$data"; data="$value"; else source=default; data=/var/lib/rancher/k3s; fi
|
||||
_k3s_is_dynamic_value "$data" || [[ "$data" == /* ]] || { printf 'ambiguous\n'; return 0; }
|
||||
[[ "$source" == default || "$data" == /var/lib/rancher/k3s ]] || { printf 'ambiguous\n'; return 0; }
|
||||
evidence="$(_k3s_datastore_local_evidence "$data")" || { printf 'ambiguous\n'; return 0; }
|
||||
if [[ -n "$endpoint" ]]; then [[ "$evidence" == none ]] && printf 'external\n' || printf 'ambiguous\n';
|
||||
elif [[ "$evidence" == sqlite || "$evidence" == embedded-etcd ]]; then printf '%s\n' "$evidence";
|
||||
else printf 'ambiguous\n'; fi
|
||||
}
|
||||
|
||||
_k3s_now_seconds() {
|
||||
printf '%s\n' "$SECONDS"
|
||||
}
|
||||
|
||||
_k3s_timeout_before_deadline() {
|
||||
local deadline="$1" now remaining command_timeout=9
|
||||
now="$(_k3s_now_seconds)" || return 1
|
||||
[[ "$now" =~ ^[0-9]+$ ]] || return 1
|
||||
remaining=$((deadline - now))
|
||||
(( remaining > 1 )) || return 1
|
||||
(( command_timeout < remaining )) || command_timeout=$((remaining - 1))
|
||||
printf '%s\n' "$command_timeout"
|
||||
}
|
||||
|
||||
_k3s_sleep_until_poll() {
|
||||
local deadline="$1" next_poll="$2" now delay remaining
|
||||
now="$(_k3s_now_seconds)" || return 1
|
||||
[[ "$now" =~ ^[0-9]+$ ]] || return 1
|
||||
(( now < deadline )) || return 1
|
||||
delay=$((next_poll - now))
|
||||
(( delay > 0 )) || return 0
|
||||
remaining=$((deadline - now))
|
||||
(( delay < remaining )) || delay="$remaining"
|
||||
sleep "$delay"
|
||||
}
|
||||
|
||||
_k3s_read_encryption_status() {
|
||||
local command_timeout="$1"
|
||||
[[ "$command_timeout" =~ ^[1-9]$ ]] || return 1
|
||||
timeout --signal=TERM --kill-after=1s "${command_timeout}s" \
|
||||
sudo --non-interactive k3s secrets-encrypt status --output json 2>/dev/null
|
||||
}
|
||||
|
||||
wait_for_k3s_api() {
|
||||
local attempt started_at deadline next_poll command_timeout
|
||||
started_at="$(_k3s_now_seconds)" || return 1
|
||||
[[ "$started_at" =~ ^[0-9]+$ ]] || return 1
|
||||
deadline=$((started_at + 600))
|
||||
for ((attempt=1; attempt<=60; attempt++)); do
|
||||
command_timeout="$(_k3s_timeout_before_deadline "$deadline")" || return 1
|
||||
if timeout --signal=TERM --kill-after=1s "${command_timeout}s" \
|
||||
sudo --non-interactive k3s kubectl get --raw=/readyz >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
(( attempt < 60 )) || break
|
||||
next_poll=$((started_at + attempt * 10))
|
||||
_k3s_sleep_until_poll "$deadline" "$next_poll" || return 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_reencrypt_finished() {
|
||||
local rotate_rc="$1"
|
||||
local attempt status_json status_class stage started_at deadline next_poll command_timeout
|
||||
|
||||
# rotate-keys can return non-zero after the server accepted the operation;
|
||||
# a later authoritative finished status is therefore allowed to succeed.
|
||||
[[ "$rotate_rc" =~ ^[0-9]+$ ]] || return 1
|
||||
started_at="$(_k3s_now_seconds)" || return 1
|
||||
[[ "$started_at" =~ ^[0-9]+$ ]] || return 1
|
||||
deadline=$((started_at + 600))
|
||||
for ((attempt=1; attempt<=60; attempt++)); do
|
||||
command_timeout="$(_k3s_timeout_before_deadline "$deadline")" || return 1
|
||||
status_json="$(_k3s_read_encryption_status "$command_timeout")" || return 1
|
||||
status_class="$(classify_encryption_status "$status_json")"
|
||||
[[ "$status_class" != hash_mismatch && "$status_class" != invalid ]] || return 1
|
||||
stage="$(jq -r '.stage' <<<"$status_json" 2>/dev/null)" || return 1
|
||||
[[ "$stage" != start ]] || return 1
|
||||
[[ "$stage" == reencrypt_finished ]] && return 0
|
||||
[[ "$stage" == reencrypt_active ]] || return 1
|
||||
(( attempt < 60 )) || break
|
||||
next_poll=$((started_at + attempt * 10))
|
||||
_k3s_sleep_until_poll "$deadline" "$next_poll" || return 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user