Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Workload Identity Federation Without Long-Lived Keys

By Kokil Thapa | Last reviewed: August 2026

Static AWS access keys in a GitLab CI variable have a way of outliving the engineer who created them. When that key leaks—or sits in a repo for three years—you get an incident, not a deploy. Workload Identity Federation Without Long-Lived Keys replaces those secrets with short-lived tokens minted by your CI platform or Kubernetes cluster and exchanged for cloud credentials at runtime. If you already run GitLab CI pipelines for Laravel, this is the upgrade path that removes the highest-risk secret from your pipeline without rewriting your application.

What is Workload Identity Federation Without Long-Lived Keys?

Workload identity federation is a trust bridge. Your cloud provider trusts a specific external identity provider (IdP)—GitHub Actions, GitLab CI, Google Kubernetes Engine, Azure AD—and accepts signed OIDC tokens from that IdP as proof of who is calling. The cloud then issues temporary credentials bound to a role with explicit permissions and a short expiry, typically 15 minutes to one hour.

The pattern solves a problem every small team recognises: long-lived keys are easy to copy, hard to rotate, and almost impossible to audit per workload. A GitHub Actions runner that uploads assets to S3 should not carry the same key as your production Laravel queue worker. Federation scopes access to this pipeline, this branch, this repository—not to everything the key ever touched.

In my experience maintaining Deployer 7 + GitLab CI pipelines on shared EC2 infrastructure, the first security win is not fancy encryption—it is deleting the `AWS_ACCESS_KEY_ID` variable entirely. Federation makes that deletion technically straightforward once the trust relationship is configured.

Long-Lived Keys vs Workload Identity FederationStatic Access KeysKey stored in CI variableSame key for yearsFull account scope if leakedOIDC FederationCI mints OIDC JWT tokenCloud validates + assumes role15–60 min scoped credentials
Workload Identity Federation Without Long-Lived Keys replaces permanent secrets with short-lived, scoped cloud credentials.

Core components you need to understand

  • Identity provider (IdP): GitHub Actions, GitLab CI, CircleCI, or a Kubernetes service account issuer that signs OIDC tokens.
  • Identity pool / OIDC provider: The cloud-side resource that registers the IdP's issuer URL and validates token signatures.
  • Attribute mapping: Maps JWT claims (`sub`, `repository`, `ref`, `aud`) to cloud principal attributes.
  • IAM role / service account binding: The target role your workload assumes after token validation.
  • Condition keys: Trust-policy constraints that limit which repos, branches, or environments can assume the role.

How does OIDC-based workload identity federation work?

The flow is the same across AWS, Google Cloud, and Azure even though the console labels differ. A workload requests an OIDC token from its platform. The token is a signed JWT containing claims about the caller. The workload presents that JWT to the cloud provider's Security Token Service (STS) or equivalent. The cloud validates the signature against the IdP's JWKS endpoint, checks audience and expiry, evaluates trust-policy conditions, and returns temporary credentials.

OIDC Token Exchange FlowCI RunnerOIDC IdPGitHub / GitLabCloud STSAWS / GCP / Azure1. Request job token2. Sign JWT3. Present JWT4. Validate: issuer, aud, exp, signature via JWKS5. Evaluate trust conditions (repo, branch, environment)Temporary credentials returnedAccessKeyId + Secret + SessionToken (AWS)6. Deploy, upload, run Terraform — then expire
OIDC-based Workload Identity Federation Without Long-Lived Keys: every credential expires automatically after the job finishes.

JWT claims that matter for CI/CD

Trust policies should reference specific claims, not wildcards. On GitHub Actions, `sub` looks like `repo:org/repo:ref:refs/heads/main`. GitLab CI exposes `project_path`, `ref`, and `environment`. Always pin the `aud` (audience) claim—AWS expects `sts.amazonaws.com` for role assumption; GCP expects your workload identity provider resource name.

A common mistake is trusting the issuer but not constraining `sub`. That lets any repository in the organisation assume your production role. Tighten conditions before you merge the pipeline change.

How do you configure AWS IAM roles for OIDC without access keys?

AWS calls this IAM Roles for OIDC Identity Providers. GitHub and GitLab each have a well-known issuer URL. You register it once per AWS account, create a role with a trust policy scoped to your repo, and configure your pipeline to request the OIDC token and call `sts:AssumeRoleWithWebIdentity`.

Step 1: Create the OIDC identity provider in AWS

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab063fa8120a90c3970c9eb877743

For GitLab.com, the issuer URL is https://gitlab.com. Self-hosted GitLab uses your instance URL. Thumbprints change rarely; verify against current AWS documentation if creation fails.

Step 2: Write a tight trust policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:myorg/laravel-app:ref:refs/heads/main"
        }
      }
    }
  ]
}

Attach a permissions policy granting only what the deploy needs—S3 sync to a single bucket prefix, ECR push to one repository, or SSM parameter read. Never attach `AdministratorAccess` to a CI role because federation feels "safer." Scoped beats broad every time.

Step 3: GitHub Actions workflow (no keys in secrets)

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy-main
          aws-region: ap-south-1
      - run: aws s3 sync public/build s3://my-bucket/assets/

The companion article on deploying to AWS from GitHub Actions with OIDC walks through a full Laravel asset-upload pipeline. The same pattern applies to Terraform, ECS task registration, and RDS snapshot exports—anything the AWS CLI or SDK can do with temporary credentials.

Step 4: GitLab CI equivalent

deploy:
  id_tokens:
    AWS_ID_TOKEN:
      aud: sts.amazonaws.com
  script:
    - >
      export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
      $(aws sts assume-role-with-web-identity
      --role-arn arn:aws:iam::123456789012:role/gitlab-deploy
      --role-session-name gitlab-${CI_PIPELINE_ID}
      --web-identity-token ${AWS_ID_TOKEN}
      --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]"
      --output text))
    - aws s3 sync public/build s3://my-bucket/assets/

GitLab's `id_tokens` keyword (available in GitLab 16.0+) is the clean path. For older runners, use the gitlab-actions OIDC flow or migrate the runner version before deleting static keys.

How does Workload Identity Federation compare across AWS, GCP, and Azure?

All three major clouds support federation without long-lived keys, but the naming and setup steps differ enough to trip up multi-cloud teams. The table below summarises what you configure on each platform in 2026.

PlatformFeature nameIdP registrationCredential typeTypical CI integration
AWSIAM OIDC Identity ProviderIAM → Identity providersSTS temporary access keys (15 min–12 hr)GitHub Actions, GitLab CI, CircleCI
Google CloudWorkload Identity FederationIAM → Workforce / Workload Identity PoolsOAuth 2.0 access token for impersonationGitHub Actions, GKE Workload Identity
AzureFederated identity credentialsApp registration → Certificates & secrets → Federated credentialsAzure AD token → ARM / storage accessGitHub Actions, Azure Pipelines OIDC

Google Cloud Workload Identity Federation

GCP separates workload identity pools (external IdP trust) from service account impersonation (what the workload can do). Create a pool, add an OIDC provider, map attributes, then grant `roles/iam.workloadIdentityUser` on the target service account.

gcloud iam workload-identity-pools create "github-pool" \
  --location="global" \
  --display-name="GitHub Actions Pool"

gcloud iam workload-identity-pools providers create-oidc "github-provider" \
  --location="global" \
  --workload-identity-pool="github-pool" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository"

In GitHub Actions, use google-github-actions/auth@v2 with workload_identity_provider and service_account inputs. The action exchanges the OIDC token for a GCP access token without a JSON key file.

Azure federated credentials

Azure attaches federated credentials directly to an app registration or managed identity. You specify the issuer (`https://token.actions.githubusercontent.com`), subject (`repo:org/repo:environment:production`), and audience (`api://AzureADTokenExchange`). GitHub's azure/login@v2 action handles token exchange. For Azure Pipelines, enable OIDC on the service connection and remove the service principal secret.

Multi-Cloud Federation TopologyCI Platform (OIDC IdP)Signed JWT (sub, aud, ref)AWSIAM OIDC ProviderAssumeRoleWithWebIdentityGCPWorkload Identity PoolService Account ImpersonationAzureFederated CredentialAzure AD Token ExchangeEach cloud returns short-lived scoped credentialsNo shared static keys across providers
Multi-cloud Workload Identity Federation Without Long-Lived Keys: one OIDC token source, separate scoped roles per cloud.

