Skip to content

feat: one-time worker registration tokens with persistent credential storage #44

Description

@cl8dep

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)

  1. Admin generates a registration token in the UI (Configuration → Workers → Register Worker) — this token can only be used once
  2. Worker starts with PIRO_WORKER_TOKEN=<registration-token> in its environment
  3. 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
  4. 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
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / API workenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions