Skip to content
Draft
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
36 changes: 36 additions & 0 deletions docs/kubescape-result-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,42 @@ CI has no cluster, so it runs only the fixture test
(`scripts/tests/test-check-kubescape-result-coverage.sh`). The test includes the negative control:
one expected result removed from a passing set must fail.

### Vulnerability summary references and scanner refreshes

A coherent summary can still point to the wrong object. The scanner writes full and relevant
vulnerability manifests into its storage namespace, while each workload summary stays in the
workload's namespace. Both populated `vulnerabilitiesRef` entries must name the namespace where
their target was actually stored. Read the referenced objects by their declared names and
namespaces; finding the same name somewhere else does not make the reference valid.

The chart's default kubevuln v0.3.430 publishes the workload namespace in these references.
Upstream [#946](https://github.com/kubescape/kubevuln/pull/946) fixes that and refreshes cached
summaries after image changes. The explicit v0.3.574 digest includes those fixes and
[#1170](https://github.com/kubescape/kubevuln/pull/1170), which preserves node-agent SBOMs when
local generation is disabled. It is bound to source
`5e312eec38548cd81e4d3a758b1a283fd6b32bb4`, with its signature verified against the pinned
upstream shared release workflow, GitHub OIDC issuer, repository, main ref and that exact source
revision before adoption (#5403). It also removes the affected RAR decoder and includes the
bounded ELF pre-filter from the Syft fork. Dependency audit records and their call-path
dispositions remain tracked separately in #5553; a valid signature alone is not a clean audit.
The chart and existing profile APIs remain in place. Syft 1.55 inventories also require the
source and package metadata support added in upstream storage #471; an unchanged storage pin
does not establish this compatibility. Keep adoption held under #5553 until the scanner client
and the deployed compatibility storage build preserve those types without losing the existing
storage repairs. The scanner still uses vulnerability-manifest, summary, SBOM, VEX and
container-profile endpoints; retaining application/network profile APIs remains necessary.

The newer scanner invalidates exception label caches through workload and namespace informers.
The Helm post-renderer supplies the matching list/watch permissions for the nine already-readable
resource types. The render regression checks each permission and the immutable scanner image;
the chart uses this same image value for its optional SBOM sidecar.

An image rollout does not rewrite existing summaries on its own. After a successful scan, verify
that the summary follows the current running image, its timestamp advances, its populated
references resolve, and its severity counters agree with the corresponding complete payloads.
Repeat the full coverage check, including metadata agreement. Missing manifests, old summaries,
failed scans and incomplete payload reads remain failures or unknowns; none is a zero-CVE result.

## First measurement (2026-09-30)

| Surface | Expected | Current | Failing |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@ spec:
# scans against their limit despite spare node capacity. A 20:1 ratio gives
# one core at the 50m floor without raising the idle request (#4389).
kubevuln:
image:
# Upstream #946 fixes cross-namespace manifest references and refreshes
# cached summaries after image changes. v0.3.574 preserves existing
# node-agent SBOMs when local generation is disabled (#1170), and carries
# the RAR/ELF parser repairs reviewed under #5553. This digest is signed
# by the pinned upstream release workflow at source 5e312ee;
# retain the chart and compatibility storage/profile APIs (#5403).
repository: quay.io/kubescape/kubevuln
tag: "v0.3.574@sha256:0a1f421ea6c35419617c836a2fd50bbd230c84a7668cb343d03b4d5ee24b1402"
resources:
requests:
cpu: 50m
Expand Down Expand Up @@ -389,6 +398,50 @@ spec:
postRenderers:
- kustomize:
patches:
# kubevuln v0.3.566 invalidates cached workload/namespace labels with
# informers. Chart 1.40.5 grants get only. Add read-only list/watch for
# precisely the informer resources so changed labels cannot leave an
# old exception selector decision cached. No new write or Secret access.
- target:
kind: ClusterRole
name: ^kubevuln$
patch: |
- op: add
path: /rules/-
value:
apiGroups:
- ""
resources:
- namespaces
- pods
- replicationcontrollers
verbs:
- list
- watch
- op: add
path: /rules/-
value:
apiGroups:
- apps
resources:
- deployments
- replicasets
- statefulsets
- daemonsets
verbs:
- list
- watch
- op: add
path: /rules/-
value:
apiGroups:
- batch
resources:
- jobs
- cronjobs
verbs:
- list
- watch
# The chart hard-codes a one-second liveness timeout. A full posture
# scan can hold the scanner busy longer than that without wedging it:
# production recorded one timeout during the daily scan while the pod
Expand Down
69 changes: 65 additions & 4 deletions scripts/tests/test-kubescape-self-hosted-scan-persistence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,15 @@ fi
# kubevuln v0.3.159 swallows an exhausted conflict retry while updating a
# VulnerabilityManifestSummary: it logs the original AlreadyExists result and
# returns success. kubescape/kubevuln#745 fixed both error reporting and
# propagation, first released in v0.3.404. Chart 1.40.5 selects v0.3.430;
# verify the rendered image below so a later chart cannot regress silently.
readonly kubevuln_tag='v0.3.430'
# propagation, first released in v0.3.404. Its chart-default v0.3.430 still
# publishes manifest references in the workload namespace even though the
# manifests live in the scanner namespace, and skips cached-summary refreshes
# after image updates. Upstream #946 fixes both; v0.3.574 retains that fix,
# preserves node-agent SBOMs when local generation is disabled (#1170), and
# includes the parser repairs reviewed under #5553.
# Bind the rendered scanner to the verified upstream release digest, so chart
# defaults or a mutable tag cannot silently restore either failure (#5403).
readonly kubevuln_tag='v0.3.574@sha256:0a1f421ea6c35419617c836a2fd50bbd230c84a7668cb343d03b4d5ee24b1402'

keep_local="$(yq -er '
.spec.values.kubescapeScheduler.requestBody.commands[] |
Expand Down Expand Up @@ -311,6 +317,61 @@ readonly node_agent_profile_reader_count
[[ "${node_agent_profile_reader_count}" == "1" ]] ||
fail 'the Helm post-renderer must restore exactly the node-agent profile read rule removed by chart 1.40.4'

# v0.3.566 invalidates exception label caches through informers. Chart 1.40.5
# grants only get on these objects, so an image-only upgrade would continuously
# fail to list/watch and could keep stale selector decisions until cache expiry.
label_watch_access() {
local chart_file="$1" api_group="$2" resource="$3"
API_GROUP="${api_group}" RESOURCE="${resource}" yq ea -r '
select(.kind == "ClusterRole" and .metadata.name == "kubevuln") |
[.rules[] |
select(.apiGroups[] == strenv(API_GROUP)) |
select(.resources[] == strenv(RESOURCE)) |
.verbs[] | select(. == "get" or . == "list" or . == "watch")]
| unique | sort | join(",") == "get,list,watch"
' "${chart_file}"
}

while IFS='|' read -r api_group resource; do
label_watch_allowed="$(label_watch_access "${postrendered_chart}" "${api_group}" "${resource}")" ||
fail "could not inspect kubevuln ${resource} label-watch access"
[[ "${label_watch_allowed}" == 'true' ]] ||
fail "kubevuln must be able to read/watch ${resource} labels for exception cache invalidation"
done <<'LABEL_WATCHES'
|namespaces
|pods
|replicationcontrollers
apps|deployments
apps|replicasets
apps|statefulsets
apps|daemonsets
batch|jobs
batch|cronjobs
LABEL_WATCHES

# Exercise the complete rendered role with three invalid permission variants.
# A subresource, another API group or a verb suffix cannot authorize this informer.
readonly invalid_watch_resource="${chart_dir}/invalid-label-watch-resource.yaml"
yq 'with(select(.kind == "ClusterRole" and .metadata.name == "kubevuln");
(.rules[].resources[] | select(. == "deployments")) = "deployments/status")' \
"${postrendered_chart}" >"${invalid_watch_resource}" || fail 'could not construct the subresource control'
[[ "$(label_watch_access "${invalid_watch_resource}" apps deployments)" == false ]] ||
fail 'the label-watch guard accepted a subresource instead of deployments'

readonly invalid_watch_group="${chart_dir}/invalid-label-watch-group.yaml"
yq 'with(select(.kind == "ClusterRole" and .metadata.name == "kubevuln");
(.rules[].apiGroups[] | select(. == "apps")) = "apps.example.invalid")' \
"${postrendered_chart}" >"${invalid_watch_group}" || fail 'could not construct the API-group control'
[[ "$(label_watch_access "${invalid_watch_group}" apps deployments)" == false ]] ||
fail 'the label-watch guard accepted another API group instead of apps'

readonly invalid_watch_verb="${chart_dir}/invalid-label-watch-verb.yaml"
yq 'with(select(.kind == "ClusterRole" and .metadata.name == "kubevuln");
(.rules[].verbs[] | select(. == "watch")) = "watchStatus")' \
"${postrendered_chart}" >"${invalid_watch_verb}" || fail 'could not construct the verb control'
[[ "$(label_watch_access "${invalid_watch_verb}" apps deployments)" == false ]] ||
fail 'the label-watch guard accepted a verb suffix instead of watch'

rendered_posture_schedule="$(yq ea -er '
select(.kind == "CronJob" and .metadata.name == "kubescape-scheduler") |
.spec.schedule |
Expand Down Expand Up @@ -362,7 +423,7 @@ rendered_kubevuln_image="$(yq ea -er '
' "${rendered_chart}")" || fail 'the rendered kubevuln image is missing'
readonly rendered_kubevuln_image
[[ "${rendered_kubevuln_image}" == "quay.io/kubescape/kubevuln:${kubevuln_tag}" ]] ||
fail 'the rendered kubevuln Deployment does not use the conflict-safe image'
fail 'the rendered kubevuln Deployment does not use the verified reference- and refresh-safe image'

# Registry blob pulls can redirect from their API hosts to separate CDN hosts.
# If a redirect host is blocked, kubevuln retries timeouts for hours and keeps
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate-eks-ci-role-policy/approved-surface.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ helm.toolkit.fluxcd.io/v2|HelmRelease|kube-system|tetragon 5781451a70a8dee38ed71

helm.toolkit.fluxcd.io/v2|HelmRelease|kubescape|alertmanager be4e40e80964e37494dde9c1df0662a58b9b8976d2a57b9efed9c65aebdb9611

helm.toolkit.fluxcd.io/v2|HelmRelease|kubescape|kubescape 6a352e2f5cb05f56d3558dcb9a11b1a56275b70c62b508f67e9c5366c79ce390
helm.toolkit.fluxcd.io/v2|HelmRelease|kubescape|kubescape 819993ece8c036d62d10847cff48481cba2db52e1adbe47d254002193ede4a68

helm.toolkit.fluxcd.io/v2|HelmRelease|kyverno|kyverno b910afb7d7bcbb10e14430ec948732454c95e649bcdb71d16a3855fea07e7fbe

Expand Down
Loading