fix(agent99): fail closed host110 runtime replay
This commit is contained in:
@@ -9,14 +9,74 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /backup/scripts/* ]] && [ "${BACKUP_RUNTIME_SHARED_LOCK_HELD:-0}" != "1" ]; then
|
||||
resolve_backup_runtime_script_path() {
|
||||
local raw_path="$1" candidate="" resolved="" resolved_directory=""
|
||||
[ -n "${raw_path}" ] || return 1
|
||||
case "${raw_path}" in /*) candidate="${raw_path}" ;; *) candidate="${PWD}/${raw_path}" ;; esac
|
||||
if command -v realpath >/dev/null 2>&1; then
|
||||
resolved="$(realpath -e -- "${candidate}" 2>/dev/null || realpath -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then printf '%s\n' "${resolved}"; return 0; fi
|
||||
fi
|
||||
if command -v readlink >/dev/null 2>&1; then
|
||||
resolved="$(readlink -f -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then printf '%s\n' "${resolved}"; return 0; fi
|
||||
fi
|
||||
[ ! -L "${candidate}" ] || return 1
|
||||
resolved_directory="$(cd -P -- "$(dirname -- "${candidate}")" 2>/dev/null && pwd -P)" || return 1
|
||||
[ -e "${resolved_directory}/$(basename -- "${candidate}")" ] || return 1
|
||||
printf '%s/%s\n' "${resolved_directory}" "$(basename -- "${candidate}")"
|
||||
}
|
||||
|
||||
backup_runtime_inherited_fd_status() {
|
||||
local expected_path="$1" fd_path="" fd_target=""
|
||||
if [ -d "/proc/$$/fd" ]; then
|
||||
fd_path="/proc/$$/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
fd_target="$(resolve_backup_runtime_script_path "${fd_path}")" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
return 0
|
||||
fi
|
||||
fd_path="/dev/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
command -v python3 >/dev/null 2>&1 || return 2
|
||||
fd_target="$(python3 - <<'PY'
|
||||
import fcntl
|
||||
import os
|
||||
|
||||
try:
|
||||
target = os.readlink("/dev/fd/197")
|
||||
except OSError:
|
||||
value = fcntl.fcntl(197, getattr(fcntl, "F_GETPATH", 50), b"\0" * 1024)
|
||||
target = os.fsdecode(value.split(b"\0", 1)[0])
|
||||
print(os.path.realpath(target))
|
||||
PY
|
||||
)" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
}
|
||||
|
||||
runtime_script_path="$(resolve_backup_runtime_script_path "${BASH_SOURCE[0]:-}")" || {
|
||||
echo "backup runtime gate unavailable: script identity unresolved" >&2
|
||||
exit 69
|
||||
}
|
||||
if [[ "${runtime_script_path}" == /backup/scripts/* ]]; then
|
||||
command -v flock >/dev/null 2>&1 || { echo "backup runtime gate unavailable" >&2; exit 69; }
|
||||
[ ! -L /tmp/agent99-host110-backup-runtime.lock ] || { echo "backup runtime gate unsafe" >&2; exit 69; }
|
||||
(umask 077; : >> /tmp/agent99-host110-backup-runtime.lock)
|
||||
[ -f /tmp/agent99-host110-backup-runtime.lock ] && [ -O /tmp/agent99-host110-backup-runtime.lock ] || { echo "backup runtime gate owner invalid" >&2; exit 69; }
|
||||
exec 197>>/tmp/agent99-host110-backup-runtime.lock
|
||||
runtime_lock_path="$(resolve_backup_runtime_script_path /tmp/agent99-host110-backup-runtime.lock)" \
|
||||
|| { echo "backup runtime gate unavailable: lock identity unresolved" >&2; exit 69; }
|
||||
if backup_runtime_inherited_fd_status "${runtime_lock_path}"; then
|
||||
:
|
||||
else
|
||||
inherited_fd_status=$?
|
||||
if [ "${inherited_fd_status}" -eq 1 ]; then
|
||||
exec 197>>/tmp/agent99-host110-backup-runtime.lock
|
||||
else
|
||||
echo "backup runtime gate unsafe: inherited fd 197 identity mismatch" >&2
|
||||
exit 69
|
||||
fi
|
||||
fi
|
||||
flock -s -w 60 197 || { echo "backup runtime deployment is active; retry later" >&2; exit 75; }
|
||||
export BACKUP_RUNTIME_SHARED_LOCK_HELD=1
|
||||
fi
|
||||
|
||||
BACKUP_BASE="${BACKUP_BASE:-/backup}"
|
||||
|
||||
@@ -3,14 +3,78 @@
|
||||
|
||||
# Production backup entrypoints share this stable gate. Agent99 takes the
|
||||
# exclusive side while promoting a complete, digest-bound runtime bundle.
|
||||
resolve_backup_runtime_source_path() {
|
||||
local raw_path="$1"
|
||||
local candidate=""
|
||||
local resolved=""
|
||||
local resolved_directory=""
|
||||
[ -n "${raw_path}" ] || return 1
|
||||
case "${raw_path}" in
|
||||
/*) candidate="${raw_path}" ;;
|
||||
*) candidate="${PWD}/${raw_path}" ;;
|
||||
esac
|
||||
if command -v realpath >/dev/null 2>&1; then
|
||||
resolved="$(realpath -e -- "${candidate}" 2>/dev/null || realpath -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then
|
||||
printf '%s\n' "${resolved}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if command -v readlink >/dev/null 2>&1; then
|
||||
resolved="$(readlink -f -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then
|
||||
printf '%s\n' "${resolved}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
[ ! -L "${candidate}" ] || return 1
|
||||
resolved_directory="$(cd -P -- "$(dirname -- "${candidate}")" 2>/dev/null && pwd -P)" || return 1
|
||||
[ -e "${resolved_directory}/$(basename -- "${candidate}")" ] || return 1
|
||||
printf '%s/%s\n' "${resolved_directory}" "$(basename -- "${candidate}")"
|
||||
}
|
||||
|
||||
backup_runtime_inherited_fd_status() {
|
||||
local expected_path="$1"
|
||||
local fd_path=""
|
||||
local fd_target=""
|
||||
if [ -d "/proc/$$/fd" ]; then
|
||||
fd_path="/proc/$$/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
fd_target="$(resolve_backup_runtime_source_path "${fd_path}")" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
return 0
|
||||
fi
|
||||
fd_path="/dev/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
command -v python3 >/dev/null 2>&1 || return 2
|
||||
fd_target="$(python3 - <<'PY'
|
||||
import fcntl
|
||||
import os
|
||||
|
||||
try:
|
||||
target = os.readlink("/dev/fd/197")
|
||||
except OSError:
|
||||
value = fcntl.fcntl(197, getattr(fcntl, "F_GETPATH", 50), b"\0" * 1024)
|
||||
target = os.fsdecode(value.split(b"\0", 1)[0])
|
||||
print(os.path.realpath(target))
|
||||
PY
|
||||
)" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
}
|
||||
|
||||
acquire_backup_runtime_shared_lock() {
|
||||
local caller_path="${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}"
|
||||
local caller_path=""
|
||||
local lock_path="/tmp/agent99-host110-backup-runtime.lock"
|
||||
local lock_canonical=""
|
||||
local inherited_fd_status=0
|
||||
caller_path="$(resolve_backup_runtime_source_path "${BASH_SOURCE[1]:-${BASH_SOURCE[0]:-}}")" || {
|
||||
echo "backup runtime gate unavailable: caller identity unresolved" >&2
|
||||
return 69
|
||||
}
|
||||
case "${caller_path}" in
|
||||
/backup/scripts/*) ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
[ "${BACKUP_RUNTIME_SHARED_LOCK_HELD:-0}" != "1" ] || return 0
|
||||
command -v flock >/dev/null 2>&1 || {
|
||||
echo "backup runtime gate unavailable: flock missing" >&2
|
||||
return 69
|
||||
@@ -21,12 +85,25 @@ acquire_backup_runtime_shared_lock() {
|
||||
}
|
||||
(umask 077; : >> "${lock_path}") || return 69
|
||||
[ -f "${lock_path}" ] && [ -O "${lock_path}" ] || return 69
|
||||
exec 197>>"${lock_path}"
|
||||
lock_canonical="$(resolve_backup_runtime_source_path "${lock_path}")" || {
|
||||
echo "backup runtime gate unavailable: lock identity unresolved" >&2
|
||||
return 69
|
||||
}
|
||||
if backup_runtime_inherited_fd_status "${lock_canonical}"; then
|
||||
:
|
||||
else
|
||||
inherited_fd_status=$?
|
||||
if [ "${inherited_fd_status}" -eq 1 ]; then
|
||||
exec 197>>"${lock_path}"
|
||||
else
|
||||
echo "backup runtime gate unsafe: inherited fd 197 identity mismatch" >&2
|
||||
return 69
|
||||
fi
|
||||
fi
|
||||
flock -s -w 60 197 || {
|
||||
echo "backup runtime deployment is active; retry later" >&2
|
||||
return 75
|
||||
}
|
||||
export BACKUP_RUNTIME_SHARED_LOCK_HELD=1
|
||||
}
|
||||
|
||||
acquire_backup_runtime_shared_lock || {
|
||||
|
||||
@@ -5,14 +5,74 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /backup/scripts/* ]] && [ "${BACKUP_RUNTIME_SHARED_LOCK_HELD:-0}" != "1" ]; then
|
||||
resolve_backup_runtime_script_path() {
|
||||
local raw_path="$1" candidate="" resolved="" resolved_directory=""
|
||||
[ -n "${raw_path}" ] || return 1
|
||||
case "${raw_path}" in /*) candidate="${raw_path}" ;; *) candidate="${PWD}/${raw_path}" ;; esac
|
||||
if command -v realpath >/dev/null 2>&1; then
|
||||
resolved="$(realpath -e -- "${candidate}" 2>/dev/null || realpath -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then printf '%s\n' "${resolved}"; return 0; fi
|
||||
fi
|
||||
if command -v readlink >/dev/null 2>&1; then
|
||||
resolved="$(readlink -f -- "${candidate}" 2>/dev/null)" || resolved=""
|
||||
if [ -n "${resolved}" ]; then printf '%s\n' "${resolved}"; return 0; fi
|
||||
fi
|
||||
[ ! -L "${candidate}" ] || return 1
|
||||
resolved_directory="$(cd -P -- "$(dirname -- "${candidate}")" 2>/dev/null && pwd -P)" || return 1
|
||||
[ -e "${resolved_directory}/$(basename -- "${candidate}")" ] || return 1
|
||||
printf '%s/%s\n' "${resolved_directory}" "$(basename -- "${candidate}")"
|
||||
}
|
||||
|
||||
backup_runtime_inherited_fd_status() {
|
||||
local expected_path="$1" fd_path="" fd_target=""
|
||||
if [ -d "/proc/$$/fd" ]; then
|
||||
fd_path="/proc/$$/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
fd_target="$(resolve_backup_runtime_script_path "${fd_path}")" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
return 0
|
||||
fi
|
||||
fd_path="/dev/fd/197"
|
||||
[ -e "${fd_path}" ] || [ -L "${fd_path}" ] || return 1
|
||||
command -v python3 >/dev/null 2>&1 || return 2
|
||||
fd_target="$(python3 - <<'PY'
|
||||
import fcntl
|
||||
import os
|
||||
|
||||
try:
|
||||
target = os.readlink("/dev/fd/197")
|
||||
except OSError:
|
||||
value = fcntl.fcntl(197, getattr(fcntl, "F_GETPATH", 50), b"\0" * 1024)
|
||||
target = os.fsdecode(value.split(b"\0", 1)[0])
|
||||
print(os.path.realpath(target))
|
||||
PY
|
||||
)" || return 2
|
||||
[ "${fd_target}" = "${expected_path}" ] || return 2
|
||||
}
|
||||
|
||||
runtime_script_path="$(resolve_backup_runtime_script_path "${BASH_SOURCE[0]:-}")" || {
|
||||
echo "backup runtime gate unavailable: script identity unresolved" >&2
|
||||
exit 69
|
||||
}
|
||||
if [[ "${runtime_script_path}" == /backup/scripts/* ]]; then
|
||||
command -v flock >/dev/null 2>&1 || { echo "backup runtime gate unavailable" >&2; exit 69; }
|
||||
[ ! -L /tmp/agent99-host110-backup-runtime.lock ] || { echo "backup runtime gate unsafe" >&2; exit 69; }
|
||||
(umask 077; : >> /tmp/agent99-host110-backup-runtime.lock)
|
||||
[ -f /tmp/agent99-host110-backup-runtime.lock ] && [ -O /tmp/agent99-host110-backup-runtime.lock ] || { echo "backup runtime gate owner invalid" >&2; exit 69; }
|
||||
exec 197>>/tmp/agent99-host110-backup-runtime.lock
|
||||
runtime_lock_path="$(resolve_backup_runtime_script_path /tmp/agent99-host110-backup-runtime.lock)" \
|
||||
|| { echo "backup runtime gate unavailable: lock identity unresolved" >&2; exit 69; }
|
||||
if backup_runtime_inherited_fd_status "${runtime_lock_path}"; then
|
||||
:
|
||||
else
|
||||
inherited_fd_status=$?
|
||||
if [ "${inherited_fd_status}" -eq 1 ]; then
|
||||
exec 197>>/tmp/agent99-host110-backup-runtime.lock
|
||||
else
|
||||
echo "backup runtime gate unsafe: inherited fd 197 identity mismatch" >&2
|
||||
exit 69
|
||||
fi
|
||||
fi
|
||||
flock -s -w 60 197 || { echo "backup runtime deployment is active; retry later" >&2; exit 75; }
|
||||
export BACKUP_RUNTIME_SHARED_LOCK_HELD=1
|
||||
fi
|
||||
|
||||
DUMP_ZIP="${AIOPS_GITEA_FULL_BACKUP_DRILL_DUMP_ZIP:-}"
|
||||
|
||||
@@ -27,11 +27,46 @@ RUNTIME_LOCK = Path("/tmp/agent99-host110-backup-runtime.lock")
|
||||
|
||||
|
||||
def acquire_runtime_lock():
|
||||
if Path(__file__).resolve().parent != Path("/backup/scripts") or os.environ.get("BACKUP_RUNTIME_SHARED_LOCK_HELD") == "1":
|
||||
if Path(__file__).resolve().parent != Path("/backup/scripts"):
|
||||
return None
|
||||
if RUNTIME_LOCK.is_symlink():
|
||||
raise RuntimeError("backup_runtime_gate_unsafe")
|
||||
handle = RUNTIME_LOCK.open("a+")
|
||||
inherited_fd_path: Path | None = None
|
||||
proc_fd_root = Path(f"/proc/{os.getpid()}/fd")
|
||||
if proc_fd_root.is_dir():
|
||||
candidate = proc_fd_root / "197"
|
||||
if candidate.exists() or candidate.is_symlink():
|
||||
inherited_fd_path = candidate
|
||||
else:
|
||||
candidate = Path("/dev/fd/197")
|
||||
if candidate.exists() or candidate.is_symlink():
|
||||
inherited_fd_path = candidate
|
||||
|
||||
if inherited_fd_path is None:
|
||||
handle = RUNTIME_LOCK.open("a+")
|
||||
else:
|
||||
if not RUNTIME_LOCK.is_file():
|
||||
raise RuntimeError("backup_runtime_gate_unavailable")
|
||||
if proc_fd_root.is_dir():
|
||||
try:
|
||||
inherited_target = inherited_fd_path.resolve(strict=True)
|
||||
expected_target = RUNTIME_LOCK.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise RuntimeError("backup_runtime_inherited_fd_unresolved") from exc
|
||||
else:
|
||||
try:
|
||||
raw_target = fcntl.fcntl(197, getattr(fcntl, "F_GETPATH", 50), b"\0" * 1024)
|
||||
inherited_target = Path(os.fsdecode(raw_target.split(b"\0", 1)[0])).resolve(strict=True)
|
||||
expected_target = RUNTIME_LOCK.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise RuntimeError("backup_runtime_inherited_fd_unresolved") from exc
|
||||
if inherited_target != expected_target:
|
||||
raise RuntimeError("backup_runtime_inherited_fd_mismatch")
|
||||
try:
|
||||
handle = os.fdopen(os.dup(197), "a+")
|
||||
except OSError as exc:
|
||||
raise RuntimeError("backup_runtime_inherited_fd_unavailable") from exc
|
||||
|
||||
if RUNTIME_LOCK.stat().st_uid != os.getuid():
|
||||
handle.close()
|
||||
raise RuntimeError("backup_runtime_gate_owner_invalid")
|
||||
|
||||
Reference in New Issue
Block a user