Attic Key Authority

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_SERVER
  • ATTIC_CACHE=main
  • ATTIC_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.

  1. Rotate a consumer ATTIC_TOKEN when one workflow, repo, or operator token is exposed. This should not change the cache public key.
  2. 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.
  3. 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.
  4. Rotate RustFS/S3 credentials only for storage-plane exposure. Do not update ATTIC_PUBLIC_KEY or ATTIC_TOKEN just 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/main reports is_public=true
  • anonymous GET /main/nix-cache-info returns HTTP 200
  • runner templates with ATTIC_SERVER also carry ATTIC_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.

GloriousFlywheel