Skip to content
VaultTerm
Browse docs

api-integrations

CI pipelines without stored secrets

Federate your CI provider's OIDC identity with VaultTerm so a pipeline exchanges its own job token for a short-lived agent, and holds no VaultTerm credential at all.

Updated Aug 1, 2026

CI is usually the worst-governed credential store an organisation has: long-lived secrets pasted into CI variables, scoped far wider than any single job needs, rarely rotated, and invisible in an audit trail. VaultTerm lets a pipeline avoid holding one entirely.

GitLab CI and GitHub Actions both mint a short-lived, signed OIDC token for every job, carrying the project, ref and pipeline identity. You register a trust describing which of those tokens you accept and what they may do. The job exchanges its token for a short-lived agent principal and holds nothing afterwards.

Register a trust

An org admin registers one trust per pipeline identity you want to accept:

POST /agents/oidc/trusts
{
  "name": "deploy pipeline",
  "issuer": "https://gitlab.example.com",
  "audience": "vaultterm",
  "subject_pattern": "project_path:group/repo:*",
  "scopes": ["vault.list", "vault.use", "ssh.exec"],
  "ttl_minutes": 30,
  "egress_class": "lan"
}

The trust — not the token — decides everything that matters. Issuer, audience, scopes, lifetime and the accountable owner all come from what you registered; a token cannot ask for more than the trust allows, and cannot nominate its own issuer.

The subject pattern is the important field. It is what binds a grant to a specific project, and optionally a specific ref. project_path:group/repo:* accepts any ref of that project; project_path:group/repo:ref_type:branch:ref:main accepts only main. A pattern that matches everything is refused at registration — it would let any project at that issuer mint a principal.

Use it in a pipeline

GitLab, requesting a token and exchanging it:

deploy:
  id_tokens:
    VAULTTERM_ID_TOKEN:
      aud: vaultterm
  script:
    - export VT_AGENT_TOKEN=$(vt ci-login --trust $VT_OIDC_TRUST_ID)
    - vt exec "$CONN_ID" "systemctl restart api"

GitHub Actions, where the job requests the token itself:

permissions:
  id-token: write
steps:
  - run: |
      export VT_AGENT_TOKEN=$(vt ci-login --trust ${{ vars.VT_OIDC_TRUST_ID }})
      vt exec "$CONN_ID" "systemctl restart api"

The trust id is not a secret — it identifies which trust you are exchanging against, and naming it is what stops the exchange being ambiguous when several organisations federate the same CI host. Put it in a plain CI variable.

What the exchange enforces

  • Signature, issuer and audience are verified against the issuer’s published keys. RS256 only — unsigned and HMAC-signed tokens are refused outright.
  • The subject must match the trust’s pattern. A token from the right issuer for the wrong repository is refused.
  • One token, one principal. A job token can be lifted out of a compromised job, so replaying it yields nothing. A token without a jti cannot be replay-protected and is refused.
  • Freshness. A token minted hours ago is refused even if its expiry is generous.
  • Every failure answers identically, so a prober cannot map which projects you trust.

What you get back

A normal agent principal, with the trust’s scopes and lifetime, named for the pipeline that asked — so your audit trail attributes a run to a project, ref and pipeline rather than to a shared service account. Everything else in the platform then applies unchanged: redacted output, the flight recorder, plan limits and the kill switch.

Revoking a trust stops all future exchanges immediately. Credentials already issued run out their own short lifetime, or can be revoked individually or in bulk from the console.