Skip to content

fix(kubescape): headlamp-exceptions ConfigMap has drifted from the ClusterSecurityException CRs #2586

Description

@devantler

🤖 Generated by the Daily AI Engineer

Problem

The headlamp-exceptions ConfigMap (k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml) is a hand-maintained JSON mirror of the ClusterSecurityException CRs, and it has drifted badly:

  • 4 CSEs are missing entirely from the mirror: kubescape-privileged, readonly-rootfs-pending, talos-cis-control-plane-false-positives, talos-cis-worker-false-positives.
  • Resource scopes diverge: delete-capable-rbac-by-design has 26 matchers in the CRs but 3 in the mirror; batch-workloads 2 vs 1; pod-security-mutations control counts differ (4 vs 5).

The Headlamp Kubescape plugin therefore shows a different (stale) exception set than the operator and CI actually apply — the UI mis-reports posture.

Proposed direction

Stop hand-maintaining the mirror: scripts/generate-kubescape-exceptions.py (added by #2584) already derives the exact PostureExceptionPolicy JSON from the CSE CRs. Either regenerate the ConfigMap's exceptionPolicies key from that output (with a CI drift check comparing the two), or — better long-term — drop the mirror once the Headlamp plugin can read the CRDs directly (upstream capability to verify). Reason strings may need light copy-editing for UI brevity, which the generator's output can carry as-is.

Rough size

S–M — one script/CI hookup plus a regenerated ConfigMap; the compare-and-fail drift gate is the part worth doing carefully.

Part of #2447.

Activity

  1. coderabbitai commented on Jul 11, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #2316 - fix(kubescape): make ClusterSecurityException posture exceptions take effect [merged]
    #2320 - feat(kubescape): mirror ClusterSecurityException CRs as Headlamp plugin ConfigMap [merged]
    #2434 - fix(kubescape): except GitOps-managed CronJobs from C-0026 posture control [merged]
    #2440 - fix(security): make C-0002 exec-into-container exception effective for infra controllers [merged]
    #2446 - fix(kubescape): except C-0007 delete-capable RBAC by object, not namespace [merged]
    #2452 - fix(kubescape): bump posture scanner to v4.0.10 so exception CRs apply offline [merged]
    #2479 - fix(kubescape): anchor the remaining literal CSE name matchers [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 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:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. moved this to 📥 Backlog in 🌊 Project Boardon Jul 18, 2026
  3. devantler commented on Jul 27, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Two corrections to this issue's premises, both measured today

    1. The Python generator is gone — that half is already done. This issue proposes reusing
    scripts/generate-kubescape-exceptions.py. There is no such file: it is now
    scripts/generate-kubescape-exceptions/ (main.go + main_test.go), and it is already wired into
    CI, which runs go test ./scripts/generate-kubescape-exceptions and then generates the exceptions
    file for the offline scan. So the tooling this issue wanted to build on exists, in Go, tested.

    Worth noting the CI-side goal is met by that generator: CI and the cluster derive their exception set
    from the same CRs. This issue's remaining scope is only the Headlamp presentation mirror.

    2. "Just delete the mirror" is not currently supported by evidence. The ConfigMap's own header
    says it becomes redundant once the scanner is ≥ v4.0.10, because the operator then applies the CRs at
    scan time and writes status=passed / subStatus=w-exceptions into the stored summaries for the
    plugin to render.

    The deployed scanner is v4.0.10. But a complete sweep of all 2200
    workloadconfigurationscansummaries, fetched by name, found no object anywhere carrying
    subStatus: w-exceptions
    — and separately, objects named explicitly in a kind+name exception CR are
    still reported as failing (evidence on platform#2823).

    So the version threshold is met while the behaviour it was supposed to unlock is not observable. On
    this evidence the mirror is not redundant, and deleting it would take the plugin's exception view
    back to "No data to be shown".

    Suggested resequencing

    This issue is now blocked on the same question as #2823: whether ClusterSecurityException CRs are
    being consumed for posture at all. That answer decides which of this issue's two options is even
    available:

    • CRs are consumed → the mirror can eventually be deleted, and the interim fix is regeneration
      plus a CI drift gate.
    • CRs are not consumed → the mirror is the only thing making the plugin's exception view correct,
      and generating it from the CRs (which the Go tool already knows how to do) becomes the durable fix
      rather than a stopgap.

    Either way the hand-maintenance should end; only the endpoint is in question.

    Also worth re-measuring before acting: the specific drift figures in the issue body are from
    2026-07-11 and several CSE files have changed since, so the "4 missing / 26 vs 3 matchers" numbers
    should be re-derived rather than trusted.

  4. self-assigned this
    on Jul 28, 2026
  5. devantler commented on Jul 28, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Drift re-measured today, and the first slice is shipped as #2837

    The 2026-07-11 figures in the body were re-derived against the current tree. Comparing the mirror to
    go run ./scripts/generate-kubescape-exceptions output:

    Claim in the body Measured today
    4 CSEs missing 2 genuinely missing (kubescape-privileged, readonly-rootfs-pending). The two talos-cis-* CRs are a deliberate exclusion documented in the file header — the plugin only evaluates workload posture and those controls never attach to a workload.
    delete-capable-rbac-by-design 26 vs 3 matchers Not drift. The mirror alternation-collapses the same 26 names (19 ClusterRoleBinding + 7 RoleBinding). It did carry one extra kind: ^Group$ / ^crossplane-masters$ resource with no CR counterpart — transcribed from a trailing comment in delete-rbac.yaml.
    batch-workloads 2 vs 1 Not drift — ^(Job|CronJob)$ is one alternation of the generator's two entries.
    pod-security-mutations 4 vs 5 controls Real, and the security-relevant one. The mirror added ^C-0017$ under a namespace: ".*" resource, suppressing C-0017 cluster-wide in the dashboard while the CRs except it only in three scoped places.

    #2837 fixes the faithfulness half. Control sets now match the CRs exactly across all 19 shared policies.

    Notes for the remaining scope (generate + CI drift gate)

    Two findings that cost real time to establish — please do not re-derive them:

    1. There is NO structural discriminator for the host/CIS exclusion. The obvious candidate — "exclude
    policies whose resources are a bare wildcard" — is refuted: nine generated policies carry a bare
    wildcard, including mirrored ones (admission-controllers, cilium-network-policies,
    helm-chart-metadata, image-verification, pod-security-mutations, upstream-chart-defaults,
    vpa-managed-resources). The talos-cis-* CRs omit spec.match for a documented reason (host
    findings are not namespaced), and the generator renders that as kind: ".*" — correct for the offline
    CI scan, wrong for a workload-presentation mirror. So the exclusion is an editorial judgement and needs
    to be marked on the CRs (an annotation) rather than inferred.

    2. The mirror and the generator use different but equivalent forms, so a byte-comparison drift gate
    would fire on day one: the mirror alternation-collapses resource lists, and expresses "everything" as
    namespace: ".*" where the generator emits kind: ".*". A gate therefore needs either a semantic
    comparison, or a decision to let the generator's canonical form overwrite the file (simpler and more
    robust, at the cost of a larger generated file).

    Live-state note

    Read-only check against prod: the three C-0017 scopes in the CRs are doing real work —
    opencost/deployment-opencost{,-primary} and wedding-app/deployment-wedding-app genuinely fail
    C-0017, while all 14 sampled workloads outside the excepted scopes pass it. So the wildcard was not
    hiding anything today; it was a latent suppression of any future regression.

  6. added a commit that references this issue on Jul 28, 2026
    c1a8aa6
  7. moved this from 📥 Backlog to 👀 In Review in 🌊 Project Boardon Jul 28, 2026
  8. moved this from 👀 In Review to ✅ Done in 🌊 Project Boardon Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Fields

No fields configured for Security.

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions