Trust policies
A trust policy tells the server which CI identity tokens it accepts, and which repositories those jobs may publish to. A CI job sends its OIDC identity token. The server finds a matching policy and opens a publish session. The job needs no stored password.
Only admins manage policies. Open the Trust policies page (#/trusts).
Fields of a policy
Section titled “Fields of a policy”| Field | Meaning |
|---|---|
| Name | A unique name for the policy |
| Issuer | The iss value of the token. It must match exactly |
| Audience | The aud value the token must carry. Empty means the base_url of the server |
| Claims | One or more claims, each with a list of allowed values |
| Repositories | The repos that a matching job may publish to |
| JWKS | Optional. Pasted signing keys, used instead of key discovery |
| Disabled | Keeps the policy but stops it from matching |
Claims and globs
Section titled “Claims and globs”A policy needs at least one claim. Each claim needs at least one allowed value. The UI refuses to save a policy without a claim, because it would match every token of the issuer.
A value is either exact, or a glob with *. The * matches any text, and it also matches /. A token must match every listed claim. For one claim, one matching value is enough.
Example, for GitHub Actions:
{ "repository": ["acme/app", "acme/lib-*"], "ref": ["refs/heads/main", "refs/tags/v*"]}This accepts a job from acme/app or acme/lib-* that runs on main or on a tag that starts with v.
Audience
Section titled “Audience”The token audience must match the policy audience. If the field is empty, the server uses the current base_url at exchange time. The Gradle plugin asks GitHub for a token with the server URL as audience, so an empty audience works for it.
By default, the server fetches the issuer’s signing keys and caches them. If the server cannot reach the issuer (for example, a Jenkins behind a firewall), paste the JWKS into the policy. The server checks a pasted JWKS when you save. It replaces key discovery.
Accepted algorithms: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512 and EdDSA.
Create a policy
Section titled “Create a policy”-
Open Trust policies and choose a preset (GitHub Actions or Jenkins), or fill in the form.
-
Enter a name and the claim values. The presets leave the values empty, so you must set them.
-
Select the repositories that the policy may publish to.
-
Save.
Presets
Section titled “Presets”The preset fills in:
- Issuer:
https://token.actions.githubusercontent.com - Claims:
repository,refandworkflow_ref
Example claims for a release workflow:
{ "repository": ["acme/app"], "ref": ["refs/tags/v*"], "workflow_ref": ["acme/app/.github/workflows/release.yml@*"]}The job needs the permission id-token: write in the workflow:
permissions: id-token: write contents: readThe Gradle plugin and the Maven extension read the token for you. See GitHub Actions for the full setup.
The preset fills in:
- Issuer:
https://JENKINS_URL/oidc(replaceJENKINS_URL) - Claims:
sub
The Jenkins OpenID Connect Provider plugin issues the token. Paste the JWKS if the server cannot reach Jenkins. See Jenkins for the full setup.
Any other OIDC issuer works. Fill in the fields without a preset.
What the server checks
Section titled “What the server checks”On a token exchange (POST /api/publish/sessions/oidc), the server checks:
- The signature, against the issuer’s keys.
- The issuer, the audience and
exp. iat: the token must be at most 10 minutes old, and at most 1 minute in the future.- An enabled policy exists that matches the issuer, the audience and every claim, and lists the requested repo.
- The token was not used before.
Replay protection
Section titled “Replay protection”The server stores a SHA-256 hash of <issuer>\n<jti> for each accepted token, until the token expires. If the token has no jti, the server hashes the whole token. A second exchange with the same token fails. The server writes the replay record and the session in one transaction.
A CI job therefore needs a fresh token for each session. The clients do this for you. If you use curl, request a new token each time.
Sessions from a policy
Section titled “Sessions from a policy”- A session lives for
publish.session_ttl. The default is 15 minutes. The maximum is 1 hour. - A CI session writes without a user permission check. The policy is the permission.
- The
subjectof the session is thesubof the token. It shows in the Publish log. - When you change the issuer, audience, claims, JWKS or repositories, disable the policy, or delete it, the server ends the open sessions of that policy.
Gotchas
Section titled “Gotchas”- The policy matches on token claims, not on the content of the workflow file. Anyone who can change a workflow in an allowed repository and ref can publish. Protect your branches and tags.
- GitHub
subvalues contain/and:. A*crosses them, so a value such asrepo:acme/*also matchesrepo:acme/other/x. - A policy with an empty audience follows
base_url. If you changebase_url, tokens must carry the new value. - If an exchange fails, compare the claims of the token with the policy. The Publish log lists the sessions that succeeded.