Other CI platforms
Thirty-two platforms prove who they are with a token they already mint, and the console builds the trust from what you name.
Your CI logs in like a person does, with no key to steal. Thirty-two platforms are in the connect catalog. On nearly all of them you name the workload and the console builds the rule that recognizes it. GitHub Actions asks for nothing, because the console reads the subject from GitHub itself.
Pick your platform
Open Machine Identities then Connect a Platform. A card asks only for the values that identify the workload, and builds the issuer and the subject from them.
| Platform | What it asks for |
|---|---|
| GitHub Actions | Nothing. The console reads the subject from GitHub |
| GitLab CI | Project path and branch |
| CircleCI | Organization ID and project ID |
| Buildkite | Pipeline ID |
| Bitbucket Pipelines | Workspace ID and repository UUID |
| Kubernetes, GKE, EKS, AKS, OKE | Cluster issuer URL, namespace and service account |
| Jenkins | Jenkins URL and the subject the plugin prints |
| TeamCity | Server URL and the build configuration path |
| GitHub Enterprise Server | Server URL, repository and branch |
| GitHub Enterprise Cloud with data residency | Enterprise subdomain and the subject |
| Forgejo Actions | Forgejo URL, repository and branch |
| Bitrise | App slug and workflow |
| HCP Terraform | Organization, workspace and run phase |
| Scalr | Workspace ID |
| SPIFFE and SPIRE | Discovery provider URL and SPIFFE ID |
| Teleport Workload Identity | Proxy URL and SPIFFE ID |
| HashiCorp Vault | Issuer URL and entity ID |
| HashiCorp Nomad | Issuer URL and job ID |
| Vercel | Team slug, project and environment |
| Deno Deploy | Organization slug, app and context |
| Fly.io | Organization slug and app name |
| Ona | Organization ID and project ID |
| Auth0, Okta, Keycloak, authentik | Tenant or issuer URL, and the client the machine runs as |
| Devin | The subject your --subject-keys produces |
| RWX | The subject the Vault UI prints |
Fill the form, and the console creates the identity, stores the trust, and prints a pipeline snippet with your workspace id in it.
Bare VPS sits on the same page and is none of the thirty-two. There is no issuer and no subject to compare, because the host generates a key rather than presenting a token. See a host that proves nothing.
Two rules that decide whether it works
One audience. Ask for this audience and no other. A token requested for two audiences fails every exchange. The audience is always your workspace id, so a token minted for one workspace cannot authenticate at another.
One exact subject. Subjects are compared byte for byte, with no wildcards. That is why the
console builds the string rather than asking you to type it. Several platforms put something
per-run in sub, so penv pins a different claim instead: CircleCI's project id, Bitbucket's
repository UUID, Fly's app_name, Nomad's nomad_job_id, Scalr's scalr_workspace_id and
Keycloak's azp. A trust that pinned the raw sub on those would match once and never again.
A self-hosted issuer has to be reachable from the internet. Penv Cloud reads the signing keys from the issuer's own origin, and refuses a discovery document that points anywhere else.
Hand penv the token
Once your platform mints a token, one variable is all penv needs.
PENV_OIDC_TOKEN="$THE_TOKEN" penv run -- npm run deployPENV_OIDC_TOKEN is the generic hand-off for any platform. penv exchanges it and runs your
command with the values in the child process. If you would rather do the exchange yourself, post
the token and use what comes back:
C=$(curl -sS "https://penv.cloud/api/v1/auth/oidc" \
-H 'content-type: application/json' \
-d "{\"token\":\"$THE_TOKEN\"}" | jq -r .credential)
PENV_TOKEN="$C" penv run -- npm run deployThe credential lasts fifteen minutes, capped by the trust's own expiry, and is bound to one project and one environment.
Pods in a cluster
Every managed Kubernetes card wires the same projected volume. Ask the cluster for a token addressed to your workspace, then mount it where the container can read it.
volumes:
- name: penv-token
projected:
sources:
- serviceAccountToken:
path: token
audience: YOUR_WORKSPACE_ID
expirationSeconds: 3600
containers:
- name: api
volumeMounts:
# Without this the volume above exists and no container can read it.
- name: penv-token
mountPath: /var/run/secrets/penv
readOnly: trueThe subject is system:serviceaccount:<namespace>:<serviceaccount> on every one of them. Use the
projected token rather than the cluster's built-in one: AWS_WEB_IDENTITY_TOKEN_FILE is addressed
to sts.amazonaws.com and AZURE_FEDERATED_TOKEN_FILE to Microsoft, and neither audience can be
changed.
Nothing in the catalog fits
Two ways out.
Enter the trust by hand. Give the issuer, the audience and the subject on the identity's own
page. The console warns you when the issuer's sub names an identity rather than a machine:
Google's names the service account, so every instance running as it matches, and Azure's names the
managed identity, so every resource assigned it matches.
Prove nothing, and bind a key instead. A plain VPS signs no metadata and has no secure element. See a host that proves nothing.
Do it in order
- Open Machine Identities then Connect a Platform and find your platform.
- Fill in the fields, then read back the issuer and the subject the console shows.
- Copy the workspace id. It is the audience every token must carry.
- Make your platform mint a token for that audience and no other.
- Put the token in
PENV_OIDC_TOKEN, or exchange it yourself and put the answer inPENV_TOKEN. - Run the job. A
401 unauthorizedmeans the subject does not match what the trust names.