Secrets
The chart reads every sensitive value from a single Kubernetes secret, named by app.existingSecret.
The keys
| Key | Required | Purpose |
|---|---|---|
APP_KEK | When KMS_BACKEND=k8s | Envelope-encryption key |
SESSION_SECRET | For more than one replica | Signs the session cookie |
INDEX_PEPPER | Yes | HMAC for blind indexes on searchable columns |
AUTH_TOKEN_PEPPER | Yes | HMAC for hashing login tokens at rest |
LICENSE_KEY | Yes | Signed license token |
DATABASE_URL | Usually | Full DSN. Not needed with the Cloud SQL Auth Proxy |
MODEL_CONTENT_KEY | Sometimes | Decrypts model assets, when the license does not carry it |
OPENAI_API_KEY | No | Enables narrative rendering and chat |
ACCESS_TOKENS | No | Break-glass login tokens |
GOOGLE_CLIENT_ID | No | Google Sign-In |
Generating the crypto material
openssl rand -base64 32 # APP_KEK
openssl rand -base64 32 # SESSION_SECRET
openssl rand -base64 32 # INDEX_PEPPER
openssl rand -base64 32 # AUTH_TOKEN_PEPPER
APP_KEK is not recoverable. Lose it and every encrypted row is permanently unreadable. A database restore does not help, because 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 different project, or your organisation's password manager. Not only in the Kubernetes Secret.
INDEX_PEPPER and AUTH_TOKEN_PEPPER are nearly as unforgiving. Changing INDEX_PEPPER invalidates every blind index, so equality lookups on sensitive columns stop matching until the data is reindexed.
Creating it
For evaluation:
kubectl create secret generic presponsieve-secrets -n presponsieve \
--from-literal=APP_KEK="..." \
--from-literal=SESSION_SECRET="..." \
--from-literal=INDEX_PEPPER="..." \
--from-literal=AUTH_TOKEN_PEPPER="..." \
--from-literal=LICENSE_KEY="..."
For production, use External Secrets, Vault, or Sealed Secrets. The chart only needs the secret to exist with the right keys.
External Secrets
Store the values as a JSON object in your cloud secret manager, then project the keys:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: presponsieve-secrets
namespace: presponsieve
spec:
refreshInterval: 1h
secretStoreRef:
name: your-store
kind: SecretStore
target:
name: presponsieve-secrets
creationPolicy: Owner
data:
- secretKey: APP_KEK
remoteRef: {key: presponsieve/app, property: APP_KEK}
- secretKey: SESSION_SECRET
remoteRef: {key: presponsieve/app, property: SESSION_SECRET}
- secretKey: INDEX_PEPPER
remoteRef: {key: presponsieve/app, property: INDEX_PEPPER}
- secretKey: AUTH_TOKEN_PEPPER
remoteRef: {key: presponsieve/app, property: AUTH_TOKEN_PEPPER}
- secretKey: LICENSE_KEY
remoteRef: {key: presponsieve/app, property: LICENSE_KEY}
The Terraform blueprints set this up for you on all three clouds.
Rotation and restarts
Changing a Kubernetes secret does not restart the pods. Install Reloader and a rotated license or pepper triggers a rolling restart automatically:
kubectl annotate externalsecret presponsieve-secrets -n presponsieve \
force-sync="$(date +%s)" --overwrite
Without Reloader:
kubectl rollout restart deploy/presponsieve -n presponsieve
Avoiding APP_KEK entirely
Set KMS_BACKEND to aws, gcp, or vault and the key-encryption key lives in a real KMS instead. That removes the one secret whose loss is unrecoverable from your backup burden, and lets you revoke access centrally.
The k8s backend is documented as the weaker fallback for a reason. Use a real KMS in production. See Encryption.