Skip to content

Run identity

Give Runs short-lived OIDC credentials for AWS, GCP, Azure, or OpenBao/Vault — with read-only trust for plans and write trust for applies — so no cloud keys ever rest with Longbridge.

Every Run can prove exactly what it is — this organization, this Stack, this phase — with a short-lived OpenID Connect token minted by the Stacks control plane. Your cloud trusts the token instead of a stored key, so your cloud credentials never rest with Longbridge. Static keys in variables still work; this page is how you retire them.

The token

  • Issuer: https://api.stacks.lngbrdg.com (discovery and JWKS are published at the standard /.well-known/… paths).
  • Subject: org:<org>:stack:<stack>:run_phase:<plan|apply> — stable and policy-friendly. <stack> is the Stack’s id (shown in the Console), not its display name, so the subject survives a rename — a stack_name claim carries the human name for readability. The phase in the subject is what lets you grant a plan read-only cloud access and an apply write access.
  • Claims: the Run’s id, kind, repository, ref, commit, and runner pool — everything asserted by the control plane, never by the runner.
  • Lifetime: 10 minutes.
  • Audience (aud): whatever the consumer requests when it mints the token. The request URL below takes an ?audience=<aud> parameter, and that value becomes the token’s aud claim (omit it and aud falls back to the issuer URL). Your trust policy then conditions on that same value — so the two must be set to the same string; they don’t align on their own. The zero-config AWS path requests sts.amazonaws.com for you, which is exactly why its trust policy conditions on aud = sts.amazonaws.com; OpenBao, GCP, and Azure each pass their own audience.

AWS, the zero-config way

  1. In your AWS account, create an IAM OIDC identity provider for https://api.stacks.lngbrdg.com with audience sts.amazonaws.com.
  2. Create two roles whose trust policies differ only in the subject condition:
{
  "Effect": "Allow",
  "Principal": { "Federated": "arn:aws:iam::<account>:oidc-provider/api.stacks.lngbrdg.com" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "api.stacks.lngbrdg.com:aud": "sts.amazonaws.com",
      "api.stacks.lngbrdg.com:sub": "org:<org>:stack:<stack>:run_phase:plan"
    }
  }
}

Give the run_phase:plan role read-only policies and the run_phase:apply role write policies.

  1. Set two plain (not sensitive — an ARN is public) environment variables on the Stack:

    • AWS_PLAN_ROLE_ARN — the read-only role
    • AWS_APPLY_ROLE_ARN — the write role

That’s it. Each phase’s runner mints a token, and the AWS provider assumes the phase’s role via AssumeRoleWithWebIdentity — no access keys anywhere. (A single AWS_ROLE_ARN also works if you don’t want the split.) Role sessions are named bosun-run-<id>-<phase>, so CloudTrail attributes every cloud action to a specific Run.

OpenBao / Vault

Enable the jwt auth method against the issuer and bind roles by subject:

bao auth enable jwt
bao write auth/jwt/config oidc_discovery_url="https://api.stacks.lngbrdg.com"
bao write auth/jwt/role/stack-apply \
  role_type=jwt user_claim=sub \
  bound_audiences="openbao" \
  bound_subject="org:<org>:stack:<stack>:run_phase:apply" \
  policies=infra-secrets ttl=10m

Inside a Run, request a token for your audience from the environment the runner provides:

curl -sS -H "Authorization: Bearer $BOSUN_ID_TOKEN_REQUEST_TOKEN" \
  "$BOSUN_ID_TOKEN_REQUEST_URL?audience=openbao"

The response’s value is the JWT to present to auth/jwt/login.

Anything else that speaks OIDC

GCP Workload Identity Federation and Azure federated credentials follow the same shape: trust the issuer, condition on the subject, map the phase split onto your roles. The request URL + bearer pattern above works for any consumer.