Encryption
At rest, in the application
Sensitive columns are encrypted by the app before they are written, using envelope encryption. A data key encrypts the row; a key-encryption key wraps the data key.
That sits above whatever your database does at rest. A pg_dump, a snapshot restored into another account, or a recovered disk image yields ciphertext without access to the KEK.
Where the KEK lives
KMS_BACKEND selects it.
| Value | KEK source |
|---|---|
k8s | APP_KEK from the application secret |
aws | AWS KMS |
gcp | Google Cloud KMS |
vault | HashiCorp Vault |
The chart's own comments call k8s the weaker fallback, and that is accurate. Use a real KMS in production.
Three reasons. You can revoke access centrally and immediately, which is the fastest way to render a dataset unreadable during an incident. Key rotation is handled by the KMS rather than by you. And it removes the one secret whose loss is unrecoverable from your backup burden.
APP_KEK is not recoverable
With KMS_BACKEND: k8s, losing APP_KEK makes every encrypted row permanently unreadable. A database restore does not help: the data in the backup is encrypted with the key you lost.
Back it up somewhere that outlives the cluster, the namespace, and the person who created it. A cloud secret manager in a separate project, or your organisation's password manager. Not only in the Kubernetes Secret.
This is the single most consequential operational fact about the deployment. It belongs in your runbook, not just in documentation.
Blind indexes
Encrypted columns cannot be searched. For sensitive columns that need equality lookups, the app stores a blind index: an HMAC of the value, keyed with INDEX_PEPPER.
Two consequences.
Changing INDEX_PEPPER invalidates every existing blind index. Lookups stop matching until the data is reindexed. Treat it as immutable in practice.
A blind index leaks equality. Two rows with the same underlying value produce the same index. That is the trade-off that makes search possible, and it is worth stating in a review rather than leaving implicit.
Token hashing
AUTH_TOKEN_PEPPER HMACs login tokens before storage, so the database holds token_hash rather than a usable credential. When it is set, the app stops relying on plaintext token lookup.
The backfill Job
Deployments that predate encryption have plaintext rows. backfill.enabled: true runs a one-off Job that encrypts them. It is idempotent.
Enable it once after provisioning a KEK, confirm it completed, then set it back to false so it does not run on every upgrade.
kubectl logs -n presponsieve job/presponsieve-backfill
In transit
TLS terminates at the load balancer. Traffic from there to pods is HTTP inside the cluster network; run a service mesh if your policy requires encryption all the way to the pod.
Database connections should use TLS. Managed PostgreSQL on all three clouds can enforce it at the server, which is better than relying on the client to ask.
AUTH_COOKIE_SECURE defaults to "1", so the session cookie is never sent over plain HTTP.