Skip to content

feat(chart): PodSecurity 'restricted'-compatible securityContext on workloads#152

Open
kamir wants to merge 2 commits into
KafScale:mainfrom
kamir:pr/psa-securitycontext
Open

feat(chart): PodSecurity 'restricted'-compatible securityContext on workloads#152
kamir wants to merge 2 commits into
KafScale:mainfrom
kamir:pr/psa-securitycontext

Conversation

@kamir

@kamir kamir commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

What

Add PodSecurity restricted-compatible securityContext blocks (pod + container) to every chart-templated Deployment, so the chart deploys into namespaces that enforce the Pod Security Admission restricted profile without per-workload patching. Each Deployment carries:

  • pod level: runAsNonRoot: true, runAsUser/runAsGroup: 10001, seccompProfile: RuntimeDefault
  • container level: allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL]

All values are overridable via values.yaml or --set <component>.podSecurityContext.<key>=<value>.

Why

Lets the chart deploy into restricted PSA namespaces with zero admission warnings and no per-workload patching.

Coverage: all four Deployments

The chart templates four Deployments (operator, proxy, console, mcp). All four are now hardened. The mcp Deployment was previously missing the blocks; this revision adds them (templates/mcp-deployment.yaml, wired to .Values.mcp.podSecurityContext / .Values.mcp.containerSecurityContext). The mcp image bakes USER 10001 (deploy/docker/mcp.Dockerfile), so the restricted defaults match the shipped image.

LFS coexistence: writable /tmp on the proxy

With readOnlyRootFilesystem: true the root filesystem is read-only. The proxy LFS verify path (cmd/proxy/lfs_http.go, os.CreateTemp("", ...)) writes a temp file to /tmp, so it returns HTTP 500 (temp_storage_failed) when KAFSCALE_PROXY_LFS_ENABLED=true. The proxy Deployment now mounts a writable emptyDir at /tmp, so the restricted default and LFS coexist. readOnlyRootFilesystem stays true. The proxy keeps fsGroup (it owns the volume); operator, console, and mcp drop fsGroup since they mount no volumes.

Test: conformance gate

test/chart/psa-restricted_test.sh, wired as make test-chart-psa, follows the existing test/chart pattern (helm only, no plugins, no cluster). It renders every chart Deployment with all workloads enabled and asserts the five restricted controls on each (operator, proxy, console, mcp x runAsNonRoot true, allowPrivilegeEscalation false, capabilities.drop includes ALL, seccompProfile RuntimeDefault, non-root runAsUser). This gate fails automatically if a future Deployment is added without the blocks. Verified: removing the mcp blocks makes the gate fail; restoring makes it pass. helm lint and helm template of all four Deployments are clean.

UID pin and overrides

runAsUser/runAsGroup: 10001 pin the Dockerfile-baked USER 10001 of the shipped images and must change in lockstep with that image USER; a values comment records this. PSA restricted itself only requires runAsNonRoot, not a specific UID. If you override image.repository with a root-running image, set <component>.podSecurityContext.runAsNonRoot=false.

Scope

The brokers and etcd are operator-reconciled (label app=kafscale-broker), not chart-templated, so they are intentionally out of scope for this chart-level change and tracked separately. Default render is unchanged in shape; the hardening defaults are safe for the shipped images and fully overridable.

Part of a small series upstreaming deployment-hardening deltas we currently carry. Chart 0.4.1 -> 0.4.2.

kamir and others added 2 commits June 4, 2026 19:37
PLAN-06 iter-7 E-10 / BUG-0009. The operator, console, and proxy
container images already run as USER 10001 (Dockerfile-baked, every
*.Dockerfile under deploy/docker/) but the chart templates did not
declare the four PSA-restricted requirements, so deployments warned
under `pod-security.kubernetes.io/audit: restricted`.

Templates extended:
  * operator-deployment.yaml: pod-level + container-level
    securityContext blocks wired to .Values.operator.podSecurityContext
    and .Values.operator.containerSecurityContext.
  * console-deployment.yaml: same shape under .Values.console.*.
  * proxy-deployment.yaml: same shape under .Values.proxy.*.

values.yaml defaults (all three components):
  * podSecurityContext: runAsNonRoot=true, runAsUser=10001,
    runAsGroup=10001, fsGroup=10001, seccompProfile=RuntimeDefault.
  * containerSecurityContext: allowPrivilegeEscalation=false,
    readOnlyRootFilesystem=true, capabilities.drop=[ALL].

Result, verified against the bp-001 umbrella with helm template:
admission emits the operator + console + proxy pod specs cleanly
under PSA `restricted` (zero warnings). The brokers themselves are
operator-reconciled, not chart-templated; broker hardening is a
follow-up that touches the operator binary, not this chart.

Chart bump 0.4.0 -> 0.4.1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…FS, conformance gate

Extend the PSA-restricted securityContext work so the chart is uniformly
hardened and the posture cannot regress unnoticed.

mcp-deployment.yaml: add the same pod + container securityContext blocks
the operator, console, and proxy already carry, wired to
.Values.mcp.podSecurityContext / .Values.mcp.containerSecurityContext.
The mcp image bakes USER 10001 (deploy/docker/mcp.Dockerfile), so the
restricted defaults are correct for the shipped image. This closes the
fourth-Deployment gap: before this, three of four chart Deployments were
hardened.

proxy-deployment.yaml: mount a writable emptyDir at /tmp. The LFS verify
path (cmd/proxy/lfs_http.go, os.CreateTemp("", ...)) writes to /tmp; with
readOnlyRootFilesystem: true the root FS is read-only, so the verify
returns HTTP 500 when KAFSCALE_PROXY_LFS_ENABLED=true. The emptyDir lets
the restricted default and LFS coexist. proxy keeps fsGroup (it owns the
volume); operator/console/mcp drop fsGroup since they mount no volumes.

values.yaml: add the mcp securityContext defaults; remove dead fsGroup on
operator/console; note that runAsUser/runAsGroup pin the Dockerfile-baked
USER 10001 and must change in lockstep with it; document the override path
for root-running custom images (set <component>.podSecurityContext.runAsNonRoot=false).

test/chart/psa-restricted_test.sh + make test-chart-psa: a helm-only
conformance gate (no plugins, no cluster, follows the test/chart pattern)
that renders every chart Deployment with all workloads enabled and asserts
the five restricted controls on each: runAsNonRoot true,
allowPrivilegeEscalation false, capabilities.drop includes ALL,
seccompProfile RuntimeDefault, and a non-root runAsUser. It fails if a new
Deployment is added without the blocks (verified: removing the mcp blocks
makes it fail; restoring makes it pass).

Chart 0.4.1 -> 0.4.2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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