Problem
Worker registration tokens are currently reusable. If a token is leaked or intercepted, an unauthorized worker could register against the API indefinitely. There is also no mechanism for a worker to persist its identity across restarts without keeping the original registration token in the environment.
Proposed solution
Registration flow (one-time token)
- Admin generates a registration token in the UI (Configuration → Workers → Register Worker) — this token can only be used once
- Worker starts with
PIRO_WORKER_TOKEN=<registration-token> in its environment
- On first connect, the API exchanges the registration token for a long-lived worker credential (a separate token tied to that specific worker identity) and marks the registration token as consumed
- The worker writes the credential to a local file (e.g.
$PIRO_DATA_DIR/worker.credentials or /var/lib/piro-worker/credentials) — path configurable via PIRO_DATA_DIR
- On subsequent restarts, the worker loads credentials from disk and connects using those — the original registration token is no longer needed or used
Failure behavior
| State |
Behavior |
PIRO_DATA_DIR exists with valid credentials |
Connect normally |
PIRO_DATA_DIR missing, PIRO_WORKER_TOKEN set |
Register, exchange token, persist credentials, connect |
PIRO_DATA_DIR missing, PIRO_WORKER_TOKEN missing |
Fail with clear error message |
PIRO_DATA_DIR exists but credentials are invalid/revoked |
Fail with clear error — do not fall back to registration token |
API changes
- Registration token gains a
UsedAt timestamp and IsConsumed flag
- Exchange endpoint:
POST /api/v1/workers/exchange — accepts registration token, returns worker credential
- Admin can see in the UI whether a registration token has been consumed and when
- Revoking a worker credential (from the Workers UI) invalidates the credential on disk — worker will not reconnect until re-registered
Security properties
- A registration token can only be used once — replay attacks are not possible
- Long-lived worker credentials are scoped to a specific worker identity
- Credentials on disk should be protected with
chmod 600 by the worker process
PIRO_DATA_DIR should default to a path that survives container restarts (e.g. mounted volume)
Also covers: token expiry & rotation (merged from #36)
#36 was closed as a duplicate; its scope is folded in here so worker-token security lives in one issue. In addition to the one-time registration flow above, this issue also covers:
Token expiry
- Add
WorkerRegistration.ExpiresAt (DateTime?, null = never expires).
WorkerHub.OnConnectedAsync rejects connections where ExpiresAt < UtcNow, with a log warning.
GET /api/v1/workers includes expiresAt in the response DTO.
Token rotation
POST /api/v1/workers/{id}/rotate-token — generates a new token hash, immediately invalidates the old one, returns a fresh CreateWorkerResponse. The worker must be reconfigured with the new PIRO_WORKER_TOKEN and restarted (or, once the one-time exchange flow above lands, re-registered).
- Only the SHA-256 hash of a token is ever stored; rotation is the recovery path for a leaked token.
Docs
- Worker setup guide: recommend rotating tokens periodically and document expiry.
Note: the one-time exchange flow and expiry/rotation are complementary — a persisted worker credential (from the exchange) can itself carry an ExpiresAt and be rotated via the same endpoint.
Problem
Worker registration tokens are currently reusable. If a token is leaked or intercepted, an unauthorized worker could register against the API indefinitely. There is also no mechanism for a worker to persist its identity across restarts without keeping the original registration token in the environment.
Proposed solution
Registration flow (one-time token)
PIRO_WORKER_TOKEN=<registration-token>in its environment$PIRO_DATA_DIR/worker.credentialsor/var/lib/piro-worker/credentials) — path configurable viaPIRO_DATA_DIRFailure behavior
PIRO_DATA_DIRexists with valid credentialsPIRO_DATA_DIRmissing,PIRO_WORKER_TOKENsetPIRO_DATA_DIRmissing,PIRO_WORKER_TOKENmissingPIRO_DATA_DIRexists but credentials are invalid/revokedAPI changes
UsedAttimestamp andIsConsumedflagPOST /api/v1/workers/exchange— accepts registration token, returns worker credentialSecurity properties
chmod 600by the worker processPIRO_DATA_DIRshould default to a path that survives container restarts (e.g. mounted volume)Also covers: token expiry & rotation (merged from #36)
#36 was closed as a duplicate; its scope is folded in here so worker-token security lives in one issue. In addition to the one-time registration flow above, this issue also covers:
Token expiry
WorkerRegistration.ExpiresAt(DateTime?, null = never expires).WorkerHub.OnConnectedAsyncrejects connections whereExpiresAt < UtcNow, with a log warning.GET /api/v1/workersincludesexpiresAtin the response DTO.Token rotation
POST /api/v1/workers/{id}/rotate-token— generates a new token hash, immediately invalidates the old one, returns a freshCreateWorkerResponse. The worker must be reconfigured with the newPIRO_WORKER_TOKENand restarted (or, once the one-time exchange flow above lands, re-registered).Docs