update security

This commit is contained in:
David Schroeder
2026-07-26 16:41:16 -05:00
parent 825a35924d
commit ebddd792b2
8 changed files with 530 additions and 4 deletions
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Cluster discovery and remote update helpers for TA-ProxMenu.
TAPM_COROSYNC_CONFIG="${TAPM_COROSYNC_CONFIG:-/etc/pve/corosync.conf}"
TAPM_CLUSTER_NODES_FROM_JSON() {
python3 -c '
import json, re, sys
try:
payload = json.load(sys.stdin)
except (json.JSONDecodeError, OSError):
raise SystemExit(1)
if isinstance(payload, dict):
payload = payload.get("data", [])
if not isinstance(payload, list):
raise SystemExit(1)
valid_name = re.compile(r"^[A-Za-z0-9][A-Za-z0-9.-]{0,62}$")
rows = []
for item in payload:
if not isinstance(item, dict):
continue
name = str(item.get("node", "")).strip()
status = str(item.get("status", "unknown")).strip().lower()
if valid_name.fullmatch(name):
rows.append((name, status))
for name, status in sorted(rows):
print(f"{name}\t{status}")
'
}
TAPM_CLUSTER_NODE_ROWS() {
local node_json
node_json="$(timeout 10 pvesh get /nodes --output-format json 2>/dev/null)" ||
return 1
printf '%s' "$node_json" | TAPM_CLUSTER_NODES_FROM_JSON
}
TAPM_LOCAL_CLUSTER_NODE() {
local local_link=''
local_link="$(readlink /etc/pve/local 2>/dev/null || true)"
if [[ -n "$local_link" ]]; then
basename "$local_link"
else
hostname -s
fi
}
TAPM_UPDATE_REMOTE_NODE() {
local node="$1"
[[ "$node" =~ ^[A-Za-z0-9][A-Za-z0-9.-]{0,62}$ ]] || return 1
timeout 240 ssh \
-o BatchMode=yes \
-o ConnectTimeout=10 \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=2 \
"root@${node}" \
'/opt/idssys/ta-proxmenu/run.sh update --local-only'
}
INSTALL_CLUSTER_UPDATES() {
local index
local local_node
local node
local required_command
local status
local node_rows=''
local cluster_failed=0
local -a cluster_nodes=()
local -a cluster_statuses=()
if [[ ! -s "$TAPM_COROSYNC_CONFIG" ]]; then
INSTALL_LOCAL_UPDATES
return $?
fi
for required_command in pvesh python3 ssh timeout; do
if ! command -v "$required_command" >/dev/null 2>&1; then
echo -e "${idsCL[Red]}Cluster update requires ${required_command}; no nodes were updated.${idsCL[Default]}"
return 1
fi
done
node_rows="$(TAPM_CLUSTER_NODE_ROWS)" || {
echo -e "${idsCL[Red]}Could not retrieve the Proxmox cluster node list; no nodes were updated.${idsCL[Default]}"
return 1
}
while IFS=$'\t' read -r node status; do
[[ -n "$node" ]] || continue
cluster_nodes+=("$node")
cluster_statuses+=("$status")
done <<<"$node_rows"
if (( ${#cluster_nodes[@]} == 0 )); then
echo -e "${idsCL[Red]}The Proxmox API returned no cluster nodes; no nodes were updated.${idsCL[Default]}"
return 1
fi
local_node="$(TAPM_LOCAL_CLUSTER_NODE)"
echo -e "${idsCL[LightCyan]}Updating TA-ProxMenu across ${#cluster_nodes[@]} cluster node(s)...${idsCL[Default]}"
for index in "${!cluster_nodes[@]}"; do
node="${cluster_nodes[$index]}"
status="${cluster_statuses[$index]}"
if [[ "$node" == "$local_node" ]]; then
continue
fi
if [[ "$status" != "online" ]]; then
echo -e "${idsCL[LightYellow]}Skipping ${node}: node status is ${status}.${idsCL[Default]}"
cluster_failed=1
continue
fi
echo -e "${idsCL[LightCyan]}Updating remote node ${node}...${idsCL[Default]}"
if TAPM_UPDATE_REMOTE_NODE "$node"; then
echo -e "${idsCL[Green]}Remote node ${node} is updated.${idsCL[Default]}"
else
echo -e "${idsCL[Red]}Remote node ${node} failed to update.${idsCL[Default]}"
cluster_failed=1
fi
done
echo -e "${idsCL[LightCyan]}Updating local node ${local_node}...${idsCL[Default]}"
INSTALL_LOCAL_UPDATES || cluster_failed=1
if (( cluster_failed == 0 )); then
echo -e "${idsCL[Green]}TA-ProxMenu is updated on all ${#cluster_nodes[@]} cluster node(s).${idsCL[Default]}"
return 0
fi
echo -e "${idsCL[LightYellow]}Cluster update completed with failures; review the node messages above.${idsCL[Default]}"
return 1
}
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
TAPM_FLEET_STATE_DIR="${TAPM_FLEET_STATE_DIR:-/var/lib/ta-proxmenu}"
TAPM_FLEET_IDENTITY_FILE="${TAPM_FLEET_IDENTITY_FILE:-${TAPM_FLEET_STATE_DIR}/identity.env}"
TAPM_FLEET_INSTALLATION_ID=''
TAPM_FLEET_CREDENTIAL=''
TAPM_FLEET_LAST_VERSION=''
TAPM_FLEET_REGISTERED=0
TAPM_FLEET_STARTED_AT=0
TAPM_FLEET_VERSION=''
TAPM_FLEET_READ_VALUE() {
local key="$1"
local value=''
if [[ -r "$TAPM_FLEET_IDENTITY_FILE" ]]; then
value="$(
sed -n "s/^${key}=//p" "$TAPM_FLEET_IDENTITY_FILE" |
tail -n 1
)"
fi
printf '%s' "$value"
}
TAPM_FLEET_LOAD_IDENTITY() {
TAPM_FLEET_INSTALLATION_ID="$(TAPM_FLEET_READ_VALUE INSTALLATION_ID)"
TAPM_FLEET_CREDENTIAL="$(TAPM_FLEET_READ_VALUE CREDENTIAL)"
TAPM_FLEET_LAST_VERSION="$(TAPM_FLEET_READ_VALUE LAST_VERSION)"
}
TAPM_FLEET_VALID_IDENTITY() {
[[ "$TAPM_FLEET_INSTALLATION_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ &&
"$TAPM_FLEET_CREDENTIAL" =~ ^[0-9a-f]{64}$ ]]
}
TAPM_FLEET_WRITE_IDENTITY() {
local temporary_file
mkdir -p "$TAPM_FLEET_STATE_DIR" 2>/dev/null || return 1
chmod 0700 "$TAPM_FLEET_STATE_DIR" 2>/dev/null || return 1
temporary_file="$(mktemp "${TAPM_FLEET_IDENTITY_FILE}.tmp.XXXXXX")" || return 1
if ! {
printf 'INSTALLATION_ID=%s\n' "$TAPM_FLEET_INSTALLATION_ID"
printf 'CREDENTIAL=%s\n' "$TAPM_FLEET_CREDENTIAL"
printf 'LAST_VERSION=%s\n' "$TAPM_FLEET_LAST_VERSION"
} >"$temporary_file" ||
! chmod 0600 "$temporary_file" ||
! mv -f "$temporary_file" "$TAPM_FLEET_IDENTITY_FILE"; then
rm -f "$temporary_file"
return 1
fi
}
TAPM_FLEET_ENSURE_IDENTITY() {
TAPM_FLEET_LOAD_IDENTITY
TAPM_FLEET_VALID_IDENTITY && return 0
command -v openssl >/dev/null 2>&1 || return 1
if [[ -r /proc/sys/kernel/random/uuid ]]; then
IFS= read -r TAPM_FLEET_INSTALLATION_ID </proc/sys/kernel/random/uuid
elif command -v uuidgen >/dev/null 2>&1; then
TAPM_FLEET_INSTALLATION_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
else
return 1
fi
TAPM_FLEET_CREDENTIAL="$(openssl rand -hex 32)" || return 1
TAPM_FLEET_LAST_VERSION=''
TAPM_FLEET_WRITE_IDENTITY
}
TAPM_FLEET_COLLECT_METADATA() {
TAPM_FLEET_GIT_COMMIT="$(git -C "${FOLDER:-/opt/idssys/ta-proxmenu}" rev-parse HEAD 2>/dev/null || true)"
TAPM_FLEET_PVE_VERSION="$(pveversion 2>/dev/null | head -n 1 || true)"
TAPM_FLEET_OS_VERSION="$(
if [[ -r /etc/os-release ]]; then
(
# shellcheck disable=SC1091
source /etc/os-release
printf '%s' "${PRETTY_NAME:-}"
)
fi
)"
TAPM_FLEET_KERNEL_VERSION="$(uname -r 2>/dev/null || true)"
TAPM_FLEET_ARCHITECTURE="$(
dpkg --print-architecture 2>/dev/null ||
uname -m 2>/dev/null ||
true
)"
if [[ -s /etc/pve/corosync.conf ]]; then
TAPM_FLEET_CLUSTERED=true
else
TAPM_FLEET_CLUSTERED=false
fi
}
TAPM_FLEET_JSON() {
local event="$1"
local result="$2"
local error_code="${3:-}"
local duration="${4:-0}"
TAPM_FLEET_EVENT="$event" \
TAPM_FLEET_RESULT="$result" \
TAPM_FLEET_ERROR_CODE="$error_code" \
TAPM_FLEET_DURATION="$duration" \
TAPM_FLEET_INSTALLATION_ID="$TAPM_FLEET_INSTALLATION_ID" \
TAPM_FLEET_CREDENTIAL="$TAPM_FLEET_CREDENTIAL" \
TAPM_FLEET_VERSION="$TAPM_FLEET_VERSION" \
TAPM_FLEET_GIT_COMMIT="$TAPM_FLEET_GIT_COMMIT" \
TAPM_FLEET_PVE_VERSION="$TAPM_FLEET_PVE_VERSION" \
TAPM_FLEET_OS_VERSION="$TAPM_FLEET_OS_VERSION" \
TAPM_FLEET_KERNEL_VERSION="$TAPM_FLEET_KERNEL_VERSION" \
TAPM_FLEET_ARCHITECTURE="$TAPM_FLEET_ARCHITECTURE" \
TAPM_FLEET_CLUSTERED="$TAPM_FLEET_CLUSTERED" \
TAPM_FLEET_INCLUDE_CREDENTIAL="${TAPM_FLEET_INCLUDE_CREDENTIAL:-0}" \
python3 -c '
import json, os, sys
payload = {
"schema_version": 1,
"installation_id": os.environ["TAPM_FLEET_INSTALLATION_ID"],
"event": os.environ["TAPM_FLEET_EVENT"],
"result": os.environ["TAPM_FLEET_RESULT"],
"proxmenu_version": os.environ["TAPM_FLEET_VERSION"],
"git_commit": os.environ["TAPM_FLEET_GIT_COMMIT"],
"pve_version": os.environ["TAPM_FLEET_PVE_VERSION"],
"os_version": os.environ["TAPM_FLEET_OS_VERSION"],
"kernel_version": os.environ["TAPM_FLEET_KERNEL_VERSION"],
"architecture": os.environ["TAPM_FLEET_ARCHITECTURE"],
"clustered": os.environ["TAPM_FLEET_CLUSTERED"] == "true",
"error_code": os.environ["TAPM_FLEET_ERROR_CODE"],
"duration_seconds": int(os.environ["TAPM_FLEET_DURATION"]),
}
if os.environ.get("TAPM_FLEET_INCLUDE_CREDENTIAL") == "1":
payload["credential"] = os.environ["TAPM_FLEET_CREDENTIAL"]
json.dump(payload, sys.stdout, separators=(",", ":"))
'
}
TAPM_FLEET_REGISTER() {
TAPM_FLEET_INCLUDE_CREDENTIAL=1 TAPM_FLEET_JSON installed success |
curl --fail --silent --show-error \
--connect-timeout 3 --max-time 10 \
--header 'Content-Type: application/json' \
--data-binary @- \
"${TAPM_BROKER_URL}/api/v1/hosts/register" \
>/dev/null 2>&1
}
TAPM_FLEET_EVENT_SEND() {
local event="$1"
local result="$2"
local error_code="${3:-}"
local duration="${4:-0}"
local event_payload=''
(( TAPM_FLEET_REGISTERED == 1 )) || return 0
event_payload="$(mktemp "${TMPDIR:-/tmp}/tapm-fleet-event.XXXXXX")" || return 0
chmod 0600 "$event_payload" 2>/dev/null || {
rm -f "$event_payload"
return 0
}
if ! TAPM_FLEET_JSON "$event" "$result" "$error_code" "$duration" >"$event_payload"; then
rm -f "$event_payload"
return 0
fi
printf 'header = "Content-Type: application/json"\nheader = "Authorization: Bearer %s"\nurl = "%s/api/v1/hosts/events"\n' \
"$TAPM_FLEET_CREDENTIAL" "$TAPM_BROKER_URL" |
curl --fail --silent --show-error \
--connect-timeout 3 --max-time 10 \
--config - --data-binary "@${event_payload}" \
>/dev/null 2>&1 || true
rm -f "$event_payload"
}
TAPM_FLEET_START() {
TAPM_FLEET_VERSION="$1"
TAPM_FLEET_STARTED_AT="$(date +%s)"
TAPM_FLEET_ENSURE_IDENTITY || return 0
command -v python3 >/dev/null 2>&1 || return 0
command -v curl >/dev/null 2>&1 || return 0
TAPM_FLEET_COLLECT_METADATA
if TAPM_FLEET_REGISTER; then
TAPM_FLEET_REGISTERED=1
else
return 0
fi
if [[ -n "$TAPM_FLEET_LAST_VERSION" &&
"$TAPM_FLEET_LAST_VERSION" != "$TAPM_FLEET_VERSION" ]]; then
TAPM_FLEET_EVENT_SEND upgraded success
fi
TAPM_FLEET_EVENT_SEND run_started started
}
TAPM_FLEET_FINISH() {
local exit_status="${1:-0}"
local duration=0
if [[ "$TAPM_FLEET_STARTED_AT" =~ ^[0-9]+$ ]] &&
(( TAPM_FLEET_STARTED_AT > 0 )); then
duration="$(( $(date +%s) - TAPM_FLEET_STARTED_AT ))"
fi
if (( exit_status == 0 )); then
TAPM_FLEET_EVENT_SEND run_completed success '' "$duration"
else
TAPM_FLEET_EVENT_SEND run_failed failure exit_nonzero "$duration"
fi
if (( TAPM_FLEET_REGISTERED == 1 )) && TAPM_FLEET_VALID_IDENTITY; then
TAPM_FLEET_LAST_VERSION="$TAPM_FLEET_VERSION"
TAPM_FLEET_WRITE_IDENTITY || true
fi
return 0
}