Skip to main content
Workflows that migrate from GitHub Actions keep their id-token: write steps, but the token they receive is minted by Preloop: a different issuer and a different signing key. Each cloud must be told to trust that issuer before the first deploy job runs. preloop ci oidc check proves the trust exists — it does not infer it from configuration.

What the check does

For a repository, ref, and optional environment, the control plane:
  1. Mints an OIDC token with the claims a real job on that repository would receive: iss, sub, aud, repository, repository_owner, ref, event_name, environment, org_id, and the immutable-subject form when the repository has one.
  2. Spends the token once against the provider’s token exchange:
  3. Reports trusted, rejected, or error, with the provider’s sanitized reason. The token and any credentials the provider returned are discarded immediately and never appear in the response.
The exchange runs in the control plane, not in a probe job on a runner. A runner microVM is provisioned in Preloop’s network, not the customer’s, so a probe job would have the same reachability as the control plane while additionally handing a live Preloop OIDC token to a job environment and its log pipeline. Keeping the exchange in the control plane means the token only ever travels to the provider it was minted for. :::note A failed connection to your Vault address is an error, not a rejected: the trust may be fine, but the control plane cannot reach the server. Publish Vault on an address the control plane can route to, or run the check from a network that can. :::

Run the check

The command reads .github/workflows, takes every cloud trust relationship it finds together with any literal provider inputs (role-to-assume, workload_identity_provider, client-id, url, …), and checks each provider:
Flags: The command exits non-zero when any provider is not trusted, so it can gate a migration pipeline. Checks are rate-limited per repository (five per minute per control-plane node).

Trust condition

Always condition on the full sub. A policy that matches only repo:OWNER/REPO:* trusts a repository name, and a name is only meaningful when the tenant that minted the token provably owns it. Add org_id only where the provider supports claim conditions: GCP attribute conditions and Vault bound_claims accept it; Azure and AWS cannot use it (see each section), so their tenant binding comes from the per-org issuer path or an exact subject.

AWS

The role’s trust policy needs sts:AssumeRoleWithWebIdentity and conditions keyed by the issuer host:
Use StringLike with repo:acme/api:ref:refs/heads/* only when every branch of the repository should deploy. A custom IAM OIDC provider exposes only sub, aud, amr and oaud as condition keys; a …:org_id condition makes every AssumeRoleWithWebIdentity deny. org_id belongs in Preloop’s Store/ABAC decisions, not in an IAM trust policy.

Google Cloud

The workload identity provider must use Preloop as its issuer, allow the provider resource as an audience, and map assertion.sub and assertion.org_id:
To bind the tenant as well, use --member "principal://iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/preloop/subject/…" with the exact subject, or an attribute.org_id principalSet.

Azure

Entra federated credentials match issuer, subject, and audiences only — there is no slot for org_id. Keep the subject exact. The hosted issuer path is per organization (https://oidc.preloop.dev/o/<org-id>), so this restriction stays tenant-safe there; a single-tenant deployment that shares an issuer between tenants must not rely on subject alone.

HashiCorp Vault

bound_audiences must equal the audience the token carries. When the workflow’s hashicorp/vault-action step does not set jwtGithubAudience, Preloop uses the GitHub-style default https://github.com/<owner>; pass --audience to check another value.

Configuration

[oidc] organization binds the control plane’s tenant into every token as org_id:
A hosted control plane that serves several organizations from one issuer must derive org_id from the authenticated organization, not from this node-wide setting. Until it does, a trust policy that conditions on sub alone cannot distinguish two tenants whose submitted repository names collide.