fix(kubescape): report health correctly and make the level filter work - #75
Open
slashben wants to merge 2 commits into
Open
fix(kubescape): report health correctly and make the level filter work#75slashben wants to merge 2 commits into
slashben wants to merge 2 commits into
Conversation
check_health reported `healthy: false` on a fully working Kubescape install, and the `level` filter on list_vulnerability_manifests silently did nothing. Both were verified on a live cluster running the kubescape-operator chart 1.40.4. Three fixes: 1. The spdx.softwarecomposition.kubescape.io resources are served by an aggregated API server (the storage service, wired via an APIService), not by CRDs. The four CustomResourceDefinitions().Get() lookups therefore returned IsNotFound on every install, forever, so check_health always reported vulnerability_crd and configuration_crd as errors and failed the whole check. Availability is now probed by listing each resource through the storage client the data tools already use, which also proves the path those tools depend on rather than a proxy for it. A single list per resource answers both "is the API reachable?" and "is there data?", so this makes four fewer API calls than before. The apiextensions client is no longer needed and is removed. Check keys are unchanged (*_crd) so existing consumers of the output are unaffected; only the lookup and the message wording change. 2. The pod label selectors matched the wrong pods. app.kubernetes.io/name=kubescape-operator is the chart-wide label carried by every pod the chart creates, so "operator_pods: 7/7" was really an all-kubescape-pods count, and app.kubernetes.io/name=storage matched nothing at all — storage_pods reported "No storage pods found" while the storage pod was Running. Per-component identity lives in the plain `app` label, so the selectors are now app=operator and app=storage. A missing storage pod is now an error that fails the health check rather than a warning: every data tool in this provider reads through the storage service. 3. list_vulnerability_manifests advertised a `level` filter but passed it as a labelSelector, and the storage API server ignores labelSelector on list (kubescape/storage#363) — all three values returned byte-identical output. Filtering now happens client-side on the same image-level/workload-level predicate the response already reports, so the filter and the output cannot disagree. An unrecognised level is now a validation error instead of silently meaning "both". Tests are fake-client based and need no cluster. The level-filter test installs a reactor that reproduces the storage server's actual behaviour of ignoring labelSelector, so a server-side filter cannot pass it. Signed-off-by: Ben Hirschberg <ben@armosec.io> Docs-exempt: bug fix; no existing doc describes check_health or the level filter Signed-off-by: Ben <ben@armosec.io>
…ore v0.0.305 kubescape/storage#362 added label selector support to list operations and shipped in v0.0.305 on 2026-08-20, so the behaviour the level filter works around is not unconditional. The kubescape-operator chart still pins storage v0.0.298, and a client cannot know which server version it is talking to, so the client-side filter stays -- it returns the same answer against either. Comments only; no behaviour change. Signed-off-by: Ben Hirschberg <ben@armosec.io> Docs-exempt: comment-only change, no behavioral change Signed-off-by: Ben <ben@armosec.io>
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's wrong
kubescape_check_healthreports"healthy": falseon a fully working Kubescape install. It's the tool an agent calls first, and answering "is this working?" is its whole job — so an agent tells the user Kubescape is broken and recommends reinstalling something that is already installed correctly.Verified on a live cluster with all 7 Kubescape pods
Runningand every other tool returning correct data:…while
kubectl get vulnerabilitymanifests -Areturned data and the storage pod was1/1 Running.Three separate bugs cause this. Also fixed: an advertised filter argument that silently does nothing.
1. It checks for CRDs that never exist
Background: most operators define their data types as CRDs. Kubescape doesn't — it runs its own extension API server (a pod named
storage, registered via anAPIService) which serves its data types. They look like normal Kubernetes resources to a client, but no CRD objects exist for them, ever, in any install.The health check did four CRD lookups:
These return
NotFoundon every install, forever. Confirmed on two clusters:Fix: check availability by listing each resource through the client the data tools already use. If the list succeeds, the API is there. This proves the exact path the tools depend on rather than a proxy for it — a CRD (or discovery) check can pass while the
storagepod is dead and every tool still fails.It also uses fewer API calls than before: one list per resource answers both "is the API reachable?" and "is there data?", replacing 4 CRD gets + 8 lists with 4 lists.
The
apiextensionsclient is no longer needed anywhere and is removed. Check keys are unchanged (vulnerability_crd, etc.) so existing consumers are unaffected; only the lookup and the message wording change.This also fixes a latent bug: two checks (
application_profiles_data,network_neighborhoods_data) were nested inside the always-failing CRD lookup, so they never ran at all.2. The pod checks select the wrong pods
app.kubernetes.io/name=kubescape-operatoris the chart-wide label on every pod the Helm chart creates, not the operator's own label. Per-component identity is in the plainapplabel:So
"operator_pods": "7/7 pods running"was really an all-pods count — ifnode-agentdied you'd get6/7with no way to tell which component failed — and"storage_pods": "No storage pods found"was emitted while the storage pod was healthy.Fix: select on
app=operatorandapp=storage.A missing storage pod now fails the health check instead of warning. Every read this provider makes goes through that pod, so the one check that would catch the failure mode breaking all nine data tools previously matched nothing and wouldn't have failed the check if it had.
3. The
levelfilter silently does nothingkubescape_list_vulnerability_manifestsadvertiseslevel:image,workload, orboth. All three returned byte-identical output:level6f1e27dfb8fblevel=image6f1e27dfb8fblevel=workload6f1e27dfb8fbThe provider passed the filter as a Kubernetes label selector, but the Kubescape API server version shipped by the chart ignores label selectors on list and returns everything regardless (a nonsense selector also returns every row).
This is fixed upstream — kubescape/storage#362, released in storage
v0.0.305— but the chart still pinsv0.0.298, seven releases earlier, and a client can't know which server version it's talking to.Fix: filter client-side, using the same image-level/workload-level test the response already computes for its own output fields. So the filter and the reported fields can't disagree, and it returns the same answer against either server version. An unrecognised
levelvalue is now a validation error instead of silently meaning "both".Risk / compatibility
apiextensions), no new ones.pkg/kubescape.Testing
go test ./pkg/kubescape/...— 60 pass, no cluster needed.go build,go vet,golangci-lint,go test -tags=test ./pkg/... ./internal/...(739 pass) all clean. The pre-existingtest/e2esuite needs a live kind cluster and was not run.All tests written first and observed failing. Two notes:
app=<component>— i.e. the real shape. That's what makes the old selector fail and the new one pass.leveltest installs a reactor that reproduces the real server ignoring label selectors. Without it the fake clientset honours selectors, so a server-side filter would pass the test while returning unfiltered data on a real cluster — which is exactly how this survived.Two existing tests whose premise was "no CRDs installed" are rewritten to exercise genuine API unavailability, including a partial-failure case (one resource down, another still reporting
ok).Ticket
None.