229 lines
6.6 KiB
Bash
Executable File
229 lines
6.6 KiB
Bash
Executable File
#!/bin/bash
|
||
# =============================================================================
|
||
# WOOO AIOps - Credential escrow verification marker
|
||
# 2026-05-06 ogt + Codex: 建立不含 secret 的人工金庫覆核 marker。
|
||
#
|
||
# 這個腳本不讀、不寫、不列印任何 credential。它只在人工確認密碼管理器
|
||
# 或離線加密金庫可用後,寫入 timestamp / item / evidence_id。
|
||
# =============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
# This helper is often used to print copy/paste-safe operator commands.
|
||
# Keep the shared library startup banner quiet by default; real marker writes
|
||
# still emit their explicit success line below.
|
||
export BACKUP_COMMON_QUIET="${BACKUP_COMMON_QUIET:-1}"
|
||
source "$(dirname "$0")/common.sh"
|
||
|
||
ESCROW_DIR="${BACKUP_BASE}/escrow-evidence"
|
||
BACKUP_HEALTH_EXPORTER="${BACKUP_HEALTH_EXPORTER:-/home/wooo/scripts/backup-health-textfile-exporter.py}"
|
||
BACKUP_HEALTH_TEXTFILE_DIR="${BACKUP_HEALTH_TEXTFILE_DIR:-/home/wooo/node_exporter_textfiles}"
|
||
TEXTFILE_REFRESH_ENABLED="${TEXTFILE_REFRESH_ENABLED:-1}"
|
||
ITEM=""
|
||
EVIDENCE_ID=""
|
||
NOTE=""
|
||
MODE="write"
|
||
DRY_RUN=0
|
||
|
||
ALLOWED_ITEMS=(
|
||
"restic_repository_password"
|
||
"offsite_provider_credentials"
|
||
"break_glass_admin_credentials"
|
||
"dns_registrar_recovery"
|
||
"oauth_ai_provider_recovery"
|
||
)
|
||
|
||
usage() {
|
||
cat <<'USAGE'
|
||
Usage:
|
||
mark-credential-escrow-verified.sh --item <item> --evidence-id <id> [--note <short-note>]
|
||
mark-credential-escrow-verified.sh --item <item> --evidence-id <id> --dry-run
|
||
mark-credential-escrow-verified.sh --status
|
||
mark-credential-escrow-verified.sh --missing-commands
|
||
|
||
Allowed items:
|
||
restic_repository_password
|
||
offsite_provider_credentials
|
||
break_glass_admin_credentials
|
||
dns_registrar_recovery
|
||
oauth_ai_provider_recovery
|
||
|
||
Rules:
|
||
- evidence-id must be a non-secret reference such as a vault item id, ticket id,
|
||
sealed envelope id, or recovery checklist id.
|
||
- Do not pass passwords, tokens, recovery codes, or secret URLs.
|
||
- Placeholder values such as EVIDENCE_ID_FOR_* or VAULT-ITEM-ID are rejected.
|
||
USAGE
|
||
}
|
||
|
||
is_allowed_item() {
|
||
local item="$1"
|
||
for allowed in "${ALLOWED_ITEMS[@]}"; do
|
||
[ "${item}" = "${allowed}" ] && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
reject_suspicious_value() {
|
||
local label="$1"
|
||
local value="$2"
|
||
if [ "${#value}" -gt 160 ]; then
|
||
echo "${label} 太長;只允許短 evidence id,不允許 secret material" >&2
|
||
return 1
|
||
fi
|
||
if grep -Eq '(BEGIN |PRIVATE KEY|[A-Za-z0-9+/]{40,}={0,2})' <<<"${value}" \
|
||
|| grep -Eiq '(password|token|secret)[[:space:]]*[:=]' <<<"${value}"; then
|
||
echo "${label} 看起來可能含 secret;拒絕寫入 marker" >&2
|
||
return 1
|
||
fi
|
||
if grep -Eiq '^(EVIDENCE_ID_FOR_|VAULT-ITEM-ID$|TODO$|TBD$|CHANGE_ME$|CHANGEME$|REPLACE_ME$|EXAMPLE)' <<<"${value}"; then
|
||
echo "${label} 是 placeholder;請換成真實、非 secret 的證據 ID" >&2
|
||
return 1
|
||
fi
|
||
if grep -Eiq 'https?://|ssh://|file://' <<<"${value}"; then
|
||
echo "${label} 看起來像 URL;請改用不含 secret 的短 evidence id" >&2
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
status() {
|
||
install -d -m 750 "${ESCROW_DIR}"
|
||
for item in "${ALLOWED_ITEMS[@]}"; do
|
||
local path="${ESCROW_DIR}/${item}.last_verified"
|
||
if [ -f "${path}" ]; then
|
||
printf '%s present ' "${item}"
|
||
sed -n 's/^timestamp=//p;s/^evidence_id=/evidence_id=/p' "${path}" | tr '\n' ' '
|
||
printf '\n'
|
||
else
|
||
printf '%s missing\n' "${item}"
|
||
fi
|
||
done
|
||
}
|
||
|
||
print_missing_commands() {
|
||
install -d -m 750 "${ESCROW_DIR}"
|
||
local missing=0
|
||
for item in "${ALLOWED_ITEMS[@]}"; do
|
||
local path="${ESCROW_DIR}/${item}.last_verified"
|
||
[ -f "${path}" ] && continue
|
||
missing=$((missing + 1))
|
||
cat <<EOF
|
||
/backup/scripts/mark-credential-escrow-verified.sh --item ${item} --evidence-id EVIDENCE_ID_FOR_${item}
|
||
EOF
|
||
done
|
||
if [ "${missing}" -eq 0 ]; then
|
||
echo "ALL_ESCROW_MARKERS_PRESENT=1"
|
||
else
|
||
echo "MISSING_ESCROW_MARKER_COUNT=${missing}"
|
||
fi
|
||
}
|
||
|
||
refresh_backup_health_textfile() {
|
||
[ "${TEXTFILE_REFRESH_ENABLED}" = "1" ] || {
|
||
echo "TEXTFILE_REFRESH_SKIPPED disabled"
|
||
return 0
|
||
}
|
||
[ -x "${BACKUP_HEALTH_EXPORTER}" ] || {
|
||
echo "TEXTFILE_REFRESH_SKIPPED exporter_missing=${BACKUP_HEALTH_EXPORTER}"
|
||
return 0
|
||
}
|
||
|
||
if AIOPS_HOST_LABEL=110 \
|
||
NODE_EXPORTER_TEXTFILE_DIR="${BACKUP_HEALTH_TEXTFILE_DIR}" \
|
||
AIOPS_BACKUP_COMMON_SH="${BACKUP_BASE}/scripts/common.sh" \
|
||
AIOPS_BACKUP_OFFSITE_ENV="${BACKUP_BASE}/scripts/offsite.env" \
|
||
AIOPS_OFFSITE_STATUS_DIR="${BACKUP_BASE}/offsite" \
|
||
AIOPS_ESCROW_EVIDENCE_DIR="${ESCROW_DIR}" \
|
||
"${BACKUP_HEALTH_EXPORTER}" >/dev/null 2>&1; then
|
||
echo "TEXTFILE_REFRESHED ${BACKUP_HEALTH_TEXTFILE_DIR}/backup_health.prom"
|
||
return 0
|
||
fi
|
||
|
||
echo "TEXTFILE_REFRESH_FAILED exporter=${BACKUP_HEALTH_EXPORTER}" >&2
|
||
return 0
|
||
}
|
||
|
||
while [ "$#" -gt 0 ]; do
|
||
case "$1" in
|
||
--item)
|
||
ITEM="${2:-}"
|
||
shift 2
|
||
;;
|
||
--evidence-id)
|
||
EVIDENCE_ID="${2:-}"
|
||
shift 2
|
||
;;
|
||
--note)
|
||
NOTE="${2:-}"
|
||
shift 2
|
||
;;
|
||
--dry-run)
|
||
DRY_RUN=1
|
||
shift
|
||
;;
|
||
--status)
|
||
status
|
||
exit 0
|
||
;;
|
||
--missing-commands)
|
||
MODE="missing-commands"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "Unknown argument: $1" >&2
|
||
usage >&2
|
||
exit 2
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if [ "${MODE}" = "missing-commands" ]; then
|
||
print_missing_commands
|
||
exit 0
|
||
fi
|
||
|
||
if [ -z "${ITEM}" ] || [ -z "${EVIDENCE_ID}" ]; then
|
||
usage >&2
|
||
exit 2
|
||
fi
|
||
|
||
if ! is_allowed_item "${ITEM}"; then
|
||
echo "不允許的 escrow item: ${ITEM}" >&2
|
||
usage >&2
|
||
exit 2
|
||
fi
|
||
|
||
reject_suspicious_value "evidence-id" "${EVIDENCE_ID}"
|
||
[ -n "${NOTE}" ] && reject_suspicious_value "note" "${NOTE}"
|
||
|
||
marker="${ESCROW_DIR}/${ITEM}.last_verified"
|
||
timestamp="$(date +%s)"
|
||
|
||
if [ "${DRY_RUN}" = "1" ]; then
|
||
echo "DRY_RUN=1"
|
||
echo "MARKER_WOULD_WRITE ${marker}"
|
||
echo "ITEM=${ITEM}"
|
||
echo "EVIDENCE_ID_ACCEPTED=1"
|
||
exit 0
|
||
fi
|
||
|
||
install -d -m 750 "${ESCROW_DIR}"
|
||
|
||
cat > "${marker}" <<EOF
|
||
timestamp=${timestamp}
|
||
item=${ITEM}
|
||
operator=$(whoami)
|
||
evidence_id=${EVIDENCE_ID}
|
||
note=${NOTE}
|
||
EOF
|
||
chmod 0640 "${marker}"
|
||
|
||
log_success "Credential escrow marker 已更新: ${ITEM}"
|
||
echo "MARKER_WRITTEN ${marker}"
|
||
refresh_backup_health_textfile
|