Skip to content

CI: trust rules

A trust rule lets a repository's GitHub Actions workflows get a bot's credential, with no secret stored in GitHub. Here's how to create one in CI and how to scope it well.

How it works

The workflow asks GitHub for an OIDC token with audience KUBELATCH_BASE_URL and exchanges it at POST /v1/ci/github-actions/token. kubelatch verifies the signature against GitHub's public keys, checks that the token hasn't been exchanged before, and looks up the repository's trust rule. If it matches, it responds once with the bot's credential and its kubeconfig. Details of the verification (key caching, clock skew, sub) are in Security model.

sequenceDiagram
  participant W as Workflow
  participant G as GitHub
  participant K as kubelatch
  W->>G: request OIDC token (audience = KUBELATCH_BASE_URL)
  G-->>W: signed id_token
  W->>K: POST /v1/ci/github-actions/token
  K->>K: verify signature, aud, jti and trust rule
  K-->>W: bot credential + kubeconfig

The credential lasts KUBELATCH_CI_TOKEN_TTL (1 hour by default; can't exceed KUBELATCH_MAX_TTL_BOT). It shows up in Credenciales (Credentials) with the name github-actions and a note with the repository, ref, run and actor. Every kubectl from the workflow is logged in the audit log as bot:<name>.

Before you start

  • A bot with permissions: create it in Usuarios (Users) and grant it a tier in Permisos (Permissions) (Users and bots).
  • The runners reach kubelatch over https with a certificate they trust. A kubelatch on a laptop or a private network won't do. With a private CA, use KUBELATCH_KUBECONFIG_CA (Install).

Create a trust rule

  1. Look up the numeric ids of the repository and its owner:

    gh api repos/<owner>/<repo> --jq '[.owner.id, .id]'
    # or without gh:
    curl -s https://api.github.com/repos/<owner>/<repo> | jq '.owner.id, .id'
    
  2. In CI, fill in the form:

    Field What to put
    Nombre (Name) A name to recognize it by, for example deploy de la app (app deploy).
    Bot The bot whose credential the workflow will receive. Only enabled ones show up.
    ID del propietario (Owner ID) The first number (repository_owner_id).
    ID del repositorio (Repository ID) The second number (repository_id).
    Ref (opcional) (Ref (optional)) A full ref: refs/heads/main, refs/tags/v1.0.
    Environment (opcional) (Environment (optional)) The GitHub environment, for example prod.
  3. Click Crear regla (Create rule).

kubelatch compares by numeric ids, not by names: repositories and organizations get renamed and their names get recycled. There can only be one rule per repository, ref and environment.

How the rule is chosen

Without Ref or Environment, the rule applies to any workflow in the repository. With them, only to that exact ref and that environment. If several match, the most specific one wins: one with an environment beats one with a ref, and both beat the generic one.

Pin the ref for anything that isn't read-only

A rule with no ref also applies to workflows from a pull request from a branch in the same repository (refs/pull/N/merge). Anyone with push permission could get the bot's credential by editing a workflow in a PR. PRs from a fork don't get an id_token on pull_request.

Two more limits:

  • Don't combine a rule with pull_request_target workflows that check out the PR's code. They run with the base branch's ref, not the ref of the code they execute.
  • A rule with an environment only protects if that environment exists in GitHub with protection rules (reviewers, branch policy). If it doesn't exist, GitHub creates it the first time, unprotected.

The workflow side

CI shows a Fragmento del workflow (Workflow snippet) with your URL, ready to copy. The full example is in examples/github-actions/kubelatch.yml. The essentials:

  • permissions: id-token: write.
  • KUBELATCH_URL matching KUBELATCH_BASE_URL letter for letter: it's the token's audience.
  • If the rule sets an environment, the job declares environment: <name>.

The guide for whoever writes the workflow is in Credentials for GitHub Actions.

Check that it works

  1. Run the workflow (workflow_dispatch or a push to the rule's ref).
  2. The job prints bot:<name> in kubectl auth whoami and the pod list.
  3. A github-actions credential with the run's note shows up in Credenciales.
  4. In Auditoría (Audit), the requests have the bot as subject. In Plano de control (Control plane) there's a credential.issue with via: github-actions.

Each run gets a new token: relaunching the job works. If it fails, see Troubleshooting.

Delete a rule

Click Borrar (Delete) in the Reglas (Rules) table. That repository's workflows stop getting credentials, but the ones already issued keep working until they expire (under KUBELATCH_CI_TOKEN_TTL). Revoke them in Credenciales if needed. Disabling the bot does revoke all its credentials instantly.

GitHub Enterprise Server

Point KUBELATCH_GITHUB_ACTIONS_ISSUER at https://<server>/_services/token. The rest doesn't change. See Configuration.