ID tokens, OIDC and pipeline security

GitLab CI/CD · Expert · 14 min

Whoever controls your pipeline controls your deploy credentials; keyless auth and protection rules limit the blast radius.

id_tokens: makes GitLab issue a signed JWT to the job, with an audience you choose. The token's claims describe the job: project_path, ref, ref_protected, environment, pipeline_source and a sub such as project_path:acme/shop:ref_type:branch:ref:main. A cloud provider (AWS, GCP, Azure) or HashiCorp Vault configured to trust your GitLab instance as an OIDC issuer exchanges the token for short-lived credentials. No long-lived keys are stored.

The trust policy is where security lives: require the exact project_path, and for production also ref_protected: true or a specific environment. Matching only the issuer would let every project on GitLab.com assume your role.

Broader pipeline hardening:

  • Protect the default and release branches and tags; protected variables, runners and environments all build on that.
  • Merge requests from forks run pipelines in the fork by default, without your secrets. Running them in the parent project is an explicit, reviewed action.
  • CI_JOB_TOKEN access is limited by the project's allowlist; keep it tight.
  • Pin included templates and components to versions; an attacker who can change a shared template changes every pipeline that includes it.
  • Treat .gitlab-ci.yml changes as security-sensitive and use code owners for them.

Example

deploy:
  image:
    name: amazon/aws-cli:2.27.0
    entrypoint: [""]           # the image's entrypoint is aws
  id_tokens:
    AWS_TOKEN:
      aud: sts.amazonaws.com
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  environment: production
  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 "$ROLE_ARN" --role-session-name "gl-$CI_JOB_ID"
      --web-identity-token "$AWS_TOKEN" --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text))
    - aws sts get-caller-identity
# AWS trust policy condition (excerpt):
# "gitlab.example.com:sub": "project_path:acme/shop:ref_type:branch:ref:main" 

Remember

  • id_tokens issue a per-job JWT for keyless cloud or Vault login
  • Restrict trust to project_path and protected refs
  • Protected branches underpin protected variables, runners and environments

Watch out for

  • A trust policy that only checks the issuer and audience, trusting every project on the instance.

Check yourself

  1. Which claim condition best ensures only main-branch jobs of acme/shop get production credentials?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.gitlab.com/ci/secrets/id_token_authentication/