154 lines
3.7 KiB
Bash
Executable File
154 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
set -eu
|
|
|
|
secret_path="kv/atlas/hermes/developer-jenkins"
|
|
jwt_file="${VAULT_K8S_JWT_FILE:-/var/run/secrets/kubernetes.io/serviceaccount/token}"
|
|
vault_role="${VAULT_K8S_ROLE:-vault-admin}"
|
|
|
|
log() { printf '[hermes-jenkins-token] %s\n' "$*" >&2; }
|
|
|
|
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 when the field exists, 10 when the secret is absent, 11 when the
|
|
# secret exists without the field, and 12 for a persistent read error.
|
|
read_build_token() {
|
|
attempt=1
|
|
while [ "${attempt}" -le 5 ]; do
|
|
set +e
|
|
secret_error="$(vault kv get "${secret_path}" 2>&1 >/dev/null)"
|
|
secret_status=$?
|
|
set -e
|
|
if [ "${secret_status}" -eq 0 ]; then
|
|
set +e
|
|
build_token="$(vault kv get -field=build_token "${secret_path}" 2>&1)"
|
|
field_status=$?
|
|
set -e
|
|
if [ "${field_status}" -eq 0 ] && [ -n "${build_token}" ]; then
|
|
return 0
|
|
fi
|
|
if printf '%s' "${build_token}" | grep -q 'No value found'; then
|
|
unset build_token
|
|
return 11
|
|
fi
|
|
if [ "${field_status}" -eq 0 ]; then
|
|
unset build_token
|
|
return 11
|
|
fi
|
|
elif printf '%s' "${secret_error}" | grep -Eq 'Code: 404|No value found at'; then
|
|
return 10
|
|
fi
|
|
if [ "${attempt}" -lt 5 ]; then
|
|
sleep "${attempt}"
|
|
fi
|
|
attempt=$((attempt + 1))
|
|
done
|
|
unset secret_error build_token
|
|
return 12
|
|
}
|
|
|
|
ensure_token
|
|
|
|
if read_build_token; then
|
|
read_status=0
|
|
else
|
|
read_status=$?
|
|
fi
|
|
case "${read_status}" in
|
|
0)
|
|
unset build_token
|
|
log "job-scoped token already present; no write performed"
|
|
exit 0
|
|
;;
|
|
11)
|
|
log "secret exists without 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
|
|
}
|
|
if [ -z "${new_token}" ]; then
|
|
log "Vault returned an empty random token"
|
|
exit 1
|
|
fi
|
|
|
|
set +e
|
|
create_error="$(printf '%s' "${new_token}" | \
|
|
vault kv put -cas=0 "${secret_path}" build_token=- 2>&1 >/dev/null)"
|
|
create_status=$?
|
|
set -e
|
|
unset new_token
|
|
if [ "${create_status}" -eq 0 ]; then
|
|
log "job-scoped token created with KV-v2 CAS create-only semantics"
|
|
exit 0
|
|
fi
|
|
|
|
# A simultaneous revisioned Job/Cron run may win the create. Accept that race
|
|
# only if a fresh retried read finds the complete field; never rotate it.
|
|
if printf '%s' "${create_error}" | grep -qi 'check-and-set'; then
|
|
unset create_error
|
|
if read_build_token; then
|
|
read_status=0
|
|
else
|
|
read_status=$?
|
|
fi
|
|
if [ "${read_status}" -eq 0 ]; then
|
|
unset build_token
|
|
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
|