What are common mistakes when replacing long-lived cloud keys?

Federation removes static keys but does not remove misconfiguration risk. These failures show up repeatedly in production audits and incident postmortems.

  1. Over-broad trust policies: Using `StringLike` with `repo:myorg/*` when you meant a single repository. Any repo in the org inherits deploy access.
  2. Missing audience checks: Skipping `aud` validation allows token replay from unintended clients. Always set and verify audience on both sides.
  3. Keeping old keys "just in case": Dual-running federation and static keys defeats the purpose. Delete unused IAM users and rotate any remaining keys on a 90-day schedule until gone.
  4. Administrator roles for CI: Terraform plans do not need `iam:*`. Grant least privilege per pipeline stage—plan role read-only, apply role write scoped to one state bucket and one DynamoDB lock table.
  5. Ignoring self-hosted runners: Self-hosted GitHub or GitLab runners are part of your trust boundary. Harden the host, restrict who can execute jobs, and use environment protection rules. See self-hosted CI runners setup and security for the full checklist.
  6. No secret scanning in parallel: Federation prevents new keys from entering CI, but old keys may still sit in git history. Run Gitleaks or similar scanning in CI until history is clean.
Should You Use Federation?Workload needs cloud API access?NoNo cloud creds neededYesCI / K8s supports OIDC?NoUse instance role or IRSA firstYesUse WIF — delete keysNever: static keys in CI variables or .env committed to gitAudit with CloudTrail / Activity Log after migration
Decision guide: prefer Workload Identity Federation Without Long-Lived Keys whenever your platform supports OIDC token issuance.

Kubernetes workloads on AWS and GCP

Container workloads use the same federation concept through pod-level identity. On AWS EKS, configure IAM Roles for Service Accounts (IRSA) by associating an OIDC provider with the cluster and annotating the Kubernetes service account with a role ARN. On GKE, enable Workload Identity to bind a Kubernetes service account to a GCP service account. Neither approach requires embedding keys in pod specs or ConfigMaps—both are federation under a different label.

How do you migrate CI/CD pipelines from static keys to federated identity?

Treat migration as a phased cutover, not a big-bang secret deletion. A pattern I've used on production Laravel deployments:

  1. Inventory existing keys: List IAM users and access keys with CloudTrail or IAM credential reports. Note which pipelines, cron jobs, and developers hold copies.
  2. Create federated roles in parallel: Build new roles with identical or tighter permissions. Test on a staging branch before touching production deploys.
  3. Update one pipeline at a time: Start with the lowest-risk job—asset upload to S3, not database migrations. Confirm CloudTrail shows `AssumeRoleWithWebIdentity` events.
  4. Add branch and environment guards: Production roles should require `environment:production` or an equivalent protected-environment claim. Staging roles get a separate trust condition.
  5. Disable and delete old keys: Deactivate the access key, wait one full deploy cycle, then delete the IAM user if it exists only for CI. Document the change in your runbook.
  6. Enable continuous auditing: Alert on any `CreateAccessKey` API call. Pair federation with OAuth and token security best practices if your pipeline also calls third-party APIs.

Terraform for reproducible trust relationships

Define OIDC providers and roles in Terraform so staging and production stay consistent. Pin provider versions per your existing module standards.

resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab063fa8120a90c3970c9eb877743"]
}

resource "aws_iam_role" "deploy" {
  name = "github-deploy-production"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = "sts:AssumeRoleWithWebIdentity"
      Principal = {
        Federated = aws_iam_openid_connect_provider.github.arn
      }
      Condition = {
        StringEquals = {
          "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
        }
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:myorg/laravel-app:environment:production"
        }
      }
    }]
  })
}

Store Terraform state in an encrypted S3 bucket with a DynamoDB lock table—also accessed via federation, not a personal admin key. That closes the loop: your infrastructure code and your deploy pipeline share the same zero-long-lived-key model.

