Skip to main content
Version: v5.6.1

GitHub integration (integrations:github)

integrations:github

The GitHub-native integration makes the OpsChain model — the estate, resolved properties, vault-backed secrets, path-based authorisation — natively consumable from GitHub Actions, so multi-repo estates stop duplicating configuration and secrets per repository. The repository holds no OpsChain configuration and no standing credentials: each workflow run authenticates itself with its own short-lived GitHub Actions OIDC token and pulls what it needs at run time.

The pieces​

  • GitHub App connection (opschain admin github-connections): the per-instance registration — one GitHub App on github.com or on a GitHub Enterprise Server host. The App's PEM private key is held reference-first (private_key_ref, a secret-vault:// address; the record never stores the key), and the connection carries the OIDC audience the token exchange requires on presented workflow tokens. Sign-in with GitHub is a separate, identity-side capability — this feature begins at the App registration.
  • Trust policies (opschain admin github-trust-policies): admin-defined claim predicates (repository, ref, GitHub environment — */? patterns) mapped onto a service identity and a token TTL. Read-only by default in the sense that the exchanged token holds exactly the service account's own authorisation grants — grant that account read on the paths the pipeline needs and nothing else.
  • The token exchange (ExchangeGitHubActionsToken on the v2 GitHubIntegrationService, served open): a workflow presents its Actions OIDC token (requested with the connection's audience); the server verifies it against the instance's OIDC issuer (derived from the connection host — GitHub Enterprise Server instances use their own issuer), matches an enabled trust policy (name order, first match wins), and answers a short-lived OpsChain access token (github-actions audience) acting as the policy's service account. The minted token authenticates on the API exactly like a session token, is never renewable (the pipeline exchanges again), and dies with session revocation.
  • Just-in-time GH_TOKEN (outbound): a property value that is, in its entirety, github-app://<connection code> — typically opschain.env.GH_TOKEN — is swapped at step dispatch for a freshly minted GitHub App installation token (about an hour's lifetime, GitHub's own cap), minted server-side from the connection's vaulted App key against the instance's REST API. The packs:common GitHub actions are unchanged — only the token's provenance changes — and the hand-provisioned secret-vault:// PAT remains the documented fallback for vaultless or unconnected instances. The connection must record the App installation id (--installation-id); minted values join the run's mask set and are never persisted, and the OpsChain actor rides the step environment as OPSCHAIN_INITIATED_BY — dispatch metadata, never authority.
  • Run attestation (AttestGitHubRun): a GitHub workflow reports its outcome back against an OpsChain change — one info:github:attestation event (status, summary, repository, Actions run id, evidence links) on the change's audit trail, so GitHub-initiated work lands in Chains like native runs. The caller must be able to read the change; the opschain/attest seed action wraps the call.
  • Webhook ingestion (opschain admin github-connections provision-webhook): provisions one signed inbound-webhook connection on the generic inbound lane — GitHub deliveries POST to /api/hooks/github-<code>, authenticated by X-Hub-Signature-256, deduplicated on the delivery id, and executed (when a trigger matches) as the named service account. Common events (push, pull_request, workflow_run, release) additionally expose a normalised typed field set under event.data.github (repository, ref, sha, action, conclusion…) for trigger conditions; the raw payload stays under event.data.payload. An omitted signing secret is generated and shown exactly once.

Who the pipeline is​

The exchanged token acts as the trust policy's service account, never as the human who triggered the run — if visibility followed whoever clicked, the same workflow would deploy to different target sets depending on who ran it. The GitHub actor rides audit metadata only. Estate visibility, properties and secret reads all follow the service identity's authorisation overlay.

Audit trail​

Every exchange lands an info:github:token_exchange event (connection, policy, repository, ref, environment, workflow run id, session id) and one exchange-register row; every refusal lands warn:github:exchange_refused with its reason. Every JIT mint lands info:github:token_mint plus one mint-register row, and every refused mint is a named step failure with warn:github:mint_refused beside it. Every attestation lands info:github:attestation on the change, and webhook provisioning lands api:github_connections:provision_webhook. Connection and policy writes are audited as api:github_connections:* and api:github_trust_policies:*. No event, response or error ever carries token, key or signing-secret material.

Entitlement and limits​

The server-side capability is gated on integrations:github, available on the Basic tier with limits as the lever:

Limit keyBasicMeaning
github_trust_policies3Trust policies that may exist.
github_token_exchanges_per_day1,000Exchanges per UTC day; over-cap exchanges refuse at setup with a clear licence message, never a mid-pipeline mystery.
github_token_mints_per_day1,000Just-in-time GH_TOKEN installation-token mints per UTC day; an over-cap mint fails the step by name and lands warn:github:mint_refused.
github_self_hosted_instances0GitHub Enterprise Server registrations — the tier boundary itself. github.com (GitHub Enterprise Cloud and EMU orgs included) never counts.

Expiry degrades politely, never destructively: new exchanges and registrations refuse with a named licence message (each an audit event), stored configuration is retained untouched, and everything lights back up on upgrade.

Wiring a workflow​

  1. Register the App connection and declare a trust policy pointing at a service account with the grants the pipeline needs.
  2. Give the job permissions: id-token: write and request the OIDC token with the connection's audience.
  3. Exchange it: POST /api/v2/opschain.api.v2.GitHubIntegrationService/ExchangeGitHubActionsToken with {"oidc_token": "...", "connection": "<code>"} — the answer carries access_token (a Bearer token) and its expiry.
  4. Call the API with that bearer: estate reads, resolved properties, secret resolution — whatever the service identity may see.

The starter workflow under examples/github-actions/ in the distribution encodes this shape end to end, and the run-action / attest seed actions close the loop the other way: a workflow invokes an OpsChain action (v2 CreateChange), awaits its conclusion, and attests the outcome back onto the change.

The loop the other way: OpsChain → GitHub​

A step whose scope declares opschain.env.GH_TOKEN: github-app://<connection code> dispatches GitHub work (the packs:common github:workflow_run / github:repo_dispatch actions) under a just-in-time installation token: no stored PAT, nothing to rotate, HA-safe (any cluster member mints). github:repo_dispatch forwards the OpsChain actor automatically in client_payload .initiated_by; github:workflow_run forwards it only when the call site sets forward_initiated_by: "true" (a workflow_dispatch target refuses undeclared inputs, so the target must declare initiated_by). Conclusions flow back on the webhook lane: provision ingestion once and point a trigger at external:webhook:received with event.data.github.* conditions.

See also​

  • opschain docs cli/admin-github-connections — the connection verbs.
  • opschain docs cli/admin-github-trust-policies — the policy verbs.
  • opschain docs entitlements — the feature and limit vocabulary.
  • opschain docs identity — service accounts and the session model.