fix(k8s): harden control-plane backup safety
This commit is contained in:
@@ -34,7 +34,7 @@ is_nonzero() { [[ "$1" -ne 0 ]]; }
|
||||
path_exists() { [[ -e "$1" ]]; }
|
||||
path_does_not_exist() { [[ ! -e "$1" ]]; }
|
||||
file_contains() { grep -Fq -- "$2" "$1"; }
|
||||
file_lacks() { ! grep -Fq -- "$2" "$1"; }
|
||||
file_lacks() { [[ ! -e "$1" ]] || ! grep -Fq -- "$2" "$1"; }
|
||||
file_line_count_is() { [[ "$(grep -Fc -- "$2" "$1")" -eq "$3" ]]; }
|
||||
|
||||
matching_archive_count_is() {
|
||||
@@ -74,6 +74,8 @@ set -Eeuo pipefail
|
||||
[[ "${1:-}" == "-r" ]]
|
||||
archive="${2:?archive path required}"
|
||||
shift 2
|
||||
[[ "${1:-}" == "--" ]]
|
||||
shift
|
||||
: > "$archive"
|
||||
for entry in "$@"; do
|
||||
source_path="/$entry"
|
||||
@@ -154,18 +156,10 @@ fi
|
||||
|
||||
if [[ "$command" == *" -s -- "* ]]; then
|
||||
value_count="${#quoted_values[@]}"
|
||||
prefix="${quoted_values[value_count-2]}"
|
||||
keep_count="${quoted_values[value_count-1]}"
|
||||
shopt -s nullglob
|
||||
archives=("${FAKE_REMOTE_ROOT}/${prefix}_"*.zip)
|
||||
shopt -u nullglob
|
||||
if (( ${#archives[@]} > keep_count )); then
|
||||
mapfile -t sorted_archives < <(ls -1t -- "${archives[@]}")
|
||||
for ((i = keep_count; i < ${#sorted_archives[@]}; i++)); do
|
||||
rm -f -- "${sorted_archives[$i]}"
|
||||
done
|
||||
fi
|
||||
cat >/dev/null
|
||||
(
|
||||
cd -- "$FAKE_REMOTE_ROOT"
|
||||
bash -s -- "$FAKE_REMOTE_ROOT" "${quoted_values[value_count-2]}" "${quoted_values[value_count-1]}"
|
||||
)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -208,7 +202,7 @@ run_backup() {
|
||||
shift 4
|
||||
local -a overrides=("$@")
|
||||
|
||||
env "${overrides[@]}" \
|
||||
env \
|
||||
K3S_CONFIG_DIR="${fixture_root}/etc/rancher/k3s" \
|
||||
K3S_SERVER_DIR="${fixture_root}/var/lib/rancher/k3s/server" \
|
||||
K3S_SERVICE_FILES="${fixture_root}/etc/systemd/system/k3s.service:${fixture_root}/etc/systemd/system/k3s.service.env:${fixture_root}/etc/default/k3s:${fixture_root}/etc/sysconfig/k3s" \
|
||||
@@ -229,6 +223,7 @@ run_backup() {
|
||||
PATH="${TMPD}/bin:${PATH}" \
|
||||
TMPD="$TMPD" \
|
||||
FAKE_REMOTE_ROOT="$remote_root" \
|
||||
"${overrides[@]}" \
|
||||
bash "$SCRIPT_UNDER_TEST" >"$log_file" 2>&1
|
||||
}
|
||||
|
||||
@@ -279,6 +274,11 @@ for day in 1 2 3 4 5; do
|
||||
done
|
||||
printf 'legacy backup\n' > "${success_remote}/k8s-backup_2026-07-01.7z"
|
||||
printf 'other zip\n' > "${success_remote}/other-prefix_2026-07-01.zip"
|
||||
newline_archive="${success_remote}/k3s-control-plane-config_2026-06-01_12-00-00.zip"$'\nunrelated.zip'
|
||||
touch -t "202606011200" "$newline_archive"
|
||||
: > "$newline_archive"
|
||||
touch -t "202606011200" "$newline_archive"
|
||||
: > "${success_remote}/unrelated.zip"
|
||||
: > "${TMPD}/curl_trace.log"
|
||||
: > "${TMPD}/unzip_trace.log"
|
||||
: > "${TMPD}/zip_invocations.log"
|
||||
@@ -302,11 +302,42 @@ assert "archive excludes kine socket" file_lacks "${success_remote}/k3s-control-
|
||||
assert "retention leaves exactly four newest matching zip archives" matching_archive_count_is "$success_remote" 4
|
||||
assert "retention removes the two oldest matching zip archives" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-01_12-00-00.zip"
|
||||
assert "retention removes the next-oldest matching zip archive" path_does_not_exist "${success_remote}/k3s-control-plane-config_2026-07-02_12-00-00.zip"
|
||||
assert "retention removes matching old archives with newlines in their filenames" path_does_not_exist "$newline_archive"
|
||||
assert "retention keeps the standalone unrelated.zip sentinel" path_exists "${success_remote}/unrelated.zip"
|
||||
assert "retention keeps unrelated legacy .7z backups" path_exists "${success_remote}/k8s-backup_2026-07-01.7z"
|
||||
assert "retention keeps unrelated zip backups" path_exists "${success_remote}/other-prefix_2026-07-01.zip"
|
||||
assert "local temporary archive state is removed" directory_is_empty "$success_local_tmp"
|
||||
assert "successful backup sends a success notification" file_contains "${TMPD}/curl_trace.log" "http://example.invalid/success"
|
||||
|
||||
# A configured source path ending in server/db/.. must be rejected before zip is
|
||||
# invoked, rather than recursively archiving server/db and its sibling socket.
|
||||
source_scope_dir="${TMPD}/source-scope"
|
||||
source_scope_fixture="${source_scope_dir}/fixture"
|
||||
source_scope_remote="${source_scope_dir}/remote"
|
||||
source_scope_local_tmp="${source_scope_dir}/local-tmp"
|
||||
source_scope_archive="${source_scope_remote}/k3s-control-plane-config_2026-07-21_12-00-00.zip"
|
||||
mkdir -p "$source_scope_remote" "$source_scope_local_tmp"
|
||||
create_source_fixture "$source_scope_fixture"
|
||||
: > "${TMPD}/zip_invocations.log"
|
||||
|
||||
set +e
|
||||
run_backup \
|
||||
"$source_scope_fixture" \
|
||||
"$source_scope_remote" \
|
||||
"$source_scope_local_tmp" \
|
||||
"${source_scope_dir}/run.log" \
|
||||
"K3S_CONFIG_DIR=${source_scope_fixture}/var/lib/rancher/k3s/server/db/.."
|
||||
source_scope_exit_code=$?
|
||||
set -e
|
||||
|
||||
assert "unsafe source override exits non-zero" is_nonzero "$source_scope_exit_code"
|
||||
assert "unsafe source override reports the source-scope error" file_contains "${source_scope_dir}/run.log" "Configured source path is not safe to archive"
|
||||
assert "unsafe source override is rejected before zip runs" file_lacks "${TMPD}/zip_invocations.log" "server/db/.."
|
||||
assert "unsafe source override archive excludes server database content" file_lacks "$source_scope_archive" "state.db"
|
||||
assert "unsafe source override archive excludes kine socket" file_lacks "$source_scope_archive" "kine.sock"
|
||||
assert "unsafe source override does not publish an archive" directory_is_empty "$source_scope_remote"
|
||||
assert "unsafe source override removes local temporary state" directory_is_empty "$source_scope_local_tmp"
|
||||
|
||||
# Upload failure: the partial remote temporary file is removed, no final archive
|
||||
# is published, local temporary data is removed, and one safe error notification
|
||||
# is attempted.
|
||||
|
||||
Reference in New Issue
Block a user