KB-006: Checkov CKV_K8S_21 Flags Namespaced Resources Without an Explicit Namespace¶
Status: Resolved by convention: set metadata.namespace explicitly on every namespaced resource you author.
Symptom¶
The security-scans workflow (Checkov) fails a PR with CKV_K8S_21 ("The default namespace should not be used") on namespaced resources under app/ (ConfigMaps, ServiceAccounts, PVCs, …), even though they deploy into the correct namespace at runtime.
Cause¶
Checkov scans the raw committed manifests, before Flux applies the Kustomization's targetNamespace (or the parent-dir kustomization.yaml namespace:). A resource with no explicit metadata.namespace looks like it lives in default to the scanner, so it gets flagged, even though targetNamespace sets it correctly at apply-time.
It bites namespaced core kinds (ConfigMaps, ServiceAccounts, PVCs, Services). Cluster-scoped kinds (ClusterRole / ClusterRoleBinding) are not flagged, and CRD instances (ExternalSecret, ToolHive MCPServer, …) are typically skipped, so it mainly surfaces on the core kinds.
Fix¶
Two accepted approaches. Pick whichever fits the resource's context:
-
Set
metadata.namespaceexplicitly (preferred for single-namespace apps). Setmetadata.namespace: <ns>on every namespaced resource you author, matching the Kustomization'stargetNamespace. It is redundant withtargetNamespaceat apply-time (and harmless), but it satisfies the raw-YAML scanner. -
Suppress with a skip annotation (preferred for shared Kustomize components consumed in multiple namespaces, or apps where an explicit namespace is impractical). Add the
checkov.io/skip1annotation with a justification:
This is the pattern used by shared components (e.g., kubernetes/components/global-vars/) and apps like filebrowser that rely on targetNamespace for correct placement.
Sibling gotcha: Trivy KSV0046 (wildcard RBAC)
For a deliberate read-only resources: ["*"] ClusterRole, suppress it path-scoped in .trivyignore.yaml with a justification rather than narrowing the role.