OIDC federation for workflow jobs
Vetrix mints short-lived, signed JWTs that workflow jobs can exchange for real credentials at AWS, GCP, Azure, HashiCorp Vault, or any other IdP that understands OpenID Connect. No long-lived cloud secrets live in Vetrix; every token is single-run, single-job, and tightly scoped.
Novel trait: wildcard subject patterns
Where GitHub OIDC forces one trust policy per branch × environment tuple
(github-oidc#172176), Vetrix
lets the admin declare one policy whose subject_claim_pattern is a glob:
| Pattern | Matches |
|---|---|
repo:acme/site:environment:prod/* |
prod/eu, prod/us (single segment) |
repo:acme/site:environment:prod/** |
prod/eu, prod/eu/lon, prod/us/nyc (any depth) |
repo:acme/site:ref:refs/heads/release/** |
any release/… ref |
repo:*/**:environment:dev |
dev env in any repo owned by any user |
The server ranks matches by (scope tier, literal-character count) — the most specific policy wins when multiple are permissive enough.
Escape metacharacters with \: foo\* matches the literal foo*.
Endpoints
| Method | Path | Auth |
|---|---|---|
| GET | /api/v1/.well-known/openid-configuration |
public |
| GET | /api/v1/.well-known/jwks.json |
public |
| POST | /api/v1/automations/oidc/token |
user JWT / runner agent |
| GET | /api/v1/repos/{owner}/{repo}/oidc-trust-policies |
PermOIDCTrustPolicyRead |
| POST | /api/v1/repos/{owner}/{repo}/oidc-trust-policies |
PermOIDCTrustPolicyCreate |
| DELETE | /api/v1/repos/{owner}/{repo}/oidc-trust-policies/{id} |
PermOIDCTrustPolicyDelete |
Token request shape
POST /api/v1/automations/oidc/token
{
"audience": "sts.amazonaws.com",
"owner": "acme",
"repo": "site",
"environment": "prod/eu",
"ref": "refs/heads/main",
"sha": "cafebabe…",
"workflow_run_id": "…",
"job_id": "…",
"run_attempt": 1,
"event_name": "push",
"runner_environment": "self-hosted",
"ttl_seconds": 900
}
Response:
{
"token": "eyJ…",
"expires_at": 1700000900,
"expires_in": 900,
"subject": "repo:acme/site:environment:prod/eu:ref:refs/heads/main:event:push",
"audience": "sts.amazonaws.com",
"policy_id": "…",
"role_arn_or_equivalent": "arn:aws:iam::123:role/deploy-prod"
}
Claim shape
| Claim | Value |
|---|---|
iss |
vetrix/<external_url> |
sub |
repo:<owner>/<repo>:environment:<env>:ref:<ref>:event:<evt> (empty parts rendered as _) |
aud |
the audience requested |
exp, iat, nbf, jti |
standard JWT fields |
typ |
always oidc-federation — reject as a session token |
repository, ref, sha, workflow_run_id, job_id, run_attempt, event_name, runner_environment |
workflow context |
Signing: RS256 with the Vetrix OIDC signing keys. Downstream IdPs fetch
the key set from /api/v1/.well-known/jwks.json.
Safety invariants
| Guardrail | Enforcement |
|---|---|
| Fork-PR workflows cannot mint environment-scoped tokens | handler 403 + issuer refuses (ErrForkPRWithEnvironment) |
| Federation token cannot be used as a Vetrix session JWT | session validator uses HS256 only; typ claim rejected in auth.Service.Verify |
| Token TTL is clamped to 300..3600 seconds | DB CHECK + issuer ValidateTTL + handler 422 |
audience is required |
handler 400 |
| Audit captures plaintext claims + policy id | oidc.token.minted action; no key material |
Helper composite step — vetrix/oidc@<SHA>
Drop into a job to export VETRIX_OIDC_TOKEN for the next step:
# .vetrix/automations/deploy.yml
jobs:
deploy:
steps:
- uses: vetrix/oidc@<SHA>
with:
audience: sts.amazonaws.com
environment: prod/eu
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.VETRIX_ROLE_ARN }}
web-identity-token-file: /dev/stdin
vetrix/oidc@<SHA> is pinned by content digest by default — tag refs
render with a warning banner in the UI. Source lives in
src/composite-steps/oidc/action.yml (vetrix-frontend repo).
The step internally:
- Reads workflow context from the runner's environment bag.
- Calls
POST /api/v1/automations/oidc/tokenwithaudience,environment, and the full workflow metadata. - Writes the returned
tokento$RUNNER_TEMP/.vetrix_oidc_token. - Exports
VETRIX_OIDC_TOKENandVETRIX_ROLE_ARNinto the next step's environment bag via the$GITHUB_ENV-compatible append protocol.
The engine refuses to run this step in a fork-PR context if an
environment: is declared — see the safety invariants above.