* feat(security): encrypt server-owned credentials at rest Introduce AES-256-GCM at-rest encryption (HKDF-derived from a required SECRET_KEY) for server-owned credentials, with row-bound AAD, a versioned enc:v1: envelope, and an idempotent startup backfill. - internal/secret: cipher + RowAAD/SettingsAAD + the startup backfill engine. - SECRET_KEY required at bootstrap; cipher threaded as an explicit dependency. - server_settings: EncryptedSettingsRepo decorator over the audited SensitiveSettingKeys (also drives admin redaction); the config watcher and watch-sync settings reads decrypt too. - Arr keys inline-encrypted; the ambiguous SecretResolver indirection removed from requests/autoscan. - Per-table columns encrypted: subtitles, watch-sync, webhook-sync (not webhook_secret), history-import, and the jellycompat session's bridged Silo access/refresh tokens. - Startup backfill (resolve-then-encrypt for arr refs) is best-effort and primary-node gated. Equality-looked-up secrets and plugin_runtime_configs.config_value are out of scope (need hashing / cross-repo design) — see docs/architecture/secret-encryption.md. Refs #45 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(compose): require SECRET_KEY in docker-compose The server now fatals without SECRET_KEY, so the integrated service (and the commented distributed proxy/transcode examples) pass it through with a fail-fast guard matching the existing MEDIA_ROOT pattern. Distributed worker nodes must use the SAME key as the primary to decrypt shared data. Generate with: openssl rand -base64 48. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): encrypt history import session credentials --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
125 lines
4.7 KiB
YAML
125 lines
4.7 KiB
YAML
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg18
|
|
restart: unless-stopped
|
|
shm_size: ${POSTGRES_SHM_SIZE:-8gb}
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-silo}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-silo}
|
|
POSTGRES_DB: ${POSTGRES_DB:-silo}
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/postgres:/var/lib/postgresql
|
|
command: ["postgres", "-c", "listen_addresses=*"]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-silo}"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 5
|
|
|
|
redis:
|
|
image: redis:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/redis:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 5
|
|
|
|
silo:
|
|
image: ${SILO_IMAGE:-ghcr.io/silo-server/silo-server:latest}
|
|
restart: unless-stopped
|
|
environment:
|
|
MODE: integrated
|
|
# Master key for at-rest credential encryption. Required — the server
|
|
# refuses to start without it. Generate with: openssl rand -base64 48
|
|
# Back it up SEPARATELY from your database dumps; losing it makes encrypted
|
|
# secrets unrecoverable. See docs/architecture/secret-encryption.md.
|
|
SECRET_KEY: ${SECRET_KEY:?Set SECRET_KEY in .env — generate one with openssl rand -base64 48}
|
|
DATABASE_URL: postgres://${POSTGRES_USER:-silo}:${POSTGRES_PASSWORD:-silo}@postgres:5432/${POSTGRES_DB:-silo}?sslmode=disable
|
|
REDIS_URL: redis://redis:6379
|
|
SILO_PLUGIN_CACHE_DIR: /var/lib/silo/plugins
|
|
POSTGRES_TUNE: ${POSTGRES_TUNE:-auto}
|
|
ports:
|
|
- "${PORT:-8090}:8080"
|
|
- "${JF_PORT:-8096}:8096"
|
|
- "${ABS_PORT:-13378}:13378"
|
|
volumes:
|
|
- ${MEDIA_ROOT:?Set MEDIA_ROOT in .env to the host media path}:${MEDIA_CONTAINER_ROOT:-/mnt/media}:ro
|
|
- ${MEDIA_BOOKS_ROOT:-${MEDIA_ROOT}}:${MEDIA_BOOKS_CONTAINER_ROOT:-${MEDIA_CONTAINER_ROOT:-/mnt/media}/books}:ro
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/plugins:/var/lib/silo/plugins
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/transcode:/tmp/silo-transcode
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/catalog-seeds:/catalog-seeds:ro
|
|
- ${SILO_DATA_ROOT:-/opt/silo}/audiobook-covers:/var/lib/silo/audiobook-covers
|
|
- /proc/meminfo:/host/proc/meminfo:ro
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
env_file:
|
|
- path: .env
|
|
required: false
|
|
devices:
|
|
- /dev/dri:/dev/dri
|
|
|
|
# Optional distributed-mode example. Most single-host installs should
|
|
# just use the integrated service above.
|
|
# silo-proxy:
|
|
# image: ${SILO_IMAGE:-ghcr.io/silo-server/silo-server:latest}
|
|
# restart: unless-stopped
|
|
# profiles: [proxy]
|
|
# environment:
|
|
# MODE: proxy
|
|
# # Must be the SAME SECRET_KEY as the primary node (shared encrypted data).
|
|
# SECRET_KEY: ${SECRET_KEY:?Set SECRET_KEY in .env — generate one with openssl rand -base64 48}
|
|
# DATABASE_URL: postgres://${POSTGRES_USER:-silo}:${POSTGRES_PASSWORD:-silo}@postgres:5432/${POSTGRES_DB:-silo}?sslmode=disable
|
|
# REDIS_URL: redis://redis:6379
|
|
# PORT: "8080"
|
|
# ports:
|
|
# - "${PROXY_PORT:-8083}:8080"
|
|
# volumes:
|
|
# - ${MEDIA_ROOT:?Set MEDIA_ROOT in .env to the host media path}:${MEDIA_CONTAINER_ROOT:-/mnt/media}:ro
|
|
# depends_on:
|
|
# postgres:
|
|
# condition: service_healthy
|
|
# redis:
|
|
# condition: service_healthy
|
|
# env_file:
|
|
# - path: .env
|
|
# required: false
|
|
|
|
# Optional distributed-mode example. Most single-host installs should
|
|
# just use the integrated service above.
|
|
# silo-transcode:
|
|
# image: ${SILO_IMAGE:-ghcr.io/silo-server/silo-server:latest}
|
|
# restart: unless-stopped
|
|
# profiles: [transcode]
|
|
# environment:
|
|
# MODE: transcode
|
|
# # Must be the SAME SECRET_KEY as the primary node (shared encrypted data).
|
|
# SECRET_KEY: ${SECRET_KEY:?Set SECRET_KEY in .env — generate one with openssl rand -base64 48}
|
|
# DATABASE_URL: postgres://${POSTGRES_USER:-silo}:${POSTGRES_PASSWORD:-silo}@postgres:5432/${POSTGRES_DB:-silo}?sslmode=disable
|
|
# REDIS_URL: redis://redis:6379
|
|
# SILO_PLUGIN_CACHE_DIR: /var/lib/silo/plugins
|
|
# PORT: "8080"
|
|
# ports:
|
|
# - "${TRANSCODE_PORT:-8082}:8080"
|
|
# volumes:
|
|
# - ${MEDIA_ROOT:?Set MEDIA_ROOT in .env to the host media path}:${MEDIA_CONTAINER_ROOT:-/mnt/media}:ro
|
|
# - ${SILO_DATA_ROOT:-/opt/silo}/plugins:/var/lib/silo/plugins
|
|
# - ${SILO_DATA_ROOT:-/opt/silo}/transcode:/tmp/silo-transcode
|
|
# depends_on:
|
|
# postgres:
|
|
# condition: service_healthy
|
|
# redis:
|
|
# condition: service_healthy
|
|
# env_file:
|
|
# - path: .env
|
|
# required: false
|