Vaultic
Integrations

Workload identity

Let CI jobs and pods authenticate with the OIDC identity their runtime already gives them — no static Vaultic token anywhere.

Every secrets manager has to answer the same awkward question: to fetch your secrets, you first need a secret. The usual answer is a long-lived VAULTIC_TOKEN pasted into GitHub repo secrets or a Kubernetes Secret — a credential that is rarely rotated, easily copied between environments, and outside Vaultic's control the moment it leaves.

Workload identity federation removes it. Every runtime you already run CI in hands the job a cryptographically verifiable identity: GitHub Actions issues a signed OIDC token to every job, Kubernetes projects a ServiceAccount JWT into every pod, GitLab CI issues one per job. Register the issuer with Vaultic, describe which claims may assume which scope, and the job exchanges its own token for a short-lived Vaultic credential.

Configuring providers and trust rules requires the Team plan. Static service tokens keep working on every plan — this is additive, and stays the right choice for local scripts, unsupported runtimes, and break-glass access.

How it works

Register a provider

A provider is one OIDC issuer this workspace trusts. Presets fill in everything that doesn't vary: for GitHub Actions the only real question is which repository.

Add trust rules

A trust rule maps claims on the incoming token — repository, ref, sub, whatever the issuer sends — to exactly the scope a service token would carry: a project, an environment, read or write, and a token lifetime.

The job exchanges its token

POST /auth/workload verifies the token's signature against the issuer's JWKS, finds the first matching rule, and returns a Vaultic access token scoped to that rule and expiring in minutes. The CLI does this transparently.

Nothing downstream changes. The exchanged token is an ordinary scoped Vaultic credential, subject to the same project/environment checks, the same trusted-IP allowlists, and the same audit log as everything else — with the workload's own claims recorded alongside each entry.

GitHub Actions

Create the provider (the issuer URL is filled in for you):

vaultic workload-identity create github --kind github-actions

That prints a globally-unique provider slug — acme-github or similar. That slug is what jobs authenticate against, and it is not a secret.

Add a rule. This one lets any workflow run on main in one repository read production:

vaultic workload-identity rules add acme-github prod-read \
  --claim "repository=acme/backend-api" \
  --claim "ref=refs/heads/main" \
  --env production --read-only --ttl 15m

Then in the workflow — note there is no secrets.VAULTIC_TOKEN anywhere:

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write        # required — without it GitHub won't mint an OIDC token
      contents: read
    env:
      VAULTIC_WORKLOAD_PROVIDER: acme-github
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @vaultic/cli
      - run: vaultic run --env production -- node deploy.js

$VAULTIC_WORKLOAD_PROVIDER is enough on its own: any command needing credentials detects the ambient OIDC token, exchanges it, and caches the result in memory for the life of the process. Add an explicit vaultic login --workload step if you'd rather a misconfiguration fail at the top of the job than at whichever command first needed a secret.

Available claims

GitHub sends a rich token. The ones worth writing rules against:

ClaimExampleNotes
repositoryacme/backend-apiThe one almost every rule should pin.
repository_owneracmeToo broad on its own — anyone in the org matches.
refrefs/heads/mainBranch or tag the run is on.
environmentproductionOnly present when the job declares a GitHub environment.
workflowdeployWorkflow name, not path — renaming it breaks the rule.
actoroctocatWho triggered the run.

Kubernetes

Point the provider at your cluster's OIDC issuer:

vaultic workload-identity create prod-cluster \
  --kind kubernetes \
  --issuer https://oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLE

Rules match the ServiceAccount through sub:

vaultic workload-identity rules add prod-cluster backend-api \
  --claim "sub=system:serviceaccount:production:backend-api" \
  --env production --read-only

The pod needs a projected ServiceAccount token with vaultic as its audience — the default token mounted into every pod is audienced for the API server and will be rejected:

spec:
  serviceAccountName: backend-api
  containers:
    - name: app
      env:
        - name: VAULTIC_WORKLOAD_PROVIDER
          value: acme-prod-cluster
        - name: VAULTIC_WORKLOAD_TOKEN_FILE
          value: /var/run/secrets/vaultic/token
      volumeMounts:
        - name: vaultic-token
          mountPath: /var/run/secrets/vaultic
          readOnly: true
  volumes:
    - name: vaultic-token
      projected:
        sources:
          - serviceAccountToken:
              path: token
              audience: vaultic
              expirationSeconds: 3600

