ID Tokens, OIDC Workload Identity, Cloud Federation, Vault Authentication, and Secretless Deployments: Configuration, Design Choices, and Tradeoffs
Choose deliberately between static secrets and federation, broad and narrow trust, service-specific audiences, and direct-provider versus Vault-broker designs.
Learning objectives
- Choose between static secrets and OIDC federation based on provider support, blast radius, operational burden and auditability.
- Design trust conditions around stable project identity plus ref/environment context instead of broad wildcard subjects.
- Use service-specific audiences and avoid accidental cross-service token reuse.
- Compare direct cloud federation with a Vault-broker architecture.
- Predict the repository, GitLab, provider and external states affected by each design.
1. Start with authorization invariants, not provider syntax
A provider-specific JSON policy is implementation detail. First
define the invariant in plain language: “Only jobs running in
project 42001, from protected main, targeting staging,
may assume role staging-writer, and that role may
mutate only the staging namespace.” Then encode the same invariant
using the provider's supported claims and policy language.
This keeps cloud migration possible. AWS, Azure, GCP and Vault use different federation mechanics, but they can all enforce a narrow trust relationship.
2. Static secret versus OIDC federation
| Choice | Advantages | Risks / operational cost | Evidence |
|---|---|---|---|
| Static provider secret | Works with providers that lack OIDC; simple mental model. | Rotation, storage, copying, leak persistence, offboarding, secret exposure to eligible jobs. | Secret version/owner/rotation + scope + use logs; never value. |
| OIDC federation | No stored cloud key; short-lived credentials; job context can enter authorization. | Provider trust policy becomes critical; issuer/JWKS reachability and claim mapping must be correct. | Token request/audience + provider decision + role/scope/expiry + external action. |
Federation is preferable where supported, but “secretless” describes the absence of a stored long-lived provider secret in GitLab. The provider still issues a credential after exchange, and that temporary credential must be protected.
3. Broad project trust versus ref/environment-specific trust
Too broad:
issuer = GitLab
subject starts with project_path:academy/app
=> every branch may assume deployment role
Narrower invariant:
issuer = GitLab
audience = deployment STS
project/job_project ID = expected project
ref = main
ref_protected = true
environment = production
deployment_tier = production
=> only the intended deployment context can exchange
Do not assume every provider supports every GitLab custom claim in
exactly the same way. GitLab's current docs explicitly note that AWS
on GitLab.com exposes additional GitLab condition keys, whereas
self-managed/Dedicated AWS federation supports only
sub as an AWS condition key. Design to the actual
provider/offering capability and document the limitation.
4. One broad audience versus service-specific audiences
deploy_cloud:
id_tokens:
CLOUD_ID_TOKEN:
aud: https://sts.cloud.example
script:
- ./cloud-exchange "$CLOUD_ID_TOKEN"
read_vault:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
script:
- ./vault-exchange "$VAULT_ID_TOKEN"
Separate audiences make accidental token reuse visible. GitLab also permits an audience array, but granting one token multiple relying parties increases the surface in which that assertion might be accepted. Use multiple audiences only when the relying-party design truly requires it.
5. Direct provider federation versus Vault broker
| Architecture | Good fit | Tradeoffs |
|---|---|---|
| Direct GitLab → cloud provider | Few providers/roles; provider has strong OIDC federation; team owns cloud IAM. | Simpler path, but trust policy is duplicated across providers/accounts. |
| GitLab → Vault → provider/secret | Central secret governance, dynamic credentials, multiple backends, policy managed by security/platform team. | Extra service availability/operations; Vault policy/auth method becomes critical; native GitLab Vault integration is Premium/Ultimate. |
Vault can reduce provider-specific logic in application pipelines, but it is not automatically safer. A broad Vault role that returns powerful credentials simply centralizes the same authorization flaw.
6. Subject design and stable identifiers
The default sub is project-path/ref-oriented. Current
GitLab also offers stable project/namespace IDs and job-project
variants. Prefer trust rules that cannot silently follow a renamed
group/project into unintended authority. On providers with only
sub support, keep the subject exact and pair it with
branch/tag protections and careful project administration.
user_login or
user_email as the only condition where providers expose
those claims.
7. Environments make deployment intent observable
If a job declares an environment, current ID tokens can carry environment name, protection state, deployment tier and action. This lets a provider distinguish a staging job from a production job even when both run from the same project/ref. That is useful defense in depth, but only if the environment declaration itself is governed and the provider checks it.
8. Token lifetime versus job timeout
Because ID-token expiry follows the job timeout when specified, very long deployment timeouts also lengthen the assertion lifetime. Set realistic job timeouts and design provider credential sessions separately. Do not increase job timeouts merely to “fix” an exchange that fails for an unrelated audience or trust-condition problem.
9. Keep repository, GitLab, runner and provider state distinct
| State layer | Evidence to capture | Why it matters |
|---|---|---|
| Source/revision |
CI_PIPELINE_SOURCE, ref/ref type,
CI_COMMIT_SHA, pipeline/job IDs
|
Workload identity must be tied to the exact job and source hypothesis being authorized. |
| Compiled CI configuration |
Merged YAML, id_tokens request, rules result,
environment declaration, CI config SHA
|
Proves which token request and deployment job GitLab actually compiled. |
| Job/runner | Job ID, runner ID/environment, executor/image/tool versions | The token belongs to a job execution context, not to a repository in the abstract. |
| OIDC token request | Token variable name, audience, intended relying party | The audience is an authorization boundary; do not mint one broad token for unrelated services by default. |
| Token claims |
Issuer, subject, project/job IDs, ref,
ref_protected, environment, SHA, expiry
|
These are provider inputs. Record safe selected claims, never the raw live JWT. |
| Provider/Vault trust | Issuer/JWKS, audience, bound subject/IDs/ref/environment conditions, role/policy version | The external service—not GitLab—decides whether the presented identity is trusted. |
| Short-lived credential | Credential class, role, scope, issue/expiry metadata; never secret material | Federation reduces stored-secret lifetime only if the exchanged credential is itself narrow. |
| External action | Exact API/resource/namespace changed, request/result ID, read-back verification | Authentication success is not deployment success or target health. |
| Governance | Trust-policy review, owner, emergency change evidence, expiration/rotation expectations | Provider-side trust is production authorization code and must be reviewable. |
10. Worked design: production deployment
Suppose a team deploys one artifact to staging and production. The
production role may write only one namespace. The project is private
and the production environment is protected. A safe design can
require: exact job project ID, main, protected ref,
production environment/tier, a production-only audience, and a
provider role whose permissions cover only the production namespace.
The pipeline rule and protected environment control reduce
accidental job creation; the provider policy independently limits
credential issuance.
| Decision | Choice | Why | Observable proof |
|---|---|---|---|
| Identity | OIDC, not static key | Short-lived job-specific assertion. |
id_tokens config + provider exchange metadata.
|
| Audience | Production STS only | No cross-service reuse. |
aud request + provider expected audience.
|
| Project | Stable job project ID | Survives path rename and handles job-project context. | Selected safe claim + provider condition. |
| Ref | Protected main |
Feature branches cannot exchange. |
ref, ref_protected, denied
negative test.
|
| Environment | production + production tier | Staging job cannot assume production role. | Environment claims + provider conditions. |
| Provider permission | One namespace write | Short-lived does not mean broad. | Role policy hash + successful bounded action + denied out-of-scope action. |
11. Tier/offering and trust prerequisites
- ID tokens: Free/Premium/Ultimate on GitLab.com, Self-Managed, Dedicated.
- Native GitLab external-secrets/Vault keyword flows: currently Premium/Ultimate.
- AWS/Azure/GCP federation: GitLab side is available on Free; the provider account and IAM privileges needed to configure federation are external prerequisites.
- Self-managed GitLab: the relying party generally needs reachable OIDC discovery/signing metadata; do not weaken TLS to work around reachability.
12. Migration from static keys
Migrate one role/action at a time. First establish federation and prove allowed + denied cases. Then run read-only or no-op operations. Next grant the narrow mutation. Only after the federated path is verified should you revoke the old long-lived key. Preserve evidence of the revocation and ensure rollback does not mean silently reintroducing a forgotten administrator key.
Knowledge check
Why can direct cloud federation be safer operationally than a static secret?
The provider can issue short-lived credentials only to a claim set that passes trust policy, eliminating storage/rotation of a long-lived cloud key in GitLab.
Why is a single audience for Vault and cloud STS usually a poor default?
It broadens where one token might be accepted. Service-specific audiences make relying-party intent explicit and easier to audit.
A provider supports only sub. What should you do?
Make the subject condition exact enough for the intended project/ref context, use GitLab protections/rules as defense in depth, document the limitation, and do not invent unsupported claim checks.
Does a protected GitLab environment replace provider least privilege?
No. It governs deployment authorization in GitLab. Provider IAM still determines what the exchanged temporary credential can do.
What is the safe rollback during a federation migration?
Prefer a reviewed, time-bounded fallback that preserves least privilege. Do not restore an old broad static administrator key as an undocumented shortcut.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-12. GitLab
documents explicit id_tokens as available on Free,
Premium, and Ultimate across GitLab.com, Self-Managed, and
Dedicated. CI_JOB_JWT/CI_JOB_JWT_V2 were
removed in GitLab 17.0; use explicit ID tokens instead. ID tokens
are RS256-signed. Their expiry is the job timeout when one is
specified, otherwise five minutes. Current custom claims include
project/namespace identity, job identity, ref/ref protection,
pipeline source, runner identity, source SHA, CI configuration
identity, and—when the job declares an environment—environment
name/protection/tier/action. GitLab recommends stable IDs such as
project/namespace IDs in trust policy conditions where the target
provider supports them. Native
secrets:vault integration is Premium/Ultimate, while
ID-token federation itself is Free. Provider claim support is not
uniform. Always verify the exact provider/offering guidance before
translating a conceptual condition into AWS IAM, Entra federated
credentials, Google CEL/attribute conditions, or Vault bound claims.
- OIDC authentication using ID tokens — official reference.
- Connect to cloud services — official reference.
- CI/CD YAML — id_tokens — official reference.
- AWS OIDC tutorial — official reference.
- Azure OIDC tutorial — official reference.
- Google Cloud workload identity federation — official reference.
- Use HashiCorp Vault secrets — official reference.
- Vault authentication tutorial — official reference.
- External secrets in CI/CD — official reference.
- GitLab deprecations and removals — official reference.
- OpenID Connect Core 1.0 — official reference.
- RFC 7519 — JSON Web Token — official reference.
Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.