The Secret template defaulted both keys to randAlphaNum 32, which Helm
re-rolls on every template render. As a result, any `helm upgrade`
without explicit secrets.systemAesKey / secrets.tenantAesKey rotated
the keys, breaking decryption of every previously encrypted field
(tenants.api_key, model API keys, vector store credentials, web
search provider keys, WeKnoraCloud.AppSecret) and surfacing
"enc:v1:..." ciphertext in the UI.
Use Helm's `lookup` to reuse the values stored in the existing Secret
when one is already present, falling back to randAlphaNum only on
first install. Also document the recovery caveat in values.yaml so
operators understand the risk of relying on the auto-generated value.
- Add crypto utility (internal/utils/crypto.go) with AES-256-GCM encrypt/decrypt
using SYSTEM_AES_KEY env var, with "enc:v1:" prefix for versioned ciphertext
- Encrypt tenant API key via GORM BeforeSave/AfterFind hooks and manual
encryption in CreateTenant/UpdateAPIKey (db.Updates bypasses hooks)
- Encrypt model API key in ModelParameters Value/Scan (driver.Valuer)
- Widen api_key column from varchar(64) to varchar(256) across all DB dialects
(MySQL, ParadeDB, SQLite) and add versioned migration 000018
- Propagate SYSTEM_AES_KEY through docker-compose, Helm secrets and values
- Fix migration 000017 PL/pgSQL dollar-quoting syntax ($ -> $$)
Official Docker images (nginx, postgres, redis, paradedb) run as root
by default. Disabled runAsNonRoot and readOnlyRootFilesystem to ensure
compatibility.
Changes:
- global.podSecurityContext: removed runAsNonRoot
- global.containerSecurityContext: removed runAsNonRoot, readOnlyRootFilesystem
- Component securityContext: commented out incompatible settings
- app.image.tag: changed from "" to "latest" (versioned tags not available)
Tested on local Kubernetes cluster (k3s) with all pods running successfully.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>