Production Deploy Without Static KeysGitLab CILaravel 12 + Vite 6OIDC JWTaud: sts.amazonaws.comAWS STSAssumeRoleS3 — static assetsnpm run build outputECR — Docker imagemulti-stage buildSSM — deploy triggerEC2 + Deployer 7Zero long-lived keys in GitLab variables or EC2 .envCloudTrail logs every AssumeRoleWithWebIdentity callPHP 8.3 + Laravel 12 on Ubuntu 24 — credentials expire after deploy
Real-world Laravel pipeline: Workload Identity Federation Without Long-Lived Keys across S3, ECR, and SSM deploy hooks.

When static keys are still acceptable

Federation is not universal. Legacy on-prem software that cannot perform OIDC token exchange, some third-party SaaS integrations, and local developer machines often still use access keys or named profiles. The goal is containment: static keys belong in a password manager or AWS Secrets Manager with rotation, scoped to one human user, never checked into git. Workloads that can federate should federate.

Cost and operational overhead

Federation itself is free on AWS, GCP, and Azure—you pay for the API calls your workload makes after authentication, not for the trust relationship. Setup time for a single-repo GitHub-to-AWS pipeline is typically one to two hours including testing. Multi-environment setups with separate roles add another hour. Compare that to the cost of a leaked production key: incident response, key rotation across every service, potential data exposure, and client notification. For Nepal-based startups operating on tight budgets (Rs 5,000–15,000/month hosting, roughly USD 37–110), federation is cheap insurance.

Ready to remove long-lived keys from your pipelines?

Workload Identity Federation Without Long-Lived Keys is the baseline security posture for CI/CD in 2026—not an optional hardening step for enterprise teams. Configure OIDC trust on your cloud account, scope roles to specific repositories and environments, migrate one pipeline at a time, and delete the static keys once CloudTrail confirms federated assumption. If you want help wiring federation into a Laravel deploy pipeline on AWS or auditing existing IAM users on a production VPS, get in touch—I set this up regularly on client projects and sister-site infrastructure.

Frequently Asked Questions

Short-lived OIDC or SAML tokens from your CI runner, VM, or Kubernetes pod are exchanged for cloud credentials at runtime. No static JSON keys or access keys are stored in repos or servers.

Long-lived keys in GitLab CI variables or .env files are a recurring breach vector. I've seen leaked GCP JSON keys and AWS access keys rotate into production incidents months after a developer left. Keys don't expire automatically, aren't tied to a single pipeline run, and often get copied across staging and production. Workload Identity Federation issues credentials scoped to one job, valid for minutes, with auditable subject claims like repository and branch. For sister sites on shared GitLab CI pipelines, removing stored keys cuts one of the highest-risk attack surfaces without adding operational overhead once OIDC is configured.

Your workload presents a signed identity token from its platform—GitHub Actions, GitLab CI, or a Kubernetes service account—to a cloud provider's federation endpoint. The provider validates the token signature, issuer, audience, and optional attribute conditions such as ref or environment name. If claims match your pool provider mapping, the cloud STS returns temporary credentials scoped to an IAM role or GCP service account impersonation. Those credentials live for one pipeline job or pod session, then expire. No secret is generated at setup time that could leak later.

All three majors do, with different names. Google Cloud Workload Identity Federation maps external OIDC or AWS identities to service accounts via workload identity pools. AWS IAM supports OIDC and SAML identity providers for roles assumed from GitHub, GitLab, or EKS. Azure Workload Identity federates Kubernetes or Entra-managed identities to Azure RBAC. For GitLab CI on Ubuntu runners I've used GCP WIF and AWS OIDC role assumption; pick the provider your workloads actually call, not the one with the prettiest console UI.

In GitLab 15.7+, add id_tokens to your .gitlab-ci.yml with aud set to your cloud provider's federation audience. Grant the GitLab OIDC issuer URL as a trusted identity provider in GCP, AWS, or Azure. Map claims like project_path or ref_type to a least-privilege role binding. Replace scripts that export GOOGLE_APPLICATION_CREDENTIALS from a file with gcloud auth login or aws sts assume-role-with-web-identity using the injected CI_JOB_JWT_V2 or ID token. Test on a feature branch before deleting old CI variables.

Federation itself is free; you pay only for API calls and resources the workload accesses.

Adopt it the moment any cloud key lives in CI/CD variables, a server crontab, or a shared .env on Deployer-managed releases. For a single Laravel app on one EC2 with manual deploys, a properly scoped IAM instance profile is simpler than full OIDC federation. But if GitLab CI pushes to GCS, runs Terraform, or deploys to multiple environments, federation pays off immediately. Teams under five people benefit most from reduced secret rotation toil and cleaner offboarding when contractors leave.

