Cloud credentials stored permanently in CI/CD variables are a time bomb waiting to explode. This episode explains how to replace them with OpenID Connect — the pipeline requests a short-lived token directly from GitLab to authenticate itself to AWS, GCP, and Azure without storing a single long-term secret.

In the previous episode 12 we covered native DevSecOps — including Secret Detection catching credentials leaked into repositories. But there's one credential source that isn't visible in the repository: CI/CD variables permanently storing cloud access keys. In episode 5 we covered storing variables with the Masked and Protected flags — but those only slow down theft, not prevent it.
The old pattern for authenticating to the cloud from a pipeline always ends up like this: someone creates a dedicated IAM user, leaves its access key attached to the CI/CD settings, and that key lives for months. A single key like that is enough to tempt an attacker — and when it leaks, the whole cloud account is at stake. It's exactly like giving one house key valid for life to everyone passing by: convenient, but once lost, every door opens.
This episode introduces a far safer approach: passwordless cloud authentication using OIDC (OpenID Connect). The pipeline no longer stores permanent credentials — it requests a short-lived token directly from GitLab for each pipeline, and that token is only valid for minutes. Let's break it down.
Before discussing the solution, let's be honest about the problem. Storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as permanent CI/CD variables brings three major risks:
Warning
Never store long-lived cloud credentials — especially the AWS Secret Access Key — as permanent CI/CD variables. Masking only hides the value in logs; it doesn't prevent theft from inside a runner or from access that's already leaked. OIDC removes the need for those permanent credentials entirely.
OIDC is a protocol that's already everywhere — you use it every time you log into a website with "Sign in with Google". In the CI/CD context, the flow looks like this:
id_tokens in a job.aud value you set.aud and project path.The key is the JWT: this token is short-lived, bound to a specific pipeline context, and never stored anywhere. GitLab acts as the identity provider — just like Google or Okta, but specifically for pipelines.
id_tokens in .gitlab-ci.ymlThe configuration is just an id_tokens block at the job, pipeline, or default level. The important value is aud — the audience that must match what's configured in the cloud provider.
deploy-to-aws:
stage: deploy
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- echo "JWT tersedia di GITLAB_OIDC_TOKEN"
- echo "${#GITLAB_OIDC_TOKEN} karakter token"Once defined, the JWT is accessible as the shell variable GITLAB_OIDC_TOKEN inside the job — available only to the job that defines it. This is what gets sent to the cloud provider to exchange for temporary credentials. Note: if the variable name ends in _FILE (e.g. GITLAB_OIDC_TOKEN_FILE), GitLab writes the token to a file and that variable holds its path — useful for tools that need a file-form token.
Tip
The aud value is free-form, but make sure it's consistent between GitLab and the cloud provider. Many teams use https://gitlab.com for GitLab.com or their self-hosted instance URL. Some providers use aud to distinguish which pipelines are allowed to use which role.
On AWS, the flow uses an IAM Role + the Security Token Service (STS). You create an IAM role, configure GitLab as a web identity provider in IAM, then the pipeline exchanges its JWT for that role's credentials.
The AWS role trust policy (condensed) ensures only tokens from the correct GitLab project can use the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/gitlab.com"
},
"Action": "sts:TagSession",
"Condition": {
"StringEquals": {
"gitlab.com:aud": "https://gitlab.com"
}
}
}
]
}Inside the job, the pipeline exchanges the JWT for role credentials using the AWS CLI:
deploy:
image: amazon/aws-cli:latest
stage: deploy
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::123456789012:role/gitlab-deployer \
--role-session-name gitlab-pipeline \
--web-identity-token "$GITLAB_OIDC_TOKEN" \
--duration-seconds 3600The temporary credentials from this assume-role can be exported to the environment and used by subsequent AWS commands, then die on their own after 1 hour. No permanent access key is stored in GitLab.
Google Cloud uses Workload Identity Federation for the same purpose. The concept: you create a workload identity pool and a provider that trusts GitLab as an identity provider, then connect it to a service account. Because GCP requires the token in file form, the job uses the id_tokens variable convention ending in _FILE — GitLab writes the JWT to a file, and the variable holds that file's path.
gcloud iam workload-identity-pools create-cred-config \
projects/123456789/locations/global/workloadIdentityPools/ci-pool/providers/gitlab-provider \
--service-account=gitlab-deployer@my-project.iam.gserviceaccount.com \
--subject-token-type=urn:ietf:params:oauth:token-type:jwt \
--credential-source-file="$GITLAB_OIDC_TOKEN_FILE" \
--output-file="$CI_PROJECT_DIR/gcp-credential.json"
gcloud auth login --cred-file="$CI_PROJECT_DIR/gcp-credential.json"The common flow: gcloud ... create-cred-config produces a temporary credential file from the JWT, then gcloud auth login --cred-file uses it. The result — GCP access without any stored service account key.
Azure uses federated identity credentials in Azure AD. You create an app registration or user-assigned managed identity, add a federation credential that trusts GitLab, then the pipeline logs in with that JWT token:
az login \
--service-principal \
--username "$AZURE_CLIENT_ID" \
--tenant "$AZURE_TENANT_ID" \
--federated-token "$GITLAB_OIDC_TOKEN"| Provider | Mechanism | Credentials stored |
|---|---|---|
| AWS | IAM Role + STS AssumeRoleWithWebIdentity | None |
| GCP | Workload Identity Federation | None |
| Azure | Federated Identity Credentials | None |
The same pattern, different language: define id_tokens in GitLab, then exchange the JWT for temporary credentials from each provider.
| Mistake | Symptom | Solution |
|---|---|---|
| Forgetting to restrict claims in the trust policy | Other projects can use the role | Tighten aud, project path, and branch in conditions |
Using the deprecated $CI_JOB_JWT | Tokens expire unexpectedly | Use id_tokens with an explicit aud |
| Still storing an access key as a fallback | Long-lived key remains | Remove the key and use full OIDC |
| Token too permissive | Wider access than needed | Use minimal roles/scopes per job |
| Not testing rotation | Deployment suddenly fails when keys rotate | OIDC has no static keys; verify periodically |
Important
OIDC eliminates static credentials, but it's not without configuration. The final security lies in the trust policy — restrict the provider to trust only the correct projects, branches, and environments. A valid token is still useless if the trust policy conditions are properly narrowed.
In this episode we covered the risks of storing long-lived cloud credentials in CI/CD variables — long exposure windows, no context, and expensive revocation; how OIDC works in GitLab with id_tokens issuing short-lived JWTs; and integration with the three major cloud providers — AWS via STS AssumeRoleWithWebIdentity, GCP via Workload Identity Federation, and Azure via federated identity credentials — all without storing a single permanent secret.
The core of this episode: credentials that are never stored can't be stolen. With OIDC, the pipeline proves its identity via a one-shot, short-lived, context-bound token, replacing the permanent keys that have been the biggest weak point.
In the next episode 14 we'll manage what gets deployed to that cloud: Environments, Deployments & Manual Approval Gates — declaring environments, deployment history in the GitLab UI, manual approval gates for production, protected environments, and Review Apps. See you there!