Skip to content

Guard the user-namespace opt-in against silent regression (enforce ratchet) #2690

Description

@devantler

🤖 Generated by the Daily AI Engineer

Evidence

The user-namespace rollout under #1807 is delivered as a mutate-only control. add-security-context.yaml's add-user-namespaces rule injects hostUsers: false into pods in namespaces carrying pod-security.devantler.tech/user-namespaces: enabled, using a conditional anchor:

mutate:
  patchStrategicMerge:
    spec:
      +(hostUsers): false

+(key) means set only if absent. A workload that explicitly sets hostUsers: true therefore keeps it — silently, with no admission error, no policy report, and no signal anywhere. The namespace still carries the opt-in label and still looks hardened.

A repo-wide search confirms hostUsers appears in exactly two places, both inside that one mutate rule — there is no validate rule anywhere that constrains it:

k8s/bases/infrastructure/cluster-policies/best-practices/add-security-context.yaml:10   (comment)
k8s/bases/infrastructure/cluster-policies/best-practices/add-security-context.yaml:93   (+(hostUsers): false)

This is the "enforce ratchet" already named as the remaining step in the 2026-07-12 per-app audit on #1807 ("Remaining after #2597: the stateful set … then the enforce ratchet").

Live production state (read-only, 2026-07-18) confirms the control is currently effective but unguarded — 5 namespaces opted in, every non-CNPG pod unshared, every CNPG-labelled pod correctly left on host userns by the rule's exclude:

namespace non-CNPG pods hostUsers=false CNPG pods (excluded, host userns)
backstage 1/1 2
crossview 2/2 0
homepage 2/2 0
umami 4/4 3
whoami 1/1 0

No pod in production currently sets hostUsers: true, so the ratchet can be applied at Enforce without blocking any existing workload.

Impact

Defence-in-depth that silently degrades is worse than none — it reports as present while being absent. Today a chart upgrade, a values change, or a new workload can opt itself out of user namespaces in a namespace explicitly designated hardened, and nothing surfaces it. This is the same regression class the platform already guards for hostNetwork / hostPID / hostIPC in validate-host-restrictions.yaml; hostUsers is the one host-namespace dimension left unguarded.

Expected behaviour

In a namespace opted into user namespaces, a pod that explicitly sets hostUsers: true is rejected at admission, with the same idiom the platform already uses for the other host namespaces (=(hostUsers): false — validate-if-present, so an absent value still passes and is filled in by the existing mutate rule).

CNPG-managed pods keep the existing cnpg.io/cluster exclude, mirroring the mutate rule exactly, so the validate rule can never block a workload the mutate rule deliberately skipped.

Acceptance criteria

  • A disallow-host-users rule is added to validate-host-restrictions.yaml, scoped by the pod-security.devantler.tech/user-namespaces: enabled namespace selector and excluding cnpg.io/cluster pods.
  • The rule uses =(hostUsers): false so an absent value passes and an explicit true fails.
  • A kyverno test fixture under tests/ covers fail (explicit hostUsers: true in an opted-in namespace), pass (hostUsers: false, and absent), and skip (non-opted-in namespace; CNPG-labelled pod) — RED/GREEN proven.
  • Verified against live production state that no existing workload would be blocked.

Part of #1807.

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions