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
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:
@@ -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") :]
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
139
scripts/reboot-recovery/agent99-provision-completion-token.sh
Executable file
139
scripts/reboot-recovery/agent99-provision-completion-token.sh
Executable 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"
|
||||
Reference in New Issue
Block a user