Repository navigation
feat(security): graduate zeroed Kubescape controls to Kyverno Enforce so they cannot regressΒ #2855
Description
π€ Generated by the Agentic Engineer
Part of #2451 (its third slice). Depends on nothing in #2854 β the two can land independently.
Evidence
The CI posture gate is a regression floor on an aggregate score, deliberately set a few points under the observed value so it does not flake on run-to-run variance. That design is right for an aggregate, but it has a consequence: an individual control that has been driven to zero findings can regress back without crossing the aggregate floor, and nothing fails.
The cluster already runs both halves needed to close that: ClusterSecurityException CRs are the single source of truth for justified exceptions, and Kyverno is deployed and already enforcing policy at admission.
Problem
A control fixed at the manifest level stays fixed only as long as nobody reintroduces the pattern. Static scanning reports it after the fact; admission control prevents it. Today a fixed control has no ratchet, so the work of driving it to zero is not durable.
Proposed direction
For each control genuinely driven to zero, add the equivalent Kyverno policy and graduate it to Enforce, so the pattern is refused at admission rather than reported afterwards. Per the fix-vs-except ladder, this is the "runtime-enforce what a static scan cannot see" rung β and it is what makes the earlier fix permanent.
Practical sequencing matters more than coverage here:
- graduate one control at a time,
Auditfirst, and read real admission decisions before flipping toEnforce - a control still producing findings is not a candidate β graduating it would block legitimate deploys
- prefer the shared
devantler-tech/kyverno-policieslibrary over a repo-local copy where the rule is generic, so every consuming platform inherits the guardrail - each graduation states, in its PR, both what the security floor gained and what the everyday deploy path cost β a guardrail that makes routine deploys harder is not ready
Success signal
Baseline: zero controls currently ratcheted, so any fixed control can silently regress. Target: each graduated control refuses its pattern at admission, evidenced by an admission decision in a test namespace rather than by the policy merely existing. Guardrail: no legitimate existing workload is blocked β measured in Audit before any flip.
Acceptance criteria
- At least one zeroed control is graduated end-to-end, with an observed admission refusal proving it actually fires (not just that the policy is installed).
Audit-mode evidence is recorded before eachEnforceflip, showing no legitimate workload would be blocked.- Generic rules land in the shared policy library rather than being vendored here.
- The CI aggregate floor is ratcheted up, never down, as controls graduate.
Rough size: M, and naturally incremental β one control per PR.
Admission-readiness gate β 2026-10-05
The obsolete scanner blocker #2933 is closed. Fresh posture coverage is 102/102 result-bearing workload objects, but that denominator proves collection only, not that any individual control has zero findings. Select a candidate from per-control GET results after applicable exceptions, record legitimate workloads in Audit, and pair a positive admission with an observed refusal of the offending pattern before promotion. The broad admission objective stays open; do not flip all rules to Enforce or infer readiness from LIST payloads with stripped controls.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Start date
Target date
Effort
Projects
- StatusShow more project fieldsπ§ Icebox
Activity
coderabbitai commentedon Jul 29, 2026
π Related PRs
#2370 - fix(autoscaling): break the DaemonSet-VPA node ratchet; keep Longhorn storage off autoscale nodes [merged]
#2434 - fix(kubescape): except GitOps-managed CronJobs from C-0026 posture control [merged]
#2580 - fix(policy): refresh replica-floor exemptions as skips [merged]
#2830 - feat(ci): publish Kubescape posture findings to Code Scanning [merged]
#2837 - fix(kubescape): stop the Headlamp mirror granting exceptions the CRs never granted [merged]
π Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.π§ͺ Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:π¬ Have feedback or questions? Drop into our discord!
devantler commentedon Aug 3, 2026
Blocked β the "zeroed control" premise cannot currently be satisfied
I picked this up as the oldest actionable Security work and started with the step the acceptance
criteria require first: identify a control that genuinely has zero findings. It turns out no such
list can be produced right now, for two independent reasons β either one alone is disqualifying.
1. The scanner is dead. The in-cluster configuration scan has aborted on every run since
~2026-08-01 (framework artifacts cannot be loaded), so it is evaluating zero controls. Every
control therefore reads as zeroed. That is the classic trap this kind of work has to defend against:
a broken scanner and a clean cluster are indistinguishable from the data alone. Filed as #2933 and
linked as a blocker.
2. A LIST read is spec-stripped, independently of the outage. Deriving control state from
get <kind> -A -o jsonreturns objects with the spec stripped β one workload reads all-zero across0 controls that way, and shows real per-severity counts across 60 controls when fetched directly by
name. So even with a healthy scanner, the obvious way to build this candidate list would have
reported every control as a false candidate. Worth pinning in whatever tooling eventually
generates the list.
Had I built the graduation set today, both faults point the same direction: toward graduating
controls that were never evaluated, to
Enforce, in a cluster's admission path. The premise issound; it just cannot be evidenced yet.
Nothing about the issue's design changes β one control at a time,
Auditfirst, generic rulesinto the shared library, and an observed admission refusal rather than a policy that merely exists.
Those all still hold. This is a sequencing blocker, not a rethink.
One thing to carry forward when it unblocks:
spec.controls[].nameisnullin the stored objects,so the cluster gives control IDs only and names must come from the framework definition.