Skip to content

GitHub Actions with OIDC

GitHub Actions can give a job an OIDC token. The token is a JWT that GitHub signs. It names the repository, the git ref, the event, and the environment of the job.

ZeroVault accepts that token in place of an API key. You record which repository you trust. The job sends its OIDC token to ZeroVault and gets back a token for the ZeroVault API. That token is valid for 15 minutes.

An API key does not expire, reaches every project in the organization, and is readable by every workflow in the repository.

Add the trust record from a terminal where zero is signed in:

Terminal window
zero ci trust add --repo octo-org/octo-repo

Then give the job the id-token: write permission and delete the ZERO_API_KEY secret:

permissions:
id-token: write # GitHub gives no OIDC token without this
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx @zeroapps/cli@0.7.0 vault run -p demo -e production -- ./build.sh

The CLI reads the two ACTIONS_ID_TOKEN_REQUEST_* variables, requests an OIDC token with the audience https://api.zeroapps.dev, and exchanges it. In the job, zero whoami prints Credential: github actions.

The trust record is stored in a key-value store that is eventually consistent. A run in the first minute after zero ci trust add can fail.

ZeroVault matches the numeric owner id and repository id from the token. It does not match the repository name, because a name can move to a different repository.

zero ci trust add reads those ids from the GitHub API without a token, so it cannot read a private repository. Set GITHUB_TOKEN, or pass the ids:

Terminal window
zero ci trust add --repo octo-org/private-repo --owner-id 65 --repo-id 74

A refused run prints both ids in the error.

A trusted workflow reads every secret in the organization. To make the trust smaller, add a condition:

Terminal window
zero ci trust add --repo octo-org/octo-repo --ref refs/heads/main
zero ci trust add --repo octo-org/octo-repo --environment production

--ref must equal the ref claim of the token. --environment must equal the environment claim.

ZeroVault refuses three events unless the trust record allows them:

Event Claim in the token
pull_request The job runs code from a branch that nobody reviewed
pull_request_target The job runs with the base repository’s identity while a fork’s pull request is open
workflow_run The job runs with the repository’s identity after another workflow
Terminal window
zero ci trust add --repo octo-org/octo-repo --allow-event pull_request

One --allow-event flag allows one event. The other two stay refused.

A fork’s pull_request run gets no OIDC token, because GitHub makes the permissions of such a run read-only. A fork’s pull_request_target run does get a token. That token contains the base repository’s ids and ref: refs/heads/main, so a --ref refs/heads/main condition matches it. Only the event rule refuses it.

Terminal window
zero ci trust list # id, repository, and conditions
zero ci trust rm <id> # delete the record

After you delete a record, the next exchange fails. A token from an earlier exchange stays valid until it expires, for at most 15 minutes.

  • GitLab, CircleCI, and Buildkite sign OIDC tokens too, but ZeroVault accepts only GitHub’s.
  • A server or a container has no OIDC issuer. Use an API key.
  • A trust record cannot name one project. It gives access to the whole organization, the same as an API key.