168 lines
4.1 KiB
Bash
168 lines
4.1 KiB
Bash
#!/usr/bin/env sh
|
|
set -eu
|
|
|
|
secret_path="kv/atlas/hermes/developer-jenkins"
|
|
secret_api_path="kv/data/atlas/hermes/developer-jenkins"
|
|
jwt_file="${VAULT_K8S_JWT_FILE:-/var/run/secrets/kubernetes.io/serviceaccount/token}"
|
|
vault_role="${VAULT_K8S_ROLE:-hermes-jenkins-token-seed}"
|
|
payload_file="${TMPDIR:-/tmp}/hermes-jenkins-token.json"
|
|
|
|
log() { printf '[hermes-jenkins-token] %s\n' "$*" >&2; }
|
|
cleanup() { rm -f "${payload_file}"; }
|
|
trap cleanup EXIT HUP INT TERM
|
|
|
|
retry_command() {
|
|
attempt=1
|
|
while [ "${attempt}" -le 5 ]; do
|
|
set +e
|
|
command_output="$("$@" 2>&1)"
|
|
command_status=$?
|
|
set -e
|
|
if [ "${command_status}" -eq 0 ]; then
|
|
printf '%s' "${command_output}"
|
|
return 0
|
|
fi
|
|
if [ "${attempt}" -lt 5 ]; then
|
|
sleep "${attempt}"
|
|
fi
|
|
attempt=$((attempt + 1))
|
|
done
|
|
return "${command_status}"
|
|
}
|
|
|
|
ensure_token() {
|
|
if [ -n "${VAULT_TOKEN:-}" ]; then
|
|
return
|
|
fi
|
|
jwt="$(cat "${jwt_file}")"
|
|
VAULT_TOKEN="$(retry_command vault write -field=token \
|
|
auth/kubernetes/login role="${vault_role}" jwt="${jwt}")" || {
|
|
log "Vault Kubernetes login failed after retries"
|
|
exit 1
|
|
}
|
|
unset jwt
|
|
if [ -z "${VAULT_TOKEN}" ]; then
|
|
log "Vault Kubernetes login returned an empty token"
|
|
exit 1
|
|
fi
|
|
export VAULT_TOKEN
|
|
}
|
|
|
|
# Return 0 for the complete field, 10 when absent, 11 when present but
|
|
# incomplete, and 12 for a persistent authorization/transport failure.
|
|
read_build_token() {
|
|
attempt=1
|
|
while [ "${attempt}" -le 5 ]; do
|
|
set +e
|
|
secret_json="$(vault read -format=json "${secret_api_path}" 2>&1)"
|
|
secret_status=$?
|
|
set -e
|
|
if [ "${secret_status}" -eq 0 ]; then
|
|
unset secret_json
|
|
set +e
|
|
build_token="$(vault kv get -field=build_token "${secret_path}" 2>&1)"
|
|
field_status=$?
|
|
set -e
|
|
if [ "${field_status}" -eq 0 ]; then
|
|
case "${build_token}" in
|
|
''|*[!0-9a-f]*) unset build_token; return 11 ;;
|
|
esac
|
|
if [ "${#build_token}" -eq 64 ]; then
|
|
unset build_token
|
|
return 0
|
|
fi
|
|
unset build_token
|
|
return 11
|
|
fi
|
|
if printf '%s' "${build_token}" | grep -q 'No value found'; then
|
|
unset build_token
|
|
return 11
|
|
fi
|
|
unset build_token
|
|
elif printf '%s' "${secret_json}" | grep -Eq 'Code: 404|No value found at'; then
|
|
unset secret_json
|
|
return 10
|
|
fi
|
|
if [ "${attempt}" -lt 5 ]; then
|
|
sleep "${attempt}"
|
|
fi
|
|
attempt=$((attempt + 1))
|
|
done
|
|
unset secret_json
|
|
return 12
|
|
}
|
|
|
|
ensure_token
|
|
|
|
if read_build_token; then
|
|
read_status=0
|
|
else
|
|
read_status=$?
|
|
fi
|
|
case "${read_status}" in
|
|
0)
|
|
log "job-scoped token already present; no write performed"
|
|
exit 0
|
|
;;
|
|
11)
|
|
log "secret exists without a valid build_token; refusing to overwrite existing fields"
|
|
exit 1
|
|
;;
|
|
12)
|
|
log "secret read failed after retries; refusing to seed"
|
|
exit 1
|
|
;;
|
|
10) ;;
|
|
*)
|
|
log "unexpected secret read state ${read_status}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
new_token="$(retry_command vault write -field=random_bytes \
|
|
sys/tools/random/32 format=hex)" || {
|
|
log "Vault random token generation failed after retries"
|
|
exit 1
|
|
}
|
|
case "${new_token}" in
|
|
''|*[!0-9a-f]*)
|
|
unset new_token
|
|
log "Vault returned an invalid random token"
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [ "${#new_token}" -ne 64 ]; then
|
|
unset new_token
|
|
log "Vault returned an invalid random token"
|
|
exit 1
|
|
fi
|
|
|
|
umask 077
|
|
printf '{"options":{"cas":0},"data":{"build_token":"%s"}}\n' \
|
|
"${new_token}" > "${payload_file}"
|
|
unset new_token
|
|
|
|
set +e
|
|
create_error="$(vault write "${secret_api_path}" @"${payload_file}" 2>&1 >/dev/null)"
|
|
create_status=$?
|
|
set -e
|
|
cleanup
|
|
if [ "${create_status}" -eq 0 ]; then
|
|
log "job-scoped token created with KV-v2 CAS create-only semantics"
|
|
exit 0
|
|
fi
|
|
|
|
if printf '%s' "${create_error}" | grep -qi 'check-and-set'; then
|
|
unset create_error
|
|
if read_build_token; then
|
|
log "another seeder won CAS; existing token preserved"
|
|
exit 0
|
|
fi
|
|
log "CAS race did not produce a readable build_token; refusing to continue"
|
|
exit 1
|
|
fi
|
|
|
|
unset create_error
|
|
log "KV-v2 CAS create failed; refusing to retry a write"
|
|
exit 1
|