From 1a9debe894d0d0246bc54de81141f1283f4194c9 Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Wed, 19 Aug 2026 18:45:00 +0200 Subject: [PATCH] fix: the pod hardening stopped postgres, web and the frontend from starting Every stateful component in the chart failed to start. Found by installing the chart into a k3s cluster -- the hardening had only ever been rendered, never run. postgres chown: /var/lib/postgresql/data/pgdata: Operation not permitted frontend chown("/var/cache/nginx/client_temp", 101) failed web chown("/var/lib/nginx/body", 65534) failed All three deliberately start as root, fix ownership on their own runtime directories, and then drop to their own user. capabilities.drop: [ALL] takes CHOWN away along with everything else, so they never reach the point of dropping privileges -- they die in the entrypoint, and the install is left with nothing serving and a database that never comes up. Grant each one exactly what its entrypoint needs (CHOWN, SETUID, SETGID, plus FOWNER and DAC_OVERRIDE for re-owning an existing PGDATA) and keep the blanket drop for everything else. The components that already run as their own user from PID 1 -- task, redis, opa, the collector, init, the assistant -- are unchanged and still drop everything. Also: assistantOllama was listed twice in the same map, and the second entry ({}) silently won, so the Ollama container had no hardening at all while appearing to have it. Now listed once. Verified on a two-node k3s cluster: postgres, web and frontend all reach 1/1 Running with the fix, from CrashLoopBackOff without it. --- values.yaml | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/values.yaml b/values.yaml index 526721d..fd63dd5 100644 --- a/values.yaml +++ b/values.yaml @@ -331,28 +331,51 @@ podSecurityContext: seccompProfile: type: RuntimeDefault securityContext: - web: &hardened - allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] + # Components whose PID 1 is already the unprivileged user, touching + # nothing they do not own. These can drop everything. + # # The task pod runs automation. When task.privileged=true (the podman-in-pod # execution path) the chart skips this block entirely -- the two cannot both # apply, and the privileged flag is the one the operator asked for. - task: *hardened - # nginx binds :80, which needs the one capability back. - frontend: + task: &hardened allowPrivilegeEscalation: false capabilities: drop: ["ALL"] - add: ["NET_BIND_SERVICE"] assistant: *hardened assistantOllama: *hardened - postgres: *hardened redis: *hardened opa: *hardened otelCollector: *hardened init: *hardened - assistantOllama: {} + + # These three start as root on purpose: they fix ownership on their own + # runtime directories and then drop to their own user. Dropping ALL takes + # CHOWN (and the setuid/setgid pair they drop with) away, so they never + # reach the point of dropping privileges at all -- they die at startup: + # + # nginx: chown("/var/lib/nginx/body", 65534) failed (Operation not permitted) + # frontend: chown("/var/cache/nginx/client_temp", 101) failed (Operation not permitted) + # postgres: chown: /var/lib/postgresql/data/pgdata: Operation not permitted + # + # Granting exactly what the entrypoint needs keeps the rest of the drop + # meaningful. This is still far short of running unconfined -- and it is + # strictly better than the "hardened" version that did not run. + # + # NET_BIND_SERVICE: both nginx instances bind :80. + web: &rootStartsThenDrops + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + add: ["CHOWN", "SETUID", "SETGID", "NET_BIND_SERVICE"] + frontend: *rootStartsThenDrops + # The postgres entrypoint also has to read and re-own an existing PGDATA + # from a previous container, hence FOWNER and DAC_OVERRIDE. It binds 5432, + # which is unprivileged, so no NET_BIND_SERVICE. + postgres: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + add: ["CHOWN", "FOWNER", "DAC_OVERRIDE", "SETUID", "SETGID"] # ── Ingress ─────────────────────────────────────────────── ingress: