Runner Authentication
Runners authenticate with ES256 JWTs signed by the runner group’s private key. The backend stores only the public key.
Key Model
Each runner group owns one ECDSA P-256 keypair:
- private key: delivered once at group creation or key rotation
- public key: stored on the runner-group record in PostgreSQL
The backend does not keep the private key.
JWT Types
Runner Group JWT
Used by:
GET /api/v1/runner-groups/pollPOST /api/v1/runner-groups/heartbeat
Purpose:
- authenticate the long-lived coordinator process
Key claims include:
- token type
- runner group ID
The group signs fresh JWTs during normal operation rather than relying on a long-lived static token.
Ephemeral Runner JWT
Used by:
POST /api/v1/runners/{runner_id}/readyPOST /api/v1/runs/{run_id}/acceptGET /api/v1/runs/{run_id}/statusPOST /api/v1/runs/{run_id}/donePOST /api/v1/runs/{run_id}/acknowledging-cancelDELETE /api/v1/runners/{runner_id}/deregister
Purpose:
- bind one ephemeral runner to one run within one runner group
Key claims include:
- token type
- runner ID
- run ID
- group ID
Handlers still verify that JWT path parameters match the signed runner/run IDs.
Post-Completion Grace Period
Ephemeral JWTs remain valid for EphemeralRunnerJWTGracePeriod = 15 minutes after the run terminates (constant in internal/crypto/jwt.go). This window lets the runner flush logs, deregister, and complete cleanup calls without racing the run-completion transaction. The primary validity gate is run.completed_at + grace; the JWT exp claim is a backstop only.
Backend Verification Flow
Middleware verifies runner JWTs by:
- reading
kidfrom the JWT header - resolving the runner group by ID
- loading its public key
- verifying the ES256 signature
- validating standard and type-specific claims
- placing authenticated identity into request context
This is stateless verification. There is no token database.
O(1) Lookup
kid is the runner group ID, so verification can jump directly to the correct public key.
Key Rotation
Key rotation replaces the stored public key immediately and returns a new private key once.
Operational impact:
- JWTs signed by the old key stop verifying immediately
- the runner group must pick up the new private key (typically a restart) before its next signed call
Labels Are Not Trusted From JWTs
Runner labels are not embedded in ephemeral JWTs as an authority source. The backend reloads effective labels from database state during ready/assignment flows instead of trusting caller-supplied label claims.