From 7695cd7dfd68c15bd4be88bacbfc59d6c91c00b8 Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 21:16:33 +0800 Subject: [PATCH] fix(agent99): bind completion callback credential --- ...nt99_transport_recovery_deploy_contract.py | 23 +++ k8s/awoooi-prod/06-deployment-api.yaml | 3 + k8s/awoooi-prod/08-deployment-worker.yaml | 3 + .../agent99-provision-completion-token.sh | 139 ++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100755 scripts/reboot-recovery/agent99-provision-completion-token.sh diff --git a/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py b/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py index 1470ce15b..7822fdc8a 100644 --- a/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py +++ b/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py @@ -207,6 +207,29 @@ def test_agent99_completion_callback_waits_for_durable_same_trace_readback() -> assert 'secretValueLogged = $false' in source +def test_agent99_completion_token_is_bound_without_reusing_telegram_secret() -> None: + api_deployment = (ROOT / "k8s/awoooi-prod/06-deployment-api.yaml").read_text() + worker_deployment = ( + ROOT / "k8s/awoooi-prod/08-deployment-worker.yaml" + ).read_text() + provisioner = ( + ROOT / "scripts/reboot-recovery/agent99-provision-completion-token.sh" + ).read_text() + + for deployment in (api_deployment, worker_deployment): + assert "name: AGENT99_SRE_ALERT_RELAY_TOKEN" in deployment + assert "key: AGENT99_SRE_ALERT_RELAY_TOKEN" in deployment + + assert "AGENT99_SRE_RELAY_TOKEN" in provisioner + assert "openssl rand -hex 32" in provisioner + assert "--patch-file=/dev/stdin" in provisioner + assert "SetEnvironmentVariable" in provisioner + assert "OPENCLAW_TG_BOT_TOKEN" not in provisioner + assert "TELEGRAM_BOT_TOKEN" not in provisioner + assert "set -x" not in provisioner + assert 'echo "$token"' not in provisioner + + def test_load_shedding_requires_configured_post_verifier() -> None: source = CONTROL.read_text(encoding="utf-8") function = source[source.index("function Invoke-LoadShedding") :] diff --git a/k8s/awoooi-prod/06-deployment-api.yaml b/k8s/awoooi-prod/06-deployment-api.yaml index 17603d287..c5aa80ecb 100644 --- a/k8s/awoooi-prod/06-deployment-api.yaml +++ b/k8s/awoooi-prod/06-deployment-api.yaml @@ -109,6 +109,9 @@ spec: - name: SRE_GROUP_CHAT_ID valueFrom: secretKeyRef: {name: awoooi-secrets, key: SRE_GROUP_CHAT_ID, optional: true} + - name: AGENT99_SRE_ALERT_RELAY_TOKEN + valueFrom: + secretKeyRef: {name: awoooi-secrets, key: AGENT99_SRE_ALERT_RELAY_TOKEN, optional: true} - name: WEBHOOK_HMAC_SECRET valueFrom: secretKeyRef: {name: awoooi-secrets, key: WEBHOOK_HMAC_SECRET, optional: true} diff --git a/k8s/awoooi-prod/08-deployment-worker.yaml b/k8s/awoooi-prod/08-deployment-worker.yaml index a432cf0ed..8f09dbe6e 100644 --- a/k8s/awoooi-prod/08-deployment-worker.yaml +++ b/k8s/awoooi-prod/08-deployment-worker.yaml @@ -109,6 +109,9 @@ spec: - name: SRE_GROUP_CHAT_ID valueFrom: secretKeyRef: {name: awoooi-secrets, key: SRE_GROUP_CHAT_ID, optional: true} + - name: AGENT99_SRE_ALERT_RELAY_TOKEN + valueFrom: + secretKeyRef: {name: awoooi-secrets, key: AGENT99_SRE_ALERT_RELAY_TOKEN, optional: true} - name: WEBHOOK_HMAC_SECRET valueFrom: secretKeyRef: {name: awoooi-secrets, key: WEBHOOK_HMAC_SECRET, optional: true} diff --git a/scripts/reboot-recovery/agent99-provision-completion-token.sh b/scripts/reboot-recovery/agent99-provision-completion-token.sh new file mode 100755 index 000000000..2ed787b56 --- /dev/null +++ b/scripts/reboot-recovery/agent99-provision-completion-token.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODE="check" +ROTATE=0 +CONTROLLER_TARGET="${AGENT99_K3S_CONTROLLER_TARGET:-wooo@192.168.0.121}" +WINDOWS_TARGET="${AGENT99_WINDOWS_TARGET:-Administrator@192.168.0.99}" +KUBECONFIG_PATH="${AGENT99_KUBECONFIG_PATH:-/etc/rancher/k3s/k3s.yaml}" +NAMESPACE="${AGENT99_K8S_NAMESPACE:-awoooi-prod}" +SECRET_NAME="${AGENT99_K8S_SECRET_NAME:-awoooi-secrets}" +K8S_TOKEN_KEY="AGENT99_SRE_ALERT_RELAY_TOKEN" +WINDOWS_TOKEN_ENV="AGENT99_SRE_RELAY_TOKEN" + +usage() { + cat <<'EOF' +Usage: agent99-provision-completion-token.sh [--check | --apply] [--rotate] + +Safely provisions the Agent99 completion callback credential to the AWOOOI +Kubernetes Secret and Windows 99 machine environment. The credential is sent +only through SSH stdin and is never printed or written to an evidence file. + +Options: + --check Read presence metadata only (default). + --apply Provision when either endpoint is missing. + --rotate Replace both endpoints even when both are present. + --controller TARGET K3s controller SSH target. + --windows TARGET Windows 99 OpenSSH target. + --kubeconfig PATH Controller kubeconfig path. + --namespace NAME Kubernetes namespace. + --secret NAME Kubernetes Secret name. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --check) MODE="check" ;; + --apply) MODE="apply" ;; + --rotate) ROTATE=1 ;; + --controller) CONTROLLER_TARGET="${2:?missing controller target}"; shift ;; + --windows) WINDOWS_TARGET="${2:?missing Windows target}"; shift ;; + --kubeconfig) KUBECONFIG_PATH="${2:?missing kubeconfig path}"; shift ;; + --namespace) NAMESPACE="${2:?missing namespace}"; shift ;; + --secret) SECRET_NAME="${2:?missing Secret name}"; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;; + esac + shift +done + +if [[ ! "$CONTROLLER_TARGET" =~ ^[A-Za-z0-9_.@:-]+$ ]] || + [[ ! "$WINDOWS_TARGET" =~ ^[A-Za-z0-9_.@:-]+$ ]] || + [[ ! "$NAMESPACE" =~ ^[A-Za-z0-9_.-]+$ ]] || + [[ ! "$SECRET_NAME" =~ ^[A-Za-z0-9_.-]+$ ]] || + [[ ! "$KUBECONFIG_PATH" =~ ^/[A-Za-z0-9_./-]+$ ]]; then + echo "Unsafe provisioning target argument" >&2 + exit 2 +fi + +for command_name in ssh openssl iconv base64; do + command -v "$command_name" >/dev/null || { + echo "Missing required command: $command_name" >&2 + exit 2 + } +done + +encode_powershell() { + iconv -f UTF-8 -t UTF-16LE | base64 | tr -d '\r\n' +} + +windows_presence_script=$(cat </dev/null 2>&1 && [[ "$ROTATE" -ne 1 ]]; then + echo "AGENT99_COMPLETION_TOKEN_ALREADY_PRESENT=1" + echo "AGENT99_COMPLETION_TOKEN_READY=1" + exit 0 +fi + +token="$(openssl rand -hex 32)" +trap 'unset token token_b64 patch_json windows_set_script windows_set_encoded' EXIT +if [[ ${#token} -ne 64 ]]; then + echo "Credential generation failed" >&2 + exit 1 +fi + +token_b64=$(printf '%s' "$token" | base64 | tr -d '\r\n') +patch_json=$(printf '{"data":{"%s":"%s"}}' "$K8S_TOKEN_KEY" "$token_b64") +printf '%s' "$patch_json" | ssh -o BatchMode=yes "$CONTROLLER_TARGET" \ + "sudo kubectl --kubeconfig='$KUBECONFIG_PATH' -n '$NAMESPACE' patch secret '$SECRET_NAME' --type merge --patch-file=/dev/stdin >/dev/null" + +windows_set_script=$(cat </dev/null + +unset token token_b64 patch_json +if ! read_presence; then + echo "Agent99 completion token provisioning did not pass presence readback" >&2 + exit 1 +fi +echo "AGENT99_COMPLETION_TOKEN_PROVISIONED=1"