Attic Key Authority
Attic uses several key and token artifacts. Do not collapse them into one “Attic key” concept.
Artifact Taxonomy
| Artifact | Secret? | Owner | Purpose | Rotation impact |
|---|---|---|---|---|
Cache signing keypair for main |
private half is secret | Attic cache config | Signs Nix binary-cache metadata; public half is trusted by Nix clients as ATTIC_PUBLIC_KEY |
disruptive; clients must receive the new public key before relying on the rotated cache |
ATTIC_PUBLIC_KEY |
no | implementation overlays and runner stack inputs | Public Nix trust root for substituter reads | public propagation change only; must match the live cache signing key |
| JWT signing secret | yes | Attic stack secret and OpenTofu state | Signs Attic JWTs for admin, private reads, and pushes | invalidates issued JWTs; does not change ATTIC_PUBLIC_KEY |
ATTIC_TOKEN |
yes | consuming workflow or operator secret | Authorizes private-cache reads and trusted writes | rotate per consumer; not needed for public reads from main |
| RustFS/S3 credentials | yes | storage stack | Backs Attic object storage | storage-plane rotation; unrelated to Nix trust or Attic JWT claims |
Current Policy
The shared main cache is public-read and credentialed-write.
Read-side consumers need:
ATTIC_SERVERATTIC_CACHE=mainATTIC_PUBLIC_KEY
Push-side consumers additionally need:
ATTIC_TOKEN
ATTIC_PUBLIC_KEY is not an authorization token. It only tells Nix which cache
signing key to trust. ATTIC_TOKEN is the authorization material for private
reads and writes, and the nix-job action maps it into an ephemeral
netrc-file when present.
Rotation Rules
Prefer the narrowest rotation that solves the problem.
- Rotate a consumer
ATTIC_TOKENwhen one workflow, repo, or operator token is exposed. This should not change the cache public key. - Rotate the JWT signing secret only when the issuer itself is exposed or the token-issuance boundary changes. This invalidates issued tokens, so refresh workflow and operator secrets afterward.
- Rotate the cache signing keypair only if the cache signing private key is
exposed or the cache is being intentionally re-keyed. Treat this as a
planned cache event because every runner, implementation overlay, GitHub
variable, and developer config that trusts the cache must receive the new
ATTIC_PUBLIC_KEY. - Rotate RustFS/S3 credentials only for storage-plane exposure. Do not update
ATTIC_PUBLIC_KEYorATTIC_TOKENjust because storage credentials change.
Desired-State Reconciliation
The Attic stack owns the desired main cache shape:
- public read enabled
store_dir=/nix/store- priority
41 - no upstream cache key skip list
The init job must create the cache when absent and patch the existing cache
when it already exists. A create-only job is not enough, because it allows
is_public, priority, upstream-key, or store-dir drift to persist forever.
Runner and overlay stacks own propagation of ATTIC_PUBLIC_KEY. If the live
cache public key changes, update those stack inputs and apply them rather than
hand-editing runner pods.
Verification
For the live Honey cache, a healthy public-read state means:
- authenticated
GET /_api/v1/cache-config/mainreportsis_public=true - anonymous
GET /main/nix-cache-inforeturns HTTP 200 - runner templates with
ATTIC_SERVERalso carryATTIC_PUBLIC_KEY
Run:
just attic-cache-authority-check
If anonymous nix-cache-info returns 401, the issue is cache public-read
configuration, not runner label taxonomy.