Data plane hardening (Postgres TLS, Redis, backups)

Audience: DevOps / platform engineers Time: ~25 min Prerequisites: Custom deployment (Kubernetes & Helm)

This guide documents implemented Control Plane and Helm defaults for data-in-transit hardening and logical database backups. Encryption at rest is customer-managed (cloud Postgres TDE, encrypted volumes).

Postgres TLS (CC6.7)

When the Control Plane API runs in a production-like environment (ENVIRONMENT=production or prod, and DEMO_MODE is not set), it enforces TLS on every startup:

  • sslmode=verify-full is injected when no mode is set, or when a weak mode (disable, allow, prefer) is present.
  • DB_SSL_MODE overrides the default (e.g. require for environments where the CA is managed elsewhere).
  • DB_SSL_ROOT_CERT pins the CA certificate path for server verification.
  • Escape hatch: DB_TLS_ENFORCE=false logs a warning and skips enforcement — use only when TLS terminates upstream (load balancer / PgBouncer with TLS passthrough).

Environment variables (Postgres TLS)

VariablePurposeDefault
DB_SSL_MODEPostgres sslmode overrideverify-full
DB_SSL_ROOT_CERTPath to the CA certificate for server verificationunset
DB_TLS_ENFORCESet false to skip enforcement (logged — not recommended)true

Helm: external managed Postgres (RDS / Azure Flexible Server)

database:
  enabled: false          # Disable in-cluster Postgres; use managed service
  external:
    enabled: true
    url: "postgresql://user:pass@host:5432/controlcore?sslmode=verify-full&sslrootcert=/etc/ssl/certs/rds-ca.pem"

Mount the CA certificate in the Control Plane pod and reference it via DB_SSL_ROOT_CERT.

Troubleshooting: SSL connection has been closed unexpectedly — verify the server cert, the CA in DB_SSL_ROOT_CERT, and that the hostname matches the certificate SAN. Run openssl s_client -connect HOST:5432 -starttls postgres to test the TLS handshake independently.

Helm: in-cluster Postgres with TLS

tls:
  enabled: true
  secretName: controlcore-postgres-tls   # kubernetes.io/tls Secret
database:
  external:
    enabled: false

Redis TLS + AUTH (CC6.7)

Production requires both TLS and a password:

  • REDIS_URL must use rediss:// (TLS) and include a password, or REDIS_PASSWORD must be set separately.
  • REDIS_TLS_ENFORCE=false disables enforcement (logged — operator-acknowledged escape hatch).

Environment variables (Redis TLS)

VariablePurposeDefault
REDIS_TLS_ENFORCESet false to skip enforcement (logged)true
REDIS_SSL_CA_CERTPath to the CA certificate for Redis TLSunset
REDIS_SSL_CERT_REQSTLS certificate verification level (required / optional / none)required
REDIS_PASSWORDRedis password (alternative to embedding in REDIS_URL)unset

Helm: external managed Redis (ElastiCache / Azure Cache)

redis:
  enabled: false
  external:
    enabled: true
    url: "rediss://:PASSWORD@redis.example:6380/0"
  auth:
    existingSecret: controlcore-redis-auth
    existingSecretPasswordKey: password

Helm: in-cluster Redis with TLS + AUTH

redis:
  auth:
    enabled: true
    existingSecret: controlcore-redis-auth
tls:
  enabled: true
  secretName: controlcore-redis-tls

Troubleshooting: Connection refused on rediss:// — confirm the Redis server is configured for TLS and presents a certificate trusted by the client. WRONGPASS — verify the password in the Kubernetes Secret matches the Redis requirepass value.

Logical backup CronJob (CC7.5)

An opt-in Helm CronJob runs pg_dump on a schedule and prunes old archives. This is not a replacement for cloud-managed PITR (RDS automated backups, Azure Flexible Server point-in-time restore).

database:
  backup:
    enabled: true
    schedule: "0 */6 * * *"     # Every 6 hours
    retentionDays: 14            # Prune dumps older than 14 days
    storage:
      existingClaim: controlcore-pg-backup   # Durable ReadWriteOnce PVC
      size: 20Gi

The CronJob uses TLS when tls.enabled: true. It runs as a non-privileged pod (PSA restricted). Credentials are read from secretKeyRef.

Verify the most recent backup ran successfully:

kubectl get jobs -n controlcore -l app.kubernetes.io/component=postgres-backup
kubectl logs -n controlcore -l job-name=<LATEST_JOB> --tail=20

Troubleshooting: Job Completed but backup PVC is empty — set storage.existingClaim to a ReadWriteOnce PVC that persists across nodes. Default emptyDir is ephemeral and not durable.

Customer-managed encryption at rest

LayerControl Core chartYour responsibility
Postgres dataPVC or external URLEnable provider TDE / encrypted EBS / CSI volume encryption
RedisOptional persistence PVCEncrypt volume or use managed Redis (encrypted at rest by default on ElastiCache / Azure Cache)
Application secretssecretKeyRef / ExternalSecretsKMS/Vault key rotation per your organization's policy

SOC 2 reconciliation

TSCStatus in productEvidence you collect
CC6.7Partial — Control Plane API TLS enforcement + Helm server TLSTLS handshake test (openssl s_client), connection string audit, Postgres host-based auth review
CC7.5Partial — Helm backup CronJob templateCronJob success logs, restore drill RTO/RPO measurement

See also Supply chain verification and Shared responsibility (enterprise).

Next steps