Skip to content

fix(k8s): harden securityContext + add postgres PVC (unblocks repo-wide Trivy gate)#923

Open
seonghobae wants to merge 2 commits into
developfrom
fix/k8s-securitycontext-hardening-and-db-pvc
Open

fix(k8s): harden securityContext + add postgres PVC (unblocks repo-wide Trivy gate)#923
seonghobae wants to merge 2 commits into
developfrom
fix/k8s-securitycontext-hardening-and-db-pvc

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Root cause

trivy-fs fails on every open PR (verified on #919, #920, and my own #921/#922) — not because of any PR's diff, but because the k8s manifests trip Trivy misconfig rules on all three workloads:

  • KSV-0014 (HIGH) — containers should set readOnlyRootFilesystem: true
  • KSV-0118 (HIGH ×2) — container/workload using the default security context (allows root privileges)

opencode-review then fails only downstream of this (it REQUEST_CHANGES when another check fails, then can't diagnose it). So this one baseline is what blocks the security gate across the repo.

Fix

Harden backend-deployment, frontend-deployment, and db-statefulset:

  • securityContext (pod + container): runAsNonRoot, capabilities.drop: [ALL], allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, seccompProfile: RuntimeDefault. App images already run non-root (Dockerfile USER appuser, frontend/Dockerfile USER node); postgres runs as uid/gid 999 with fsGroup: 999.
  • Writable mounts required by readOnlyRootFilesystem: /tmp for backend (email import extracts .zip/.mbox via tempfile), /app/.next/cache + /tmp for the frontend (next start runtime caches), postgres socket dir + /tmp.
  • Postgres PVC: added volumeClaimTemplates. Without it the DB lived on the pod's ephemeral filesystem and was lost on every restart/reschedule (separately flagged P0 in docs/assessment/cloud-native-deployability-assessment.md). PGDATA moved to a subdir of the mounted volume.

Verification (same scanners CI uses, run locally)

trivy config --severity HIGH,CRITICAL k8s/                            -> 0 findings
trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL .  -> exit 0

(Before: 3 HIGH per workload = 9 findings, exit 1.)

Deployer note (please smoke-test before rollout)

These are deploy templates (REPLACE_ME_VERSION image placeholders), not live manifests, so there's no running infra to break. Two things warrant a cluster smoke-test before production rollout: (1) postgres non-root + PVC ownership via fsGroup, and (2) the frontend readOnlyRootFilesystem with the .next/cache emptyDir mount. Everything is Trivy-green and reasoned for runtime-safety, but a live kubectl rollout status is the right final check.

🤖 Generated with Claude Code

seonghobae and others added 2 commits July 5, 2026 23:18
…ate)

The Trivy filesystem scan (trivy-fs) fails on EVERY PR because the k8s
manifests trip misconfig rules KSV-0014 (readOnlyRootFilesystem) and
KSV-0118 (default security context) on all three workloads — a repo-wide
baseline failure unrelated to individual PRs.

Harden all three:
- Pod/container securityContext: runAsNonRoot, drop ALL capabilities,
  allowPrivilegeEscalation=false, readOnlyRootFilesystem, seccomp
  RuntimeDefault. App images already run non-root (Dockerfile USER appuser /
  frontend USER node); postgres runs as uid/gid 999 with fsGroup.
- Add writable emptyDir mounts required by readOnlyRootFilesystem: /tmp for
  backend (email import extracts archives via tempfile), /app/.next/cache +
  /tmp for the frontend (next start runtime caches), and the postgres socket
  dir + /tmp.
- Add volumeClaimTemplates to the postgres StatefulSet. Without it the
  database lived on the pod's ephemeral filesystem and was lost on every
  restart/reschedule (separately flagged P0 in the cloud-native assessment).
  PGDATA moved to a subdir of the mounted volume.

Verified locally with the same scanners CI uses:
  trivy config --severity HIGH,CRITICAL k8s/                       -> 0
  trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL . -> exit 0

Runtime note for the deployer: the postgres non-root + PVC (fsGroup) and the
frontend readOnlyRootFilesystem writable-cache mounts should be smoke-tested
on a cluster before rollout; these manifests use REPLACE_ME_VERSION image
placeholders and are deploy templates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant