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

## Set it up

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

```bash
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:

```yaml
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.

## Private repositories

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:

```bash
zero ci trust add --repo octo-org/private-repo --owner-id 65 --repo-id 74
```

A refused run prints both ids in the error.

## Limits on a trust record

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

```bash
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.

## Refused events

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 |

```bash
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.

## Manage trust records

```bash
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.

## Where an API key is still necessary

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