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
jticannot 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.