DEV Community

Pavan Kumar Sadashiv
Pavan Kumar Sadashiv

Posted on

Zero-Secret Pipelines: Hardening GitLab CI/CD with OIDC & HashiCorp Vault

Zero-Secret Pipelines: Hardening GitLab CI/CD with OIDC & HashiCorp Vault

Every security team's worst nightmare begins with a developer committing a secret to Git, or an attacker compromising a project's CI/CD variable settings.

For years, connecting GitLab CI/CD to external infrastructure (AWS, Google Cloud, Azure, or internal databases) meant creating static, long-lived credentials:

  • An AWS IAM Access Key and Secret Key pasted into GitLab CI variables.
  • A database password stored in plaintext inside runner environments.
  • Tokens that rarely get rotated and live indefinitely.

If a rogue merge request or malicious dependency executes env inside a build script, your entire cloud infrastructure is compromised.

The modern industry gold standard for CI/CD security is Zero Stored Secrets using OpenID Connect (OIDC).

In this practical guide, I will show you how to configure GitLab CI/CD’s native id_tokens to dynamically authenticate against HashiCorp Vault or cloud providers, minting short-lived (15-minute) credentials on demand with zero static secrets stored in GitLab.


1. The Architecture: Static Secrets vs. OIDC Federated Trust

+-------------------------------------------------------------+
|                     TRADITIONAL STATIC MODEL                |
|                                                             |
|  GitLab CI Runner reads:                                    |
|  $AWS_ACCESS_KEY_ID = "AKIA..." (Static, never rotated)     |
|  $AWS_SECRET_ACCESS_KEY = "wJal..." (Massive leak risk!)    |
+-------------------------------------------------------------+

                              vs.

+-------------------------------------------------------------+
|                  ZERO-SECRET OIDC FEDERATION                |
|                                                             |
|  1. GitLab Runner generates cryptographically signed JWT    |
|     token (`id_tokens: GITLAB_OIDC_TOKEN`)                  |
|  2. Token contains claims: project_id, branch, user_login  |
|  3. HashiCorp Vault / Cloud IAM verifies GitLab's OIDC pubkey|
|  4. Vault issues temporary 15-minute token scoped to branch |
|  5. Zero credentials stored in GitLab repository settings!  |
+-------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

2. Step 1: Configuring HashiCorp Vault for GitLab OIDC

Inside your HashiCorp Vault cluster, enable the JWT/OIDC authentication engine and establish trust with GitLab’s public identity provider:

# 1. Enable the JWT auth method
vault auth enable jwt

# 2. Configure Vault to trust GitLab's OpenID Connect issuer
vault write auth/jwt/config \
    oidc_discovery_url="https://gitlab.com" \
    bound_issuer="https://gitlab.com"
Enter fullscreen mode Exit fullscreen mode

Next, define a Vault role that restricts access based on strict GitLab JWT claims (e.g., only commits on the protected main branch of project my-org/core-infra):

vault write auth/jwt/role/gitlab-production-deployer \
    role_type="jwt" \
    bound_audiences="https://vault.mycompany.com" \
    user_claim="user_email" \
    bound_claims_type="glob" \
    bound_claims='{
      "project_path": "my-org/core-infra",
      "ref": "main",
      "ref_type": "branch"
    }' \
    policies="production-database-read" \
    ttl="15m"
Enter fullscreen mode Exit fullscreen mode

If an attacker tries to run a pipeline from an unapproved fork or feature branch, Vault automatically rejects the token!


3. Step 2: Minting OIDC Tokens in .gitlab-ci.yml

In your GitLab CI configuration, declare id_tokens with an audience matching your Vault configuration. GitLab automatically signs this JSON Web Token using its private key:

stages:
  - deploy

deploy_production:
  stage: deploy
  image: hashicorp/vault:latest
  environment: production
  id_tokens:
    # GitLab generates a cryptographically signed OIDC JWT
    VAULT_ID_TOKEN:
      aud: https://vault.mycompany.com
  variables:
    VAULT_ADDR: "https://vault.mycompany.com:8200"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  script:
    - |
      echo "Authenticating against HashiCorp Vault with ephemeral OIDC JWT..."

      # 1. Exchange the GitLab OIDC token for a 15-minute Vault Client Token
      VAULT_AUTH_RESPONSE=$(vault write -format=json auth/jwt/login \
        role="gitlab-production-deployer" \
        jwt="$VAULT_ID_TOKEN")

      export VAULT_TOKEN=$(echo $VAULT_AUTH_RESPONSE | jq -r '.auth.client_token')

      # 2. Fetch the dynamic database secret from Vault
      DB_SECRET=$(vault kv get -format=json secret/production/database)
      DB_PASSWORD=$(echo $DB_SECRET | jq -r '.data.data.password')

      echo "Executing deployment with ephemeral secret..."
      ./scripts/deploy.sh --db-pass "$DB_PASSWORD"
Enter fullscreen mode Exit fullscreen mode

4. Key OIDC Claims Available for Role Hardening

GitLab embeds rich contextual metadata directly into every OIDC token:

JWT Claim Example Value Security Use Case
iss https://gitlab.com Verifies the token originated from GitLab.
project_path my-org/core-infra Restricts secrets to exact repository paths.
ref main Ensures production secrets never leak to feature branches.
environment production Binds access to protected GitLab environments.
user_login hrlpavan Enforces identity auditing for human triggers.
ci_config_sha a1b2c3d4... Pins access to specific audited CI configurations.

5. Why OIDC Replaces Static API Keys Forever

  1. Zero Secret Leakage: Even if a developer accidentally runs printenv or prints build artifacts, there are no permanent AWS or database passwords stored in GitLab.
  2. Deterministic Expiration: Ephemeral tokens expire automatically after 15 minutes, neutralizing replay attacks.
  3. No Rotation Ceremonies: Security teams never have to schedule quarterly downtime to rotate static cloud passwords.
  4. Complete IAM Auditability: Cloud and Vault access logs record the exact GitLab project, user, and commit SHA responsible for every request.

Resources & Links

Has your team transitioned to OIDC in your CI/CD pipelines? Share your security architecture below!

Top comments (0)