Skip to content
Browse the docs

CI/CD

Bind a service token to CI with OIDC

Make a service token work only together with a JWT from your CI provider for one repository, branch and deployment environment, so a leaked token is useless.

Last updated

On this page

A service token on its own works wherever it's presented. A CI binding changes that: the server accepts the token only together with a JWT that the CI provider issued to a job of the right repository, branch or deployment environment. A token copied out of a build log is then useless outside that pipeline.

Supported providers: GitHub Actions, GitLab CI, Bitbucket Pipelines and any other OIDC issuer that publishes its keys through OIDC discovery.

How it works

  1. You add one or more rules to a service token. A rule names a provider, an issuer, an audience and the claims the JWT must carry.
  2. In the pipeline, the CI provider issues a short-lived JWT to the job. crunu finds it itself, as described in Environment variables and exit codes.
  3. When crunu signs in with the token, it sends the JWT too. The server checks the signature against the provider's keys and matches the claims against the rules. At least one rule must match.
  4. Each JWT can be used once. A replayed JWT is refused, and the replay raises an alert. For Bitbucket, whose tokens have no jti claim, the server recognizes a replay by a fingerprint of the token's signed part.

Adding a rule ends the token's current sessions, so the next sign-in already needs a JWT. Deleting the last rule lets the token sign in without one again. A token can have up to 20 rules.

Adding a binding

When you create a token, tick Add a CI binding (OIDC) right after creating. For an existing token, open it in Service tokens and add the binding there. Only owners and admins manage service tokens and their rules.

Use the server address as the audience: https://app.crunu.com in the cloud, or your own server's address. That way the JWT is made for Crunu and nothing else.

GitHub Actions

FieldRequiredExample
repositoryyeskiteline/web-app
audienceyeshttps://app.crunu.com
refnomain, which becomes refs/heads/main
environmentnoproduction
workflow ref, repository ID, owner IDnofurther limits on the workflow and repository

The issuer defaults to https://token.actions.githubusercontent.com. The job needs permissions: id-token: write. See GitHub Actions.

GitLab CI

FieldRequiredExample
project pathyeskiteline/web-app
audienceyeshttps://app.crunu.com
refnomain
ref typenobranch or tag. With a ref it defaults to branch
protected refnoonly protected branches or tags
environmentnoproduction
namespace path, project IDnofurther limits

The issuer defaults to https://gitlab.com. The job declares id_tokens with CRUNU_ID_TOKEN and the audience. See GitLab CI.

Bitbucket Pipelines

FieldRequiredExample
workspaceyesthe workspace name
workspace UUIDyesthe workspace's UUID
repository UUIDyesthe repository's UUID
branchnomain, or a list of patterns
deployment environment UUIDnothe production deployment environment
audiencenoyour server address, set in the step under oidc: audiences:

The issuer is https://api.bitbucket.org/2.0/workspaces/<workspace>/pipelines-config/identity/oidc. Without your own audience, the default workspace audience is used, which every service in the workspace receives. UUIDs are compared without braces and without regard to case. See Bitbucket Pipelines.

Any OIDC issuer

A generic rule takes an https issuer address, an audience and 1 to 32 claim constraints. A constraint is an exact value, a pattern with *, or a list. At least one constraint must pin the repository, project or owner without a * pattern. The standard claims iss, aud, exp, nbf, iat and jti can't be constrained, because the server checks them itself.

Production tokens

The recommended setup keeps production secrets out of CI entirely: the production server runs the agent and fetches them itself. When CI does need production, for example to push variables into a hosting platform, give that job its own token:

  • grant only the production environment it needs;
  • bind it to the main branch and the production deployment environment;
  • remember that an owner approves production access for such a token, and every grant is recorded in the audit log.

For an extra layer, an environment's network rule can admit service tokens only if they have a CI binding. Machines are never admitted under such a rule. This is useful on cloud runners, whose addresses come from the provider's shared ranges and say little about who is calling. See Network rules.

What a binding doesn't cover

The binding checks who is calling. It doesn't limit what a legitimate job does with the variables it receives. A malicious dependency inside the bound job still sees them. Keep each token's grant as small as the job allows, and revoke and replace a token whenever it may have leaked.