Clusters the Vaultic server can't reach

Self-hosted control planes usually aren't publicly reachable, so server-side OIDC discovery fails. Paste the JWKS document instead:

kubectl get --raw /openid/v1/jwks > jwks.json
vaultic workload-identity create prod-cluster \
  --kind kubernetes \
  --issuer https://kubernetes.default.svc \
  --jwks-file jwks.json

Pasted keys are pinned: they are never auto-refreshed, so re-paste them if the cluster's signing keys are ever rotated.

GitLab CI

vaultic workload-identity create gitlab --kind gitlab-ci
vaultic workload-identity rules add acme-gitlab prod \
  --claim "project_path=acme/backend-api" \
  --claim "ref=main" \
  --env production --read-only

Declare the token in the job with the matching audience — the CLI reads $VAULTIC_ID_TOKEN:

deploy:
  id_tokens:
    VAULTIC_ID_TOKEN:
      aud: vaultic
  variables:
    VAULTIC_WORKLOAD_PROVIDER: acme-gitlab
  script:
    - vaultic run --env production -- ./deploy.sh

Writing trust rules

Every predicate in a rule must match. Three operators, deliberately no regex — regex in a trust policy is how repository == acme/app.* ends up trusting acme/app-fork-by-attacker.

CLI syntaxOperatorMatches
--claim "repository=acme/api"exactthat value and nothing else
--claim "ref~=refs/heads/*"glob* matches one path segment
--claim "ref@=refs/heads/main,refs/heads/release"one ofany listed value

A glob never crosses a /. refs/heads/* matches refs/heads/main but not refs/heads/feature/login — and not refs/heads/main/../evil either, which is the point. Write a rule per branch shape rather than reaching for a wildcard that swallows extra path segments.

Rules are evaluated in creation order and the first fully-matching rule wins. A broad rule created first shadows narrower ones created after it, so add the specific rules first.

A rule grants exactly one project/environment scope, like a service token does. A deploy job that needs staging and production needs two rules and two exchanges.

Debugging a rule that doesn't match

This is the normal failure mode, and the exchange endpoint deliberately won't help an unauthenticated caller debug it: a 403 lists which claim names the token carried, never their values. Run the dry run inside the job instead — it reports what would happen and mints nothing:

vaultic workload-identity test --provider acme-github
Would match rule "prod-read" in workspace "acme"
  access: read, backend-api/production
  matched on:
    repository = acme/backend-api
    ref = refs/heads/main
    sub = repo:acme/backend-api:ref:refs/heads/main
    workflow = deploy

Nothing was minted — this was a dry run.

It exits non-zero when nothing matches, so it works as a CI assertion. The web app's Workload Identity page shows the same information from the other side: every recent exchange, which rule matched, and the claims it matched on.

Token lifetime

Exchanged tokens default to 15 minutes and are capped at 1 hour. Long-running commands re-exchange transparently — a vaultic run --watch process outliving its token doesn't die, it quietly gets another one, because the ambient OIDC token is still there.

Exchanged tokens are never listed on the Tokens page and can't be revoked individually. To stop a workload authenticating, delete its trust rule or disable the provider; anything already minted expires on its own within minutes.

Auditing

Each exchange writes a workload.exchanged audit entry carrying the matched rule and the workload's subject claims, and fires a workload.authenticated webhook event. So the audit log answers which workflow run read production, rather than token sht_ab12… read production.

Environment variables

VariablePurpose
VAULTIC_WORKLOAD_PROVIDERProvider slug to exchange against. Enough on its own — no login step needed.
VAULTIC_WORKLOAD_WORKSPACEOnly when addressing a provider by name instead of slug.
VAULTIC_WORKLOAD_AUDIENCEAudience to request; defaults to vaultic.
VAULTIC_WORKLOAD_TOKENSupply the OIDC token directly, bypassing detection.
VAULTIC_WORKLOAD_TOKEN_FILERead the OIDC token from a file (projected volumes).

Next steps

On this page