Repository navigation
Conversation
Running Postgres clusters under the restricted Pod Security Standard requires settings the operator could not configure. Add operator options that are independent of the configured user ID: - spilo_runasnonroot: sets runAsNonRoot in the pod security context - spilo_seccomp_profile: sets the seccomp profile in the pod security context (RuntimeDefault, Unconfined or Localhost/<profile>) - drop_pod_capabilities: capabilities dropped from the containers Pod level settings apply to all containers. Dropped capabilities and a disabled spilo_allow_privilege_escalation are filled into the Spilo, sidecar, init and logical backup containers, unless a container defines them itself or runs privileged. The cluster manifest and operator configuration are not modified. The new options are unset by default, so upgrading the operator does not change pod templates, except for setups that already disable spilo_allow_privilege_escalation: their sidecars and init containers now get allowPrivilegeEscalation: false as well.
yyvess
requested review from
FxKu,
Jan-M,
hughcapet,
idanovinda,
jopadi and
mikkeloscar
as code owners
November 7, 2026 06:21
Replace the validation regexp and the pod template conversion with one config.SeccompProfile function used by both, so the accepted values are defined in one place and an invalid value can no longer be turned into an arbitrary profile type. Generating the pod template fails on an invalid value instead.
5 tasks done
Align the option name with the one proposed in zalando#3209. runAsNonRoot is now also filled into the Spilo, sidecar, init and logical backup containers, unless they define it themselves or run privileged. Admission webhooks enforcing runAsNonRoot (e.g. Kyverno) inject it on container level too; with only the pod field set, the generated StatefulSet kept differing from the mutated one and the operator rolled the cluster on every sync (zalando#3210).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem description
Postgres clusters cannot run in namespaces enforcing the Pod Security Standards
restrictedprofile (Pod Security Admission, Kyverno, Gatekeeper, Azure Policy), even when Spilo already runs as a non-root user viaspilo_runasuser. The profile requires fields the operator cannot emit:restrictedrunAsNonRoot: trueseccompProfile.type: RuntimeDefault(orLocalhost)capabilities.drop: ["ALL"]additional_pod_capabilitiesto add)allowPrivilegeEscalation: falseThe usual workaround is a mutating webhook, which makes the generated StatefulSet differ from the live one and causes a rolling update with a switchover on every sync (#1879, #3210).
Changes
New operator options (ConfigMap and
OperatorConfiguration,kubernetessection), independent ofspilo_runasuser:spilo_run_as_non_rootrunAsNonRootin the pod security context, and in the container security contexts (see below)spilo_seccomp_profileRuntimeDefault,UnconfinedorLocalhost/<profile>(validated by the CRD pattern and the ConfigMap validation)drop_pod_capabilitiescapabilities.dropof the containers, e.g.ALLspilo_allow_privilege_escalation(existing)false, now also applied to sidecars, init containers and the logical backup containertrueHow they are applied:
runAsNonRoot, seccomp): applies to all containers of the Spilo and logical backup pods, including init containers; a container can still override it in its own security context.allowPrivilegeEscalation: false,runAsNonRoot): filled into the Spilo, sidecar, init and logical backup containers only where the container does not define the field itself. Privileged containers are left untouched, andallowPrivilegeEscalationis not disabled for containers addingSYS_ADMIN(rejected by the API server).runAsNonRootis set on both levels on purpose: webhooks enforcing it (e.g. Kyverno, Rolling update loop on every resync when a mutating admission webhook injects security context fields (e.g. runAsNonRoot) #3210) inject it into the containers too, so with only the pod field the generated StatefulSet would keep differing from the mutated one and the operator would roll the cluster on every sync.spilo_run_as_non_rootuses the name proposed in #3209.drop_pod_capabilitiesmirrorsadditional_pod_capabilities; happy to rename it todropped_pod_capabilitiesas proposed in #2993 / #2244 if preferred.All options are operator-wide, like
spilo_privileged,spilo_allow_privilege_escalationandadditional_pod_capabilities; onlyspiloRunAsUser/spiloRunAsGroup/spiloFSGroupexist per cluster. Withspilo_run_as_non_rootenabled, a cluster manifest overridingspiloRunAsUserwith0fails to start its pods (documented). Per-cluster overrides of the security options (#3158, thespiloRunAsNonRootfield of #3209) are left to a follow-up covering all of them consistently.Docs: parameter reference, a new "Pod Security Standards" section in
administrator.mdwith arestrictedrecipe, chartvalues.yamland sample manifests.Upgrade impact
All new options are unset by default, so upgrading the operator does not change any pod template. Exception: setups that already set
spilo_allow_privilege_escalation: falsegetallowPrivilegeEscalation: falseon their sidecars and init containers too, which triggers one rolling update.Cron, backups and certificate rotation without privilege escalation
The docs claimed
spilo_allow_privilege_escalationis required by cron (#1730). This only holds when Spilo runs as root: as non-root, Spilo starts cron withLD_PRELOAD=cron_unprivileged.so(no-opseteuid/initgroups), so no setuid is needed. Verified withghcr.io/zalando/spilo-18:4.1-p2running as101:103withno-new-privileges, all capabilities dropped (CapBnd: 0) and the default seccomp profile:crontab -u postgres -installs the crontab and cron executes the jobs. Only Spilo's optionalrenicejob is skipped (Spilo logs it). Note thatspilo_runasgroupmust be103: with thecrontabgroup (101) cron silently fails to run jobs. Thespilo_allow_privilege_escalationdocs were updated accordingly.Linked issues
Fixes #2081 —
runAsNonRooton the Postgres StatefulSetFixes #2291 —
allowPrivilegeEscalation: falsefor the exporter sidecar (withspilo_allow_privilege_escalation: false)Fixes #3210 — rolling update loop when a webhook injects
runAsNonRoot(configure the same value withspilo_run_as_non_root; webhooks injecting fields the operator has no option for, e.g. sysctls #3095, still diverge)Addressed for Spilo and logical backup pods:
Partially addressed:
Not covered: connection pooler (#1722, see #3105), operator UI (#2682), per-cluster overrides in the
postgresqlmanifest.Related pull requests
spilo_runasuser, not configurable; the operator Deployment hardening from that PR can be submitted separately)spilo_run_as_non_rooton pod and Spilo container level): same option name, applied to all containers including sidecars and init containers; its per-clusterspiloRunAsNonRootfield is not included (see above)dropped_pod_capabilities), Custom SecurityContext in Postgresql, OperatorConfiguration CRDs #2244 (seccomp profile, dropped capabilities, sidecar security context)pod_sysctls)allowPrivilegeEscalation), configurable container capabilities #1336, Fix empty capabilities #1380 (configurable capabilities), Set user and group in security context #1083, Fix operator configs runasuser and runasgroup #1361, StatefulSet fsGroup config option to allow non-root spilo #531 (runAsUser/runAsGroup/fsGroup), Set securityContext for backup container #2117 (logical backup security context)Checklist
runAsNonRooton pod and container level; container-defined fields win; privileged andSYS_ADMINcontainers; cluster manifest and operator configuration not mutated; ConfigMap parsing and seccomp validation (invalid values fail validation and StatefulSet generation)🤖 Generated with Claude Code