atlas-iac/services/vault/scripts/vault_hermes_jenkins_build_token_ensure.sh

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