Skip to content

Wire cache backends into setup quadlets; fast cache status#16

Merged
jdoss merged 1 commit intomasterfrom
fix/cache-setup-quadlet-and-status
Apr 8, 2026
Merged

Wire cache backends into setup quadlets; fast cache status#16
jdoss merged 1 commit intomasterfrom
fix/cache-setup-quadlet-and-status

Conversation

@jdoss
Copy link
Copy Markdown
Contributor

@jdoss jdoss commented Apr 8, 2026

Summary

  • psi-{provider}-setup.container generators now propagate the same cache-backend wiring that the serve quadlet gets. HSM backend: pcscd volume, CREDENTIALS_DIRECTORY, LoadCredentialEncrypted=hsm-pin, After=pcscd.service. TPM backend: LoadCredentialEncrypted=psi-cache-key.
  • psi cache status defaults to a fast path that reads only config and the envelope header — no PKCS#11 session, no TPM unseal. --verify preserves the old decrypt-and-count behavior.

Why

PR #15 shipped the encrypted cache but only wired the serve container for HSM access. On the test server the cache silently skipped population with "Secret cache backend hsm unavailable during setup: No HSM PIN found" because the setup container could not resolve the PIN. Manually patching the setup quadlet to match the serve container confirmed the fix — setup then populated 490 entries into cache.enc.

Separately, psi cache status was opening a full HSM session (PKCS#11 login + pubkey fetch + RSA-OAEP decrypt) just to print an entry count, which took 25+ seconds on Nitrokey HSM. Routine status checks never need that cost. The fast path now reads the first six bytes of cache.enc for magic, version, and backend tag — no crypto, no provider interaction.

What changes

psi/unitgen.py

  • _cache_hsm_container_lines and _cache_tpm_container_lines return the [Container]/[Service] lines each backend needs
  • _cache_quadlet_extras picks the right one based on settings.cache.backend and also signals whether the caller should add After=pcscd.service
  • generate_container_serve_quadlet and generate_container_provider_setup_quadlet both consume the helpers

psi/cache.py

  • New read_header(path) helper: opens the file, reads 6 bytes, validates magic, returns (version, backend_tag). Raises CacheError on short files or bad magic. No payload access.

psi/cli.py

  • psi cache status default path: prints config, file size, mtime, and on-disk backend tag without opening any backend
  • psi cache status --verify preserves the prior behavior: opens the backend, decrypts, reports entry count
  • Output now also shows the header version and backend tag as recorded in the file, making it easy to spot a mismatch between config and disk

tests/test_unitgen.py

  • 10 new tests covering each backend × unit-type combination:
    • Setup quadlet with no cache → no wiring
    • Setup quadlet with disabled cache → no wiring
    • Setup quadlet with HSM → pcscd + pin + After ordering
    • Setup quadlet with TPM → cache-key credential only, no pcscd
    • Serve quadlet with HSM → same HSM wiring
    • Serve quadlet with TPM → cache-key credential, no pcscd
    • Serve quadlet with no cache → no wiring
    • Native serve TPM → cache-key credential, StateDirectory set
    • Native serve HSM → no credential emitted (HSM in native mode is an open question)
    • Native serve with no settings → safe no-op

Test plan

  • uv run ruff check psi/ tests/ — clean
  • uv run ruff format --check psi/ tests/ — clean
  • uv run ty check — clean
  • uv run pytest -q — 288 passed (10 new cache-wiring tests)
  • Deploy the new image on the test server, re-run psi systemd install --mode container --image ghcr.io/quickvm/psi:latest --enable, confirm the regenerated psi-infisical-setup.container has the HSM wiring
  • Confirm psi cache status (no flag) returns in well under a second inside the serve container
  • Confirm psi cache status --verify still decrypts and reports the entry count

The secret cache silently broke on hosts using the HSM backend because
only psi-secrets.container got the pcscd socket and
LoadCredentialEncrypted=hsm-pin wiring. The psi-{provider}-setup
containers also need HSM access now that setup eagerly populates the
cache, but the generators did not emit that wiring. Result: setup logs
"Secret cache backend hsm unavailable during setup: No HSM PIN found",
skips cache population, and every lookup still hits the live provider.

unitgen now has helpers that return the Container/Service lines needed
by each cache backend. Both generate_container_serve_quadlet and
generate_container_provider_setup_quadlet consume them. HSM backend
adds the pcscd volume, credentials directory, hsm-pin credential, and
After=pcscd.service ordering. TPM backend adds the psi-cache-key
credential.

psi cache status now defaults to a fast path that reads only config
and the envelope header — no PKCS#11 session, no TPM unseal, no
decrypt. The entry count previously required a full backend open just
to report a number, which took 25+ seconds on HSM. Pass --verify to
get the decrypt-and-count path. psi.cache.read_header parses magic,
version, and backend tag from the first six bytes without touching
the payload.
@jdoss jdoss merged commit 866e746 into master Apr 8, 2026
2 checks passed
@jdoss jdoss deleted the fix/cache-setup-quadlet-and-status branch April 8, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant