diff --git a/.mega-linter.yml b/.mega-linter.yml index 0986b5f48..699894ff4 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -86,7 +86,21 @@ DISABLE_ERRORS_LINTERS: # checkov (37) and trivy (624): Kubernetes misconfiguration in k8s/, which the section above # explains is NOT covered elsewhere. Tracked by #2787. # - # checkov's remaining 37 are all `kubernetes` framework, led by CKV_K8S_40 (11). The `secrets` + # checkov's 37 are all `kubernetes` framework, and all 11 CKV_K8S_40 findings stand unsuppressed. + # Four scoped skips were tried and withdrawn: each named a per-site technical constraint that does + # not survive checking. The two vault-backup jobs claimed to share the vault-snapshots PVC with the + # OpenBao StatefulSet — but the live StatefulSet mounts `config`/`unseal-keys`/`tmp`/`home` plus + # claim templates `data`/`audit`, and never that PVC at all, so there is no shared ownership to + # preserve. The two user-namespace probes claimed their UID was the measurement subject — but + # `/proc/self/uid_map` describes the pod's namespace and reads identically for every process in it + # regardless of UID, and `fsGroup` supplies volume access as a GID independent of `runAsUser`. + # #2904 carries the per-site remediation and the risk-acceptance question for those four. Of the + # 11, two are inside the vendored KubeVirt and CDI release bundles and need a mechanism that + # survives a vendor bump (#2899). The remaining five have no demonstrated constraint and are + # candidates to simply raise (#2901) — including CoreDNS, whose pinned + # image config reads `User: "nonroot:nonroot"`, so an explicit `runAsUser: 65532` would both match + # the image and clear the check. CKV_K8S_40 is NOT disabled: a new workload running + # below UID 10000 with no stated reason is still flagged. The `secrets` # framework is at 0: its 31 CKV_SECRET_6 findings were ExternalSecret/PushSecret key NAMES and # OpenBao paths rather than secret material, and each site now carries a scoped skip naming the # control and the reason (#2892). CKV_SECRET_6 is NOT disabled — a real base64 secret committed diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml index 20b8cf570..f1213e25f 100644 --- a/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml +++ b/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml @@ -105,9 +105,14 @@ spec: priorityClassName: system-cluster-critical restartPolicy: Always schedulerName: default-scheduler - # No runAsNonRoot: the upstream CoreDNS image runs as root, and the - # NET_BIND_SERVICE capability it needs to bind :53 is not effective - # for non-root users without ambient capabilities. + # No runAsUser/runAsNonRoot is set here, so the container runs as whatever + # the image declares. Read from the pinned image's own config (v1.14.3, + # sha256:884b72dd…): `User: "nonroot:nonroot"` with `Entrypoint: /coredns` + # — the distroless nonroot identity (65532), not root, and it binds :53 + # through a file capability on the binary rather than an ambient one. + # Setting `runAsUser: 65532` explicitly would match that and satisfy + # checkov CKV_K8S_40, but nothing in CI brings up a cluster to prove DNS + # still resolves, so it is tracked in #2901 rather than changed blind. securityContext: seccompProfile: type: RuntimeDefault diff --git a/k8s/providers/hetzner/apps/userns-headlamp-mapping-probe/job.yaml b/k8s/providers/hetzner/apps/userns-headlamp-mapping-probe/job.yaml index 5627696fb..0aca8dbaa 100644 --- a/k8s/providers/hetzner/apps/userns-headlamp-mapping-probe/job.yaml +++ b/k8s/providers/hetzner/apps/userns-headlamp-mapping-probe/job.yaml @@ -76,8 +76,10 @@ spec: app.kubernetes.io/instance: headlamp topologyKey: kubernetes.io/hostname securityContext: - # headlamp's own identity, so the mapping measured is the one applied to - # the UID/GID pair the workload actually runs as. + # headlamp's own identity, so the probe runs the way headlamp does rather + # than as an arbitrary user. It does NOT determine what is measured: an + # idmap is a property of the pod's user namespace, so /proc/self/uid_map + # reads identically for every process in it whatever UID that process is. runAsNonRoot: true runAsUser: 100 runAsGroup: 101