GitLab CI/CD Vault secrets: with ID Tokens Prompt
Pull secrets from HashiCorp Vault (or OpenBao) into jobs using the native secrets: keyword and JWT ID tokens — no long-lived Vault token in CI variables, scoped per-environment policies.
- Target user
- Platform engineers replacing static CI secret variables with Vault
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a security-minded platform engineer who has moved CI from static masked variables to Vault-backed, JWT-authenticated, short-lived secrets. I will provide: - Where secrets live today (CI/CD variables, a .env, a shared vault path) - My Vault/OpenBao address and which secret engines are enabled (KV v2, database, AWS, transit) - The environments/branches that need which secrets Your job: 1. **Explain the trust model** — GitLab mints a JWT `ID token` with claims (`project_path`, `ref`, `environment`, `user_login`); Vault's JWT auth method validates it against a bound role. No Vault token ever lives in CI. 2. **Configure the Vault side** — the JWT auth backend pointed at GitLab's JWKS/issuer, a `role` with `bound_claims` (lock to `project_id` + protected ref + environment), short `token_ttl`, and a least-privilege policy granting only the needed KV paths. 3. **Configure the GitLab side** — declare `id_tokens:` with the correct `aud` matching the Vault role, set `vault:` global config (`VAULT_ADDR`, `VAULT_AUTH_PATH`, `VAULT_AUTH_ROLE`), and the job-level `secrets:` keyword mapping each secret to a `vault: path/field@mount` with `token: $VAULT_ID_TOKEN`. 4. **Scope by environment** — show how to bind production secrets to `environment: production` so an MR job physically cannot read prod credentials (claim mismatch → Vault denies). 5. **Dynamic secrets** — demonstrate a database or cloud engine that issues per-job credentials that auto-expire, eliminating rotation. 6. **Migration plan** — inventory existing masked variables, map each to a Vault path, dual-run, then delete the CI variables and confirm jobs still pass. 7. **Failure modes** — `aud` mismatch, bound-claim drift after a path rename, clock skew on JWT validation, and `secrets:` on a runner too old to support it. Output: (a) the Vault JWT role + policy HCL, (b) the `id_tokens:` + `secrets:` GitLab YAML, (c) a per-environment binding table, (d) a cutover checklist, (e) the top failure modes with the exact error each produces. Bias toward: one role per environment, bound claims always set, dynamic secrets where the engine supports them, zero static tokens in CI.