GitLab CI/CD id_tokens to AWS STS AssumeRoleWithWebIdentity Prompt
Wire a GitLab id_token directly into AWS STS AssumeRoleWithWebIdentity so jobs get short-lived AWS credentials with no stored access keys — including the IAM trust policy conditions that pin the role to your project, branch, and environment.
- Target user
- Engineers killing long-lived AWS keys in GitLab pipelines
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a cloud security engineer who replaces static AWS keys in GitLab CI with OIDC federation, and who writes IAM trust policies tight enough that a fork or a wrong branch can never assume the role. I will provide: - My GitLab instance URL (or gitlab.com), full project path, and the protected branches/environments that should get AWS access - The AWS account ID and the permissions the job needs - Which jobs deploy where (e.g., staging vs prod roles) Your job: 1. **id_tokens block** — write the GitLab `id_tokens:` config that mints a JWT with `aud:` set to the AWS STS audience, and explain why the audience value must match what the IAM trust policy expects. 2. **OIDC provider** — give the exact AWS IAM OIDC identity provider setup for my GitLab issuer URL (the `iss` claim) and thumbprint guidance. 3. **Trust policy (CRITICAL)** — produce the IAM role trust policy `Condition` block pinning `aud` to my audience AND `sub` (or GitLab's project/ref/environment claims like `project_path`, `ref`, `ref_protected`, `environment`) so ONLY the intended project + protected ref + environment can assume it. Show separate staging and prod roles with least privilege. 4. **Job wiring** — write the `.gitlab-ci.yml` `script` that calls `aws sts assume-role-with-web-identity` (or exports the token for the AWS SDK/`AWS_WEB_IDENTITY_TOKEN_FILE`) and uses the temporary creds. Set short session duration. 5. **Blast-radius checks** — list what stops an attacker who opens an MR from a fork, or pushes to an unprotected branch, from getting creds (protected-ref claim, environment claim, `if:` rules gating the job). 6. **Verify** — give the `aws sts get-caller-identity` check and how to confirm no static `AWS_ACCESS_KEY_ID` remains in any variable scope. Output: (a) the `id_tokens:` block, (b) the AWS OIDC provider setup, (c) the staging + prod trust policies with claim conditions, (d) the job script, (e) the fork/branch blast-radius checklist, (f) the verify commands. Bias toward: least-privilege per-environment roles, pinning sub/project_path/ref_protected/environment in the trust policy, and the shortest viable session duration.
Why this prompt works
OIDC federation is the right way to get AWS credentials into CI, but the dangerous part isn’t the GitLab side — it’s the IAM trust policy. A trust policy that only checks the aud claim will happily hand short-lived admin credentials to any project hosted on the same GitLab issuer, because they all share the same OIDC provider. The interesting, security-relevant work is in the Condition block: pinning project_path, ref_protected, and environment so that only your repository, only on a protected branch, only when deploying to the named environment, can assume the role. This prompt makes that trust-policy hardening the centerpiece rather than an afterthought, which is exactly where generic tutorials fall short.
It also handles the two-role reality that production setups need. Staging and prod should be different IAM roles with different permissions and different trust conditions, so a staging deploy can never reach a prod resource. By asking the model to emit both roles with least-privilege policies and distinct claim conditions, you get an architecture you can actually ship, not a single all-powerful role that becomes the next audit finding. The job-wiring step covers both the explicit assume-role-with-web-identity call and the SDK-native AWS_WEB_IDENTITY_TOKEN_FILE path, so it fits however your tooling consumes credentials.
The blast-radius checklist is what turns this from a happy-path recipe into a defensible control. Fork MR pipelines, unprotected branches, and missing rules: gates are the classic ways OIDC setups leak — the prompt forces the model to enumerate each and show what blocks it. Combined with the verify step that confirms no static AWS_ACCESS_KEY_ID survives the cutover, you end up with a migration you can prove is complete, not one that quietly leaves the old keys in place as a fallback nobody removes.