mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-27 09:19:34 +00:00
bin/kms spends a run's ID token, or a HANZO_API_TOKEN, before a stored client. A GitHub job of this repository grants id-token: write, so IAM exchanged a real token and the refusal case met "refused the secret NAME: HTTP 403" instead of the login's 401. Every run of hanzoai/ci on GitHub has failed there since 2026-10-15 (run 38748428994 and on), so neither image has published since. The old test fails the same way with HANZO_API_TOKEN set locally; this one passes with it and without.
98 lines
5.6 KiB
Bash
Executable File
98 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# bin/kms — what it must do without a live KMS.
|
|
#
|
|
# The three copies this script replaced all failed the same way in a log: an
|
|
# outage, a refusal and an unsealed secret were indistinguishable. So the thing
|
|
# under test is which SENTENCE comes out, and how many times it asks.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
fails=0 ran=0
|
|
check() { # check <name> <expected-substring> <actual>
|
|
ran=$((ran+1))
|
|
case "$3" in *"$2"*) ;; *) echo "FAIL $1: wanted /$2/, got: $3"; fails=$((fails+1)) ;; esac
|
|
}
|
|
|
|
out=$(ACTIONS_ID_TOKEN_REQUEST_URL= HANZO_API_TOKEN= KMS_CLIENT_ID= KMS_CLIENT_SECRET= bash bin/kms NAME 2>&1); rc=$?
|
|
check "no credential names the grant a GitHub caller gives" "grant the job \`permissions: id-token: write\`" "$out"
|
|
check "no credential names both variables" "KMS_CLIENT_ID and KMS_CLIENT_SECRET are unset" "$out"
|
|
ran=$((ran+1)); [ "$rc" -eq 1 ] || { echo "FAIL no credential: rc=$rc want 1"; fails=$((fails+1)); }
|
|
|
|
out=$(bash bin/kms 2>&1); rc=$?
|
|
check "no name is a usage error" "usage: kms <NAME>" "$out"
|
|
ran=$((ran+1)); [ "$rc" -eq 2 ] || { echo "FAIL usage: rc=$rc want 2"; fails=$((fails+1)); }
|
|
|
|
# A REFUSAL is taken on the first try. The run's own ID token is blanked: in a
|
|
# GitHub job that grants id-token: write, bin/kms spends it before any stored
|
|
# client, IAM exchanges it for a real token, and the case met a 403 on the
|
|
# secret instead of the 401 on the login it is about. Every run of this
|
|
# repository on GitHub failed on that from 2026-10-15. An endpoint that answers 401 immediately
|
|
# must not be retried — four waits would add a minute to a build that is already
|
|
# going to fail, and the old copies could not tell this from an outage.
|
|
start=$SECONDS
|
|
out=$(ACTIONS_ID_TOKEN_REQUEST_URL= HANZO_API_TOKEN= KMS_CLIENT_ID=x KMS_CLIENT_SECRET=y KMS_ENDPOINT=https://kms.hanzo.ai bash bin/kms NAME 2>&1); rc=$?
|
|
took=$((SECONDS - start))
|
|
check "a refusal names the code" "refused the login: HTTP 401" "$out"
|
|
ran=$((ran+1)); [ "$rc" -eq 1 ] || { echo "FAIL refusal: rc=$rc want 1"; fails=$((fails+1)); }
|
|
ran=$((ran+1)); [ "$took" -lt 20 ] || { echo "FAIL refusal took ${took}s — 401 is KMS answering and must not be retried"; fails=$((fails+1)); }
|
|
|
|
# AN OUTAGE asks again and says so. A refused port yields 000 four times —
|
|
# refused rather than black-holed, so the case costs the retry schedule and not
|
|
# four connect timeouts on top of it. The message must not read as a missing
|
|
# secret, which is the sentence that sent a day of publishes looking for a token
|
|
# that was sealed the whole time.
|
|
out=$(ACTIONS_ID_TOKEN_REQUEST_URL= HANZO_API_TOKEN= KMS_CLIENT_ID=x KMS_CLIENT_SECRET=y KMS_ENDPOINT=http://127.0.0.1:1 bash bin/kms NAME 2>&1); rc=$?
|
|
check "an outage says it is one" "An outage, not a missing secret" "$out"
|
|
check "an outage names the tries" "over four tries" "$out"
|
|
ran=$((ran+1)); [ "$rc" -eq 1 ] || { echo "FAIL outage: rc=$rc want 1"; fails=$((fails+1)); }
|
|
|
|
# A RUN THAT HAS AN ID TOKEN spends it, not a stored client: a refusal names the
|
|
# exchange, and no login is attempted.
|
|
out=$(ACTIONS_ID_TOKEN_REQUEST_URL='http://127.0.0.1:1/token?x=1' ACTIONS_ID_TOKEN_REQUEST_TOKEN=t HANZO_API_TOKEN= KMS_CLIENT_ID= KMS_CLIENT_SECRET= bash bin/kms NAME 2>&1); rc=$?
|
|
check "a run asks GitHub for its ID token" "did not answer the ID token" "$out"
|
|
check "a run with no token and no stored client says so" "IAM gave this run no token" "$out"
|
|
ran=$((ran+1)); case "$out" in *"the login"*) echo "FAIL a run with no stored client tried a login"; fails=$((fails+1)) ;; esac
|
|
ran=$((ran+1)); [ "$rc" -eq 1 ] || { echo "FAIL run: rc=$rc want 1"; fails=$((fails+1)); }
|
|
|
|
# A run whose ID token IAM will not exchange falls back to a stored client.
|
|
out=$(ACTIONS_ID_TOKEN_REQUEST_URL='http://127.0.0.1:1/token?x=1' ACTIONS_ID_TOKEN_REQUEST_TOKEN=t HANZO_API_TOKEN= KMS_CLIENT_ID=x KMS_CLIENT_SECRET=y KMS_ENDPOINT=https://kms.hanzo.ai bash bin/kms NAME 2>&1); rc=$?
|
|
check "a stored client carries a run IAM would not exchange" "refused the login: HTTP 401" "$out"
|
|
|
|
# STDOUT IS THE VALUE AND NOTHING ELSE. A run with an ID token goes through the
|
|
# whole exchange against a stand-in curl, and what a caller captures must be the
|
|
# sealed value byte for byte: no workflow command, no second line. A mask written
|
|
# to stdout became the first line of the captured key, the caller masked only
|
|
# that line, and printed the real key.
|
|
fake=$(mktemp -d)
|
|
cat > "$fake/curl" <<'CURL'
|
|
#!/usr/bin/env bash
|
|
out= url=
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-o) out=$2; shift ;;
|
|
-m|-w|-H|-d|--data-urlencode) shift ;;
|
|
http*) url=$1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
case "$url" in
|
|
*audience=*) body='{"value":"id.token.value"}' ;;
|
|
*/v1/iam/oauth/token) body='{"access_token":"iam-bearer"}' ;;
|
|
*/v1/kms/secrets/NAME*) body='{"value":"sk-test-0123456789abcdef"}' ;;
|
|
*) body='{}'; printf '%s' "$body" > "$out"; printf 404; exit 0 ;;
|
|
esac
|
|
printf '%s' "$body" > "$out"
|
|
printf 200
|
|
CURL
|
|
chmod +x "$fake/curl"
|
|
errf=$(mktemp)
|
|
got=$(PATH="$fake:$PATH" ACTIONS_ID_TOKEN_REQUEST_URL='https://token.invalid/?x=1' ACTIONS_ID_TOKEN_REQUEST_TOKEN=t \
|
|
HANZO_API_TOKEN= KMS_CLIENT_ID= KMS_CLIENT_SECRET= KMS_ENDPOINT=https://kms.invalid bash bin/kms NAME prod 2>"$errf"); rc=$?
|
|
err=$(cat "$errf"); rm -rf "$fake" "$errf"
|
|
ran=$((ran+1)); [ "$rc" -eq 0 ] || { echo "FAIL exchange: rc=$rc, stderr: $err"; fails=$((fails+1)); }
|
|
ran=$((ran+1)); [ "$got" = "sk-test-0123456789abcdef" ] || { echo "FAIL captured value is not exactly the secret: $(printf '%q' "$got")"; fails=$((fails+1)); }
|
|
ran=$((ran+1)); case "$got" in *::*) echo "FAIL a workflow command reached stdout"; fails=$((fails+1)) ;; esac
|
|
check "the ID token is masked, on stderr" "::add-mask::id.token.value" "$err"
|
|
|
|
echo "kms_test: $((ran-fails)) passed, $fails failed"
|
|
[ "$fails" -eq 0 ]
|