How GitLab CI/CD OIDC replaces stored cloud keys
Pipelines that deploy to AWS, Azure or Google Cloud often hold long-lived access keys in CI/CD variables. Those keys work from anywhere, rarely rotate and are copied into every project that needs cloud access. GitLab offers a different path: each CI/CD job receives an ID token, a JWT that the cloud provider verifies through OpenID Connect before it issues short-lived credentials. The job never stores a cloud secret, and the credential expires when the job ends. GitLab publishes a setup guide for each of the three providers, and the feature is available in the Free, Premium and Ultimate tiers, so it does not depend on buying a higher edition for self-managed instances. The work sits in two places. In GitLab, you decide which projects and branches request an ID token and what the token's claims look like. In the cloud account, you register GitLab as an identity provider and write a trust policy or federated credential that accepts only the tokens you intend. Confirm the exact provider-side steps against the current GitLab guides for your target cloud, because the console screens and CLI commands change more often than the trust model.
Whether the switch actually reduces risk depends on the trust conditions, not on the token itself. The claims in the token identify where the job runs, and the cloud role should be conditioned on them. Plan to test the aud claim so that only the audience you configured is accepted, and to match the sub claim tightly, usually to the project and the protected branch or environment that deploys. A loose condition lets any pipeline that can request an ID token assume the role.
GitLab's guidance also warns that OIDC access applies to every pipeline where it is configured. Before you enable it, review which groups, projects and branches can run deployment jobs, and treat that review as part of your supply chain controls. Keep the trust policy in version control so changes are reviewed like any other infrastructure change.
What a self-managed GitLab OIDC setup requires before configuration
Before any trust relationship is configured, four things need to be in place and confirmed: the GitLab instance and its version, administrator access to the cloud account, the cloud identity provider and roles, and a runner to execute jobs. Each one constrains the configuration that follows, so it is worth checking them in your own environment first.
Self-managed GitLab instance and version
Confirm which GitLab version your instance runs and that it is on a supported release. GitLab maintains only the current and two prior minor lines, and upgrades must pass through required upgrade stops, so an instance on an older line may need upgrading before OIDC work begins. Check the version against the GitLab documentation for the ID token and cloud services features, since requirements can change between releases. Ask your GitLab administrator who owns the upgrade calendar.
Cloud administrator access
Creating an identity provider and roles in AWS, Azure or Google Cloud requires administrator-level permissions in the target account or subscription. Confirm who holds those rights, whether a change ticket is needed, and whether your organization enforces service control policies or organization policies that block external identity providers. Agree on the owning team for the trust configuration so that later changes to conditions are reviewed rather than made ad hoc.
Cloud identity provider and roles
The cloud provider must trust the GitLab instance as an OpenID Connect issuer, and each role or service account must define which tokens it accepts. Confirm the issuer URL your instance will publish, since it is derived from your GitLab hostname, and confirm that the cloud account can reach it. Plan role names and conditions in advance so that each role maps to a single project, group or environment.
Network reachability for OIDC discovery
The cloud provider fetches the discovery document and signing keys from your GitLab instance before it issues any credentials. If the instance sits behind a firewall, a private network or a restricted egress policy, that fetch can fail. Confirm with your network team and the provider documentation which endpoints must be reachable and from where, and test the discovery path before relying on it in pipelines.
Runner that executes the jobs
Jobs that request an ID token must run on a runner that your GitLab instance can use, and the runner needs outbound access to the cloud provider's token endpoints. Confirm which runner fleet will execute these jobs, its GitLab Runner version aligned with the instance, and whether it runs on infrastructure where the job can reach the cloud APIs. Document the runner tags so that the conditions you write match the jobs that should receive credentials.
Pipeline and project ownership
Every project and branch allowed by a trust condition can obtain cloud access, so the list of projects that will use OIDC is a security decision. Confirm which groups and projects need cloud access, who approves additions, and whether a supply chain review has been done. Record the project IDs and paths you intend to allow, since renamed projects can change the claims a condition matches.
Configuring the OIDC trust from GitLab to each cloud
This sequence sets up a cloud-side trust for GitLab's OIDC issuer, scopes a role or workload identity to specific projects, and proves that a pipeline job receives temporary credentials without any stored cloud keys.
Confirm the GitLab issuer and project identifiers
Before touching the cloud console, record the issuer URL of your self-managed GitLab instance, the group and project paths that will be allowed to assume a role, and the numeric project ID of each project. Your cloud administrator and the GitLab project maintainer should agree on this list together. Confirm the issuer is reachable over HTTPS from the cloud provider's token verification path, because a provider that cannot fetch your discovery document will reject every token. The step is done when you have a written list of issuer, project paths and project IDs that the trust policy will reference.
Register GitLab as an OIDC identity provider
In each cloud, create an OpenID Connect identity provider or workload identity pool that points at your GitLab issuer and trusts the audience value you plan to use. The exact resource names and console steps differ between AWS, Azure and Google Cloud, so follow the GitLab cloud services guide for each provider rather than improvising. Your cloud administrator makes this change, and it is a one-time configuration per cloud account or tenant. The check is that the provider appears in the cloud console with your GitLab issuer URL and the expected audience, and that the cloud reports no certificate or discovery errors.
Create a role scoped to the intended projects
Create a cloud role or workload identity that can be assumed only through the GitLab provider, with a trust condition that matches the claims of the projects you approve. Condition the role on the aud claim so only tokens minted for that audience are accepted, and on the sub claim so only the intended project paths or refs are accepted. Where your organization allows it, prefer the immutable project ID in the condition so a rename does not silently break or widen access. Attach only the permissions the deployment job needs. The check is a policy review by someone outside the team that wrote it, confirming that no wildcard subject grants access to every project in the group.
Request an ID token with the required audience
In the pipeline configuration of an approved project, add an id_tokens entry to the job that requests a token with the audience value the cloud trust expects. The job then receives a JWT in the variable you named. Keep the audience value consistent with the cloud-side condition, because a mismatch is the most common reason an exchange fails. Limit the job to the environments and branches that need cloud access. The check is a pipeline run in which the job starts and the token variable is populated, without printing the token itself to the log.
Exchange the token for temporary credentials
Within the job, call the cloud provider's token exchange endpoint or its official CLI or SDK helper with the GitLab ID token. The provider verifies the token signature against the GitLab issuer, checks the aud and sub claims against the role condition, and returns short-lived credentials. The job then uses those credentials for its deployment commands and does not write them to artifacts. Your platform team should confirm which exchange method the cloud documents for the runner image in use. The check is that the exchange returns credentials with an expiry time rather than a permanent key.
Verify the assumed identity on a non-production branch
Run a test job on a non-production branch that calls a read-only identity command, such as the cloud's caller identity or account-information command, and then performs one harmless read against a test resource. The job log should show the expected role or identity name and the expected account, tenant or project. Then run the same job from an unapproved branch or a project outside the trust and confirm that the exchange is refused. The step is complete when both the accepted and the refused runs behave as documented, and the results are saved with the change record.
Record the trust and plan ongoing review
Document the issuer, audience, role names, subject conditions and the approved project list in your change management record, and note who can alter the trust policy. Because access applies to every pipeline where the configuration exists, schedule a review of the project list and of any group-level or project-level settings that could extend the trust, and remove stale static cloud keys from CI/CD variables once the job runs successfully. Ask GitLab support or your partner to confirm the current setup steps for your version before rollout. The check is a reviewed record that names the owner of each trust and the date of the last access review.
How a CI/CD job exchanges its JWT for temporary credentials
When a pipeline job needs cloud access, GitLab issues that job an ID token, a signed JSON Web Token (JWT) that describes the job: the project, the branch or tag, the pipeline and the runner context. The job presents this token to the cloud provider's security service, which checks the signature against GitLab's OpenID Connect issuer. Only after the signature validates does the provider evaluate the claims in the token against the trust policy you wrote on the cloud side, such as the aud audience and the sub subject. If every condition matches, the provider returns temporary credentials that expire on their own after a short period. The job uses them for that pipeline and nothing is kept afterward.
The point of this flow is what is absent. No access key, client secret or service account key is stored in a group or project CI/CD variable, so there is no long-lived credential to copy, leak through a job log or forget to rotate. The trust relationship lives in the cloud identity configuration instead, where your cloud team controls it. Two things still need planning. First, the trust policy is the control, so a permissive condition grants access to every pipeline that can produce a matching token, and GitLab advises a supply chain review before enabling it. Second, confirm with the provider-specific GitLab guides, GitLab Docs: Connect to cloud services, which exact claims, audience values and role settings each provider expects, since these details differ between AWS, Azure and Google Cloud.
Verify the flow end to end before you rely on it. Run a test job that requests the token, decode its claims in a non-production project, and confirm the cloud role accepts it and rejects a job from a project outside the intended scope. Also confirm that the temporary credentials expire as expected and that your audit logs in the cloud record the role assumption with the GitLab project as the source. Ask your cloud and platform teams to agree on who owns the trust policies and how changes to them are reviewed, because the security of this flow depends more on those policies than on the pipeline YAML.
Which token claims to condition cloud roles on
This table compares the ID token claims a cloud role trust policy can check, what each one proves, where it breaks, and the condition to write. Read it before writing any trust policy, because a condition that matches too loosely lets other pipelines assume the role. The main takeaway is to pair a stable identifier such as project_id with an explicit audience check, and to avoid trusting the default sub claim alone.
| Claim | What it proves and where it breaks | Recommended trust condition |
|---|---|---|
| aud (audience) | Set by the job and checked by the provider. It shows the token was requested for that cloud, but any job that sets the same audience can present a token with it. It does not identify the project. | Require the expected audience value exactly, such as the audience the provider setup guide specifies for that cloud. Never use a wildcard. |
| sub (default) | Built from project path, ref type and ref. It proves which project and branch or tag ran the job. It breaks when a project is renamed or moved, because the path changes and the role stops matching, or a new project takes over the old path. | Match the exact path, ref type and ref for the branch or tag allowed to deploy. Avoid wildcards on the ref, since they widen access to every branch. |
| project_id | An immutable numeric identifier for the project. It survives renames and transfers, so a trust policy keyed to it keeps working after path changes. It does not show which branch ran the job, so pair it with a ref condition. | Condition on the project_id value together with the audience. Confirm with your provider that the claim is passed through in the token format you use. |
| ref and ref_type | Show the branch or tag name and whether it is a branch or tag. They prove which ref ran the job, but a ref name can be created by anyone with push rights unless protected branch rules apply. | Combine with protected branch or tag settings in GitLab, and match the exact ref name in the trust policy. |
| ref_protected | Indicates whether the ref is protected in GitLab. It helps separate protected release branches from feature branches, but it depends on repository protection settings being maintained. | Require the value to be true for production roles. Confirm the claim is present in your token before relying on it. |
| pipeline_source and environment | Describe how the pipeline started and which environment the job targets. They narrow access to scheduled or deployment runs, but they depend on the job definition and can be changed by anyone who edits .gitlab-ci.yml. | Add these as additional conditions for production roles, and review who can change pipeline configuration. |
| namespace_id and namespace_path | Identify the group that owns the project. They help when a whole group should share a role, but a group rename changes the path while the ID stays fixed. | Prefer the numeric ID for group scoping. Confirm the claim names in your GitLab version before writing conditions. |
Why stable project IDs matter after renames
The default sub claim in a GitLab CI/CD ID token is built from the project path, the ref type and the ref, so the project path is part of the identity your cloud role trusts. If a team renames a project, moves it into another group or changes its namespace, the sub value changes. A trust policy that matched the old path will then stop accepting that job, and the pipeline fails at the credential exchange. That failure is the easy case. The harder one is a trust policy written with a broad pattern, such as a wildcard over a group path. A new project created later at a path the pattern matches, or an existing project moved into that group, can then obtain the same role without anyone reviewing the change. Confirm the exact claim format for your GitLab version in the GitLab cloud services documentation before you write any condition.
The safer pattern is to condition the role on the project's numeric ID, which does not change when the project is renamed or moved. GitLab supports switching the subject to immutable identifiers such as project_id, and AWS, Azure and Google Cloud each let you match claims in the trust policy, federated credential or workload identity provider. Where the provider exposes a claim for the numeric project ID, match on that claim alongside aud and the ref constraints you need, and avoid wildcards on the project path. Check whether your provider's condition syntax can express this combination, because the exact field names differ between providers and should be taken from each provider's own documentation. Record the project ID with the role in your change records so a reviewer can see what the role trusts.
Test a rename before it happens to production. Create a non-production project with the same CI/CD configuration, a throwaway role or federated identity, and a job that requests credentials and makes a harmless read call. Run the job, rename the project, then run it again and observe whether the exchange succeeds, fails or succeeds under a different subject. Then move the project into another group and repeat the test. Keep the decoded token from each run so you can compare the sub and project_id values. Document the result as a runbook step: anyone who renames or transfers a production project should know which trust policies to update and should confirm that the old path no longer grants access.
Scoping OIDC access so it does not cover every pipeline
An OIDC trust policy is only as narrow as its conditions. Without deliberate scoping, any pipeline in a configured project or ref can obtain the same cloud role, so the controls below need to be designed before the first job exchanges a token. Each one closes a different gap between who can run code and who can reach your cloud accounts.
Condition the sub claim tightly
The sub claim is the main lever in a cloud trust policy. Match it to the full project path and the protected ref, not a wildcard across a group. Compare the default format, which combines project path, ref type and ref, with an immutable project_id form, and decide which one your trust policy will check. Test each condition with a job that should fail as well as one that should succeed.
Use one role per environment
Give development, staging and production their own cloud roles, each trusted only by the projects and refs that deploy there. A shared role across environments means a compromised branch in a low-trust project can reach production resources. Name roles so audit logs show which environment a session was issued for, and keep production trust limited to protected branches.
Grant least-privilege permissions
Attach only the actions each pipeline needs to the role, scoped to specific resources such as one bucket, one registry repository or one deployment target. Broad managed policies copied from a tutorial often grant far more than a deploy job requires. Review the policy with the team that owns the workload, and expect to revise it after the first real pipelines run.
Restrict who can change CI/CD config
Anyone who can edit .gitlab-ci.yml, add a runner or change a protected branch can potentially request the cloud role. Limit maintainer rights, require merge request approval on protected branches, and restrict who can modify pipeline configuration or include files from other projects. Confirm these settings in your own instance, since the exact options depend on your GitLab version and tier.
Review the supply chain before rollout
GitLab advises a supply chain review before enabling OIDC because access applies to every pipeline where the configuration exists, including pipelines from forks, scheduled jobs and included templates. List every project and group that will receive a trust relationship, identify who can alter their pipelines, and record the reason each one needs cloud access. Repeat the review when ownership or project structure changes.
Retire static keys after cutover
Once a pipeline authenticates through OIDC and a test deployment succeeds, remove the long-lived access keys from CI/CD variables, rotate any copies that existed elsewhere, and deactivate the IAM user or service principal behind them. Keeping old keys as a fallback leaves a credential that bypasses every condition above. Record the removal date as evidence for your change records.
Why the cloud rejects the job token
This table lists the failures teams most often hit when a CI/CD job tries to exchange its ID token for cloud credentials, with the likely cause, how to confirm it from job logs and token claims, and the fix. Read it top to bottom when a job fails at the credential step, and compare the decoded claims in the job against the trust policy before changing anything.
| Failure | Likely cause | How to confirm | Fix |
|---|---|---|---|
| Audience mismatch | The aud value in the job token does not match the audience the cloud trust policy or federated credential expects. Teams often change one side only. | Decode the ID token in a test job and compare aud with the audience in the role trust condition or federated identity credential. The cloud error usually names an invalid audience. | Set the aud in the job's id_tokens configuration to the value the trust policy expects, or update the trust policy to match. Change one side deliberately and document it. |
| Sub claim mismatch after a rename | The default sub claim includes the project path, so renaming or moving the project changes the subject the role was written against. | Compare the sub claim in the decoded token with the value in the trust condition. A path change in the job log or project settings explains the difference. | Switch the condition to a stable identifier such as project_id, as GitLab documents for immutable claims, then update the trust policy once. Confirm the new sub format in a test job first. |
| Sub claim mismatch from a different ref | The job runs on a branch, tag or ref type the trust condition does not allow, so the default sub value differs from the expected pattern. | Check the ref, ref type and ref claims in the decoded token against the condition. A job from a feature branch often fails while the main branch succeeds. | Narrow or widen the condition on purpose: allow only protected refs for production roles, and use a separate role for merge request pipelines if they need access. |
| Issuer URL or thumbprint mismatch | The OIDC provider registered in the cloud does not match the issuer in the token, or the certificate thumbprint configured no longer matches the endpoint in use. | Compare the iss claim in the token with the issuer URL registered in the cloud account. Check the provider configuration for the thumbprint or trusted key settings. | Register the exact issuer URL GitLab presents for your instance, including any custom domain. Recreate or update the provider configuration if the endpoint or certificate has changed. |
| ID token not requested | The job does not declare id_tokens, the runner or an older configuration does not pass the token through, or the job uses a variable that is not set. | Search the job log for the id_tokens block and check whether the token variable is populated. A missing token often causes a silent empty value in the cloud CLI step. | Add an id_tokens block to the job that requests the token with the correct aud. Confirm the job runs on a runner version that supports ID tokens and that no job-level override removes it. |
| Missing role permissions | The federation succeeds and temporary credentials are issued, but the role lacks the permissions the deployment step needs. | The cloud call fails with an authorization error after the credential exchange log line appears. The assumed role or service account identity in the error shows which principal was used. | Attach only the permissions the job needs to that role, test each action in a non-production account, and avoid granting broad access just to clear the error. |
| Clock skew or token expiry | The token is short-lived, so a slow job, a delayed step or a clock drift between the runner and the cloud endpoint can invalidate it before use. | Look for timestamp errors such as token not yet valid or expired in the cloud response. Compare the exp and iat claims with the time shown in the job log. | Exchange the token at the start of the job step that needs it, keep the job steps that use credentials short, and check runner host time synchronization. Confirm token lifetime limits with each cloud provider's documentation. |
Questions teams ask before moving off stored cloud keys
How much effort does moving off stored cloud keys take?
The effort depends mainly on how many projects and pipelines use cloud keys, how many cloud providers and accounts are involved, how many environments (development, staging, production) each pipeline deploys to, and how the existing keys are used today. A team with a handful of projects in one cloud account usually has a much smaller job than one with dozens of projects spread across several accounts. Each cloud needs its own identity provider and roles, and each role needs trust conditions written for the right projects and branches. Ask any provider for a written scope that lists the projects, providers and environments in the first phase, because that list drives the quote more than anything else.
Can our own platform team do this without a partner?
Often yes, if the team already administers its cloud accounts and GitLab, and has time to test each role before removing the old keys. The work is mostly configuration: an OpenID Connect trust with the cloud provider, roles with conditions on the aud and sub claims, and CI/CD jobs that request an ID token and exchange it for temporary credentials. The harder parts are inventorying every stored key, deciding which roles each project needs, and rolling out changes without breaking deployments. A partner is most useful when the inventory is large, several clouds are involved, or the team needs the change documented as audit evidence.
What do we need to provide before the work starts?
Plan for four things. First, cloud administrator access to create identity providers and roles in each account or subscription. Second, GitLab administrator or group owner access so the team can confirm the instance settings and the projects in scope. Third, a change window agreed with the teams that own production pipelines, since removing a stored key can stop a deployment. Fourth, a test project that mirrors a real pipeline, so roles can be proven before production jobs switch over. Ask the provider to confirm exactly which accounts, projects and approvals they expect from your side.
Is OIDC for CI/CD available on our GitLab tier?
According to GitLab's cloud services documentation, CI/CD ID tokens for connecting to AWS, Azure and Google Cloud are available in the Free, Premium and Ultimate tiers, so the tier you hold does not block this change. Confirm the tier of your self-managed instance before planning, because other features you may need alongside it, such as secrets managers that read ID tokens, can be tier-specific. Your GitLab version also matters, so check the release you run against the GitLab documentation before starting.
Which claims should we check in the cloud trust policy?
Restrict the trust to the audience you configure and to specific subjects, rather than allowing any project on the instance to assume the role. The sub claim by default combines the project path, ref type and ref, so a role can be limited to one branch of one project. GitLab also supports immutable identifiers such as project_id, which keep a rule valid after a project is renamed or moved. Test each condition with a deliberately wrong project to confirm the cloud provider refuses the exchange, and document the conditions as part of your change record.
Why do stable project IDs matter after a rename?
Path-based subject claims change when a project is renamed, moved to another group or transferred, and a role trusting the old path then stops working or, worse, can be matched by a different project that takes the old path. Using the immutable project identifier in the condition keeps access tied to the project itself. Before relying on it, confirm the claim format with GitLab's documentation and test a rename in a non-production project so you know how your trust policies behave.
Who should review the access before we switch off the old keys?
Access granted through OIDC applies to every pipeline where the trust is configured, so GitLab recommends a supply chain review before rollout. Involve the security owner, the cloud account owner and the leads of the projects that will receive access. The review should list which projects and branches can obtain credentials, which roles they assume and what those roles can change, including production. Once the review is signed off, disable the old keys in stages and keep a record of who approved each step.
How DeployOpen scopes an OIDC migration for your pipelines
DeployOpen plans and implements the move from stored cloud keys to OIDC ID tokens in self-managed GitLab, starting from an inventory of the pipelines and cloud accounts involved. Each engagement is fixed-scope, with deliverables and timelines agreed in writing before work starts.
How we scope
- Inventory every CI/CD variable that holds a cloud access key, and map which projects and branches use it.
- Agree which AWS, Azure or Google Cloud accounts, roles and identity federation settings are in the written scope.
- Record the trust conditions planned for each role, including aud and sub claim values and the project ID strategy.
How we build
- Configure identity providers and roles in your cloud accounts, with least-privilege permissions per pipeline purpose.
- Update job definitions to request ID tokens and exchange them for temporary credentials, one pipeline pattern at a time.
- Test trust conditions against protected and unprotected branches, and confirm that unintended refs cannot assume a role.
How we hand over
- Deliver documented role definitions, trust policies and job templates that your platform team can review and change.
- Document the revocation steps for old keys and the removal of obsolete CI/CD variables after cutover.
- Walk your team through the supply chain review, so owners know which pipelines can reach each cloud account.
How we support
- Ongoing support, upgrades and continuous testing are available as a retainer, with scope agreed in writing.
- Review role trust conditions when projects are renamed, moved or added, to keep access tied to stable project IDs.
- Confirm cloud provider and GitLab version behavior with the vendors and your own environment before each change.

