Attestation
Attestation is the material the control-plane verifies at enrollment to decide which tenant this
runner belongs to. You choose it with RUNNER_ATTESTATION_MODE.
Status of each mode
Section titled “Status of each mode”| Mode | Status | Where to use it |
|---|---|---|
bootstrap-token |
default | Development and proofs of concept. It’s the weakest link: a secret someone has to mint and hand over. |
aws-sts |
implemented | EC2, EKS, or a laptop authenticated with aws sso login. |
gke-oidc |
implemented | GKE with Workload Identity. |
eks-oidc |
not implemented | Nowhere. On EKS, use aws-sts. |
bootstrap-token
Section titled “bootstrap-token”RUNNER_ATTESTATION_MODE=bootstrap-tokenRUNNER_BOOTSTRAP_TOKEN=<token minted per tenant>The token is minted per tenant, and it is what pins which organization the runner joins. Without it, boot fails fast with its own message:
RUNNER_ATTESTATION_MODE=bootstrap-token requires RUNNER_BOOTSTRAP_TOKENThat fail-fast exists because the alternative is worse: without it the runner boots, tries to enroll, takes a 401, and enters a reconnect loop, an error that looks like networking and is credentials.
aws-sts
Section titled “aws-sts”RUNNER_ATTESTATION_MODE=aws-stsAWS_REGION=us-east-1This is the mode that closes the cold-start problem for a fleet: with an AWS identity there is no token to mint by hand, so bringing the fleet up stops requiring a human in a browser.
How it works
Section titled “How it works”The runner pre-signs an sts:GetCallerIdentity request (Vault’s IAM-auth style) and sends the
parts (signed headers, body, and region), never a URL. The control-plane executes the call against
an STS endpoint from its own allowlist and reads the ARN from the response.
Two properties follow:
- AWS validates the signature. The control-plane signs nothing and cannot be tricked into calling an arbitrary host: it’s anti-SSRF by construction.
- Nothing secret travels. The SigV4 signature proves possession of the credential without
revealing it, and the
X-Amz-Security-Tokenaccompanying temporary credentials is already public by construction.
The credential comes from the SDK’s default chain: environment variables, the SSO cache, IRSA on EKS, IMDS on EC2.
AWS_REGION is required
Section titled “AWS_REGION is required”RUNNER_ATTESTATION_MODE=aws-sts requires AWS_REGIONThe region is not guessed, on purpose: the control-plane only accepts regions from its own allowlist,
and a wrong guess would surface as an attestation_invalid that reads like a credential problem.
gke-oidc
Section titled “gke-oidc”RUNNER_ATTESTATION_MODE=gke-oidc# RUNNER_GKE_TOKEN_PATH=/var/run/secrets/tokens/gke-oidc/token # defaultThe runner reads the projected OIDC token of the Kubernetes ServiceAccount and sends it as attestation. Nothing secret enters the image: the token is a short-lived, audience-bound JWT projected by the kubelet. The control-plane verifies it against Google’s JWKS and maps the KSA and project to the tenant.
The pod needs a projected volume with a serviceAccountToken mounted exactly at the configured
path. If it’s missing, the error says so:
RUNNER_ATTESTATION_MODE=gke-oidc: failed to read projected KSA token at/var/run/secrets/tokens/gke-oidc/token (is the Workload Identity token volume mounted?)And if the file exists but is empty:
RUNNER_ATTESTATION_MODE=gke-oidc: projected token at … is emptyWhat about renewal?
Section titled “What about renewal?”Certificate renewal uses its own attestation, mtls-renewal, which is not a configurable mode.
See Enrollment and mTLS.