EC2 instance profiles attach an IAM role directly to a running server via the instance metadata service. Credentials are short-lived and automatic, but they bind to that VM's lifetime. Workload Identity Federation targets external or cross-cloud identities—GitLab runners, GitHub Actions, on-prem Kubernetes—without placing the workload inside AWS. On shared EC2 hosting several sites, instance profiles work for app runtime S3 or SES access. Federation is the right tool when CI pipelines or external clusters need cloud API access without embedding keys in the repo.

Audit every CI variable and server env file for GOOGLE_APPLICATION_CREDENTIALS, AWS_ACCESS_KEY_ID, and similar patterns. Create the federation pool and provider in a staging project first, bind a test role with read-only permissions, and run one pipeline branch against it. Compare CloudTrail or GCP audit logs to confirm the OIDC subject appears correctly. Roll out environment by environment, then revoke and delete old keys only after at least one full release cycle confirms no hidden cron or backup script still references them.

Wrong audience claim is the most frequent failure—GitLab aud must exactly match what GCP or AWS expects. Attribute mapping typos leave you with empty google.subject or aws:userid and a 403 on impersonation. Clock skew rarely bites OIDC but expired tokens from long-running jobs need refresh logic. I've debugged pipelines where ref_protected was required but feature branches weren't protected, so federation silently failed only on MR pipelines. Enable verbose auth logging, copy the exact JWT from CI artifacts, and decode it at jwt.io to compare claims against your provider condition strings.

Yes, because rotation doesn't fix leakage between cycles and keys often get duplicated across environments. Federation credentials are minted per job, bound to identity claims, and expire in minutes without a human storing a replacement secret. Rotation still leaves a window where a copied key works everywhere it was pasted. Federation also gives auditable attribution—CloudTrail shows which GitLab project and commit assumed the role. For production Laravel deployments I've moved to federation specifically because quarterly rotation on three environments still left stale keys in forgotten staging configs.

GKE Workload Identity binds Kubernetes service accounts to GCP service accounts via IAM, eliminating key files mounted into pods. EKS Pod Identity or IRSA federates pods to AWS IAM roles through OIDC tied to the cluster. Configure the KSA annotation, create the trust binding, and verify with a test pod calling the metadata server or GCP metadata endpoint. This pattern suits microservices pulling secrets from Secret Manager or writing to Cloud Storage. For monolithic Laravel on a single VM, K8s federation is overkill unless you're already containerised.

AWS creates an IAM OIDC provider pointing at tokens.actions.githubusercontent.com or gitlab.com, then attaches a trust policy on a role limiting sub or aud claims. GCP creates a workload identity pool, adds an OIDC provider, maps claims to google.subject, and grants roles/iam.workloadIdentityUser on a service account. AWS returns temporary access keys via STS AssumeRoleWithWebIdentity; GCP returns a federated access token you exchange for service account impersonation. Syntax differs but the security model is identical: trust the issuer, constrain claims, issue short-lived creds.

Grant the minimum needed for that pipeline stage, not AdministratorAccess because setup was easier. A Laravel deploy job typically needs S3 PutObject on one bucket prefix, ECR pull/push on one repository, or Secret Manager accessor on named secrets. Split plan and apply Terraform stages into separate roles. Use attribute conditions so only protected main branch refs can touch production ARNs. On client projects I've seen over-privileged federation roles become the new long-lived key equivalent—one compromised pipeline branch can still exfiltrate data if the role can read all S3.

If your runner platform lacks native OIDC—older Jenkins without the OIDC plugin, custom shell scripts on a cron server—use cloud-native short-lived alternatives first. AWS Systems Manager Session Manager, GCP service account impersonation from a hardened bastion with MFA, or HashiCorp Vault with dynamic secrets beat static keys. For GitLab versions before 15.7, upgrade before building workarounds. As a last resort, store keys in a dedicated secrets manager with automatic rotation and strict audit, but treat that as technical debt with a dated migration plan to federation.

Share this article

Quick Contact Options
Choose how you want to connect me: