Use tokio-cron for PASETO key rotation
Context
The application runs as a long-lived Rust/Axum server on a 3-node cluster. PASETO key rotation needs to happen automatically — generate new keys, deprecate old ones, revoke expired ones.
Decision
Use tokio-cron to schedule daily key rotation within the application process. PostgreSQL advisory locks (ADR-0022) prevent concurrent execution across nodes.
Key lifecycle: active → deprecated (365 days) → revoked
Production example
Actual data from the paseto_keys table showing the rotation cycle:
SELECT key_identifier, status, created_at, rotated_at FROM paseto_keys;
key_identifier | status | created_at | rotated_at
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | revoked | 2025-08-12 02:00:00.003055+00 | 2025-08-19 02:00:00.004467+00
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | deprecated | 2025-08-19 02:00:00.004467+00 | 2025-08-26 02:00:00.005355+00
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | deprecated | 2025-08-26 02:00:00.005355+00 | 2025-09-02 02:00:00.009917+00
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | deprecated | 2025-09-02 02:00:00.009917+00 | 2025-09-09 02:00:00.006877+00
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | deprecated | 2025-09-09 02:00:00.006877+00 | 2025-09-16 02:00:00.006826+00
k4.lid.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | active | 2025-09-16 02:00:00.006826+00 |
Weekly rotation at 02:00 UTC. One active key at a time, multiple deprecated keys for validating existing tokens.
Consequences
Automated key rotation without manual intervention or external schedulers. Runs within the application lifecycle. The trade-off is a new dependency and clock sensitivity — rotation fails silently if scheduling breaks, so monitoring consecutive failures matters.