Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
- 'go.mod'
- 'go.sum'
- 'scripts/tests/test-cilium-bandwidth-manager-component.sh'
- 'tests/cilium-bandwidth-manager-bbr/**'
- 'tests/**'
- 'scripts/refresh-flux-ghcr-auth.sh'
- 'scripts/ghcr-auth-lib.sh'
- 'scripts/run-ksail-prod-with-pull-auth.sh'
Expand Down Expand Up @@ -131,6 +131,23 @@ jobs:
# scripts/validate-embedded-json.py (#2480).
run: python3 scripts/validate-embedded-json.py

- name: ⚖️ Install Kyverno CLI
if: needs.changes.outputs.k8s == 'true'
# Pinned to the version the cluster actually runs (the kyverno Helm
# release is v1.18.2), so a policy that passes here is evaluated by the
# same engine that admits it in production. Bump both together.
uses: kyverno/action-install-cli@fcee92fca5c883169ef9927acf543e0b5fc58289 # v0.2.0
with:
release: v1.18.2

- name: ⚖️ Validate cluster policies
if: needs.changes.outputs.k8s == 'true'
# Runs the kyverno test fixtures under tests/. Until now nothing
# executed them, so tests/validate-replica-floor had never actually run
# in CI — a policy regression could ship green. No cluster or secrets
# required; the CLI evaluates the committed ClusterPolicy files offline.
run: kyverno test ./tests

- name: 🚦 Validate default-off Cilium bandwidth manager
if: needs.changes.outputs.k8s == 'true'
# Render the committed and temporary opt-in states so the staged
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Validates that pods in non-infrastructure namespaces do not use
# host-level access (hostPath, hostNetwork, hostPID, hostIPC).
# host-level access (hostPath, hostNetwork, hostPID, hostIPC), and that pods
# in user-namespace-enabled namespaces do not opt back out (hostUsers).
#
# Addresses kubescape controls: C-0045, C-0048, C-0041, C-0038
#
Expand Down Expand Up @@ -93,3 +94,49 @@ spec:
=(hostNetwork): false
=(hostPID): false
=(hostIPC): false
# The user-namespace rollout (#1807) is delivered by the add-user-namespaces
# MUTATE rule in add-security-context.yaml, which injects hostUsers: false
# through a +(hostUsers) conditional anchor. A conditional anchor only fills
# in an ABSENT value, so a workload that explicitly sets hostUsers: true
# keeps it — silently, with no admission error and no policy report, in a
# namespace that still carries the opt-in label and still looks hardened.
# This rule closes that gap: it is the enforce ratchet for the same control.
#
# Scope mirrors the mutate rule exactly, on purpose:
# - the same namespaceSelector, so only namespaces that have graduated
# through a userns pilot are held to it;
# - the same cnpg.io/cluster exclude, so the validate rule can never block a
# workload the mutate rule deliberately skipped. CNPG pods stay on host
# user namespaces pending their own per-app pilot, and leave hostUsers
# absent; excluding them keeps that true even if CNPG later sets the field
# explicitly.
#
# =(hostUsers) is validate-if-present, matching the hostNetwork/hostPID/
# hostIPC idiom above: an absent value passes (and the mutate rule supplies
# the default), while an explicit true is rejected.
- name: disallow-host-users
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
pod-security.devantler.tech/user-namespaces: enabled
exclude:
any:
- resources:
kinds:
- Pod
selector:
matchExpressions:
- key: cnpg.io/cluster
operator: Exists
validate:
message: >-
hostUsers: true is not allowed in a namespace opted into user
namespaces. Remove the field to inherit hostUsers: false, or drop the
pod-security.devantler.tech/user-namespaces label from the namespace.
pattern:
spec:
=(hostUsers): false
37 changes: 37 additions & 0 deletions tests/validate-host-restrictions/kyverno-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
name: validate-host-restrictions-host-users
policies:
- >-
../../k8s/bases/infrastructure/cluster-policies/best-practices/validate-host-restrictions.yaml
resources:
- resources.yaml
# Declares the namespace labels the namespaceSelector matches on. Without it the
# CLI reports every resource "Excluded" and the fixture passes vacuously.
variables: values.yaml
results:
# The regression the mutate rule's conditional anchor cannot catch.
- policy: validate-host-restrictions
rule: disallow-host-users
resources:
- userns-enabled/explicit-host-users
kind: Pod
result: fail
# Absent inherits the mutated default; explicit false is already correct.
- policy: validate-host-restrictions
rule: disallow-host-users
resources:
- userns-enabled/explicit-unshared
- userns-enabled/host-users-absent
kind: Pod
result: pass
# CNPG pods are excluded, and namespaces without the opt-in label are out of scope.
- policy: validate-host-restrictions
rule: disallow-host-users
resources:
- userns-enabled/cnpg-instance
- userns-absent/unscoped-host-users
kind: Pod
result: skip
77 changes: 77 additions & 0 deletions tests/validate-host-restrictions/resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
# Namespace opted into user namespaces — the disallow-host-users rule applies here.
apiVersion: v1
kind: Namespace
metadata:
name: userns-enabled
labels:
pod-security.devantler.tech/user-namespaces: enabled
---
# Namespace that has NOT graduated through a userns pilot — the rule must not apply.
apiVersion: v1
kind: Namespace
metadata:
name: userns-absent
---
# Explicit opt-out inside an opted-in namespace. This is the regression the
# +(hostUsers) conditional anchor in add-security-context.yaml cannot catch.
apiVersion: v1
kind: Pod
metadata:
name: explicit-host-users
namespace: userns-enabled
spec:
hostUsers: true
containers:
- name: app
image: registry.k8s.io/pause:3.10
---
# Explicit hostUsers: false — the value the mutate rule injects.
apiVersion: v1
kind: Pod
metadata:
name: explicit-unshared
namespace: userns-enabled
spec:
hostUsers: false
containers:
- name: app
image: registry.k8s.io/pause:3.10
---
# hostUsers absent — passes validate-if-present; the mutate rule supplies false.
apiVersion: v1
kind: Pod
metadata:
name: host-users-absent
namespace: userns-enabled
spec:
containers:
- name: app
image: registry.k8s.io/pause:3.10
---
# CNPG-managed pod inside an opted-in namespace. Mirrors the mutate rule's
# exclude, so a mixed namespace (stateless app + CNPG database) stays safe.
apiVersion: v1
kind: Pod
metadata:
name: cnpg-instance
namespace: userns-enabled
labels:
cnpg.io/cluster: demo-db
spec:
hostUsers: true
containers:
- name: postgres
image: registry.k8s.io/pause:3.10
---
# Same explicit opt-out, but in a namespace without the opt-in label: out of scope.
apiVersion: v1
kind: Pod
metadata:
name: unscoped-host-users
namespace: userns-absent
spec:
hostUsers: true
containers:
- name: app
image: registry.k8s.io/pause:3.10
15 changes: 15 additions & 0 deletions tests/validate-host-restrictions/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
# The Kyverno CLI does not read namespace labels from the Namespace objects in
# resources.yaml — a rule matched by namespaceSelector is reported "Excluded"
# for every resource unless the namespace labels are declared here. Without
# this file the whole fixture passes vacuously, including the fail case.
apiVersion: cli.kyverno.io/v1alpha1
kind: Values
metadata:
name: values
namespaceSelector:
- name: userns-enabled
labels:
pod-security.devantler.tech/user-namespaces: enabled
- name: userns-absent
labels: {}
Loading