fix(agent99): bind completion callback credential
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m34s
CD Pipeline / build-and-deploy (push) Failing after 4m30s
CD Pipeline / post-deploy-checks (push) Has been skipped
Agent Version Lifecycle Watch / version-lifecycle-watch (push) Successful in 15s

This commit is contained in:
ogt
2026-07-11 21:16:33 +08:00
parent a1f87282ef
commit 7695cd7dfd
4 changed files with 168 additions and 0 deletions

View File

@@ -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") :]

View File

@@ -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}

View File

@@ -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}

View File

@@ -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 <<EOF
\$ProgressPreference = 'SilentlyContinue'
\$value = [Environment]::GetEnvironmentVariable('${WINDOWS_TOKEN_ENV}', 'Machine')
if ([string]::IsNullOrWhiteSpace(\$value)) { Write-Output '0' } else { Write-Output '1' }
EOF
)
windows_presence_encoded=$(printf '%s' "$windows_presence_script" | encode_powershell)
read_presence() {
local k8s_presence windows_presence
k8s_presence=$(ssh -o BatchMode=yes "$CONTROLLER_TARGET" \
"sudo kubectl --kubeconfig='$KUBECONFIG_PATH' -n '$NAMESPACE' get secret '$SECRET_NAME' -o go-template='{{if index .data \"$K8S_TOKEN_KEY\"}}1{{else}}0{{end}}'" | tr -d '\r\n')
windows_presence=$(ssh -o BatchMode=yes "$WINDOWS_TARGET" \
powershell.exe -NoProfile -NonInteractive -OutputFormat Text \
-EncodedCommand "$windows_presence_encoded" | tr -d '\r\n')
[[ "$k8s_presence" == "1" ]] || k8s_presence="0"
[[ "$windows_presence" == "1" ]] || windows_presence="0"
printf 'K8S_COMPLETION_TOKEN_PRESENT=%s\n' "$k8s_presence"
printf 'WINDOWS_COMPLETION_TOKEN_PRESENT=%s\n' "$windows_presence"
if [[ "$k8s_presence" == "1" && "$windows_presence" == "1" ]]; then
printf 'AGENT99_COMPLETION_TOKEN_READY=1\n'
return 0
fi
printf 'AGENT99_COMPLETION_TOKEN_READY=0\n'
return 1
}
if [[ "$MODE" == "check" ]]; then
read_presence
exit $?
fi
if read_presence >/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 <<EOF
\$ProgressPreference = 'SilentlyContinue'
\$token = [Console]::In.ReadToEnd().Trim()
if (\$token.Length -ne 64) { throw 'Invalid completion token length' }
[Environment]::SetEnvironmentVariable('${WINDOWS_TOKEN_ENV}', \$token, 'Machine')
Write-Output 'WINDOWS_COMPLETION_TOKEN_WRITTEN=1'
EOF
)
windows_set_encoded=$(printf '%s' "$windows_set_script" | encode_powershell)
printf '%s' "$token" | ssh -o BatchMode=yes "$WINDOWS_TARGET" \
powershell.exe -NoProfile -NonInteractive -OutputFormat Text \
-EncodedCommand "$windows_set_encoded" >/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"