The module lifecycle stage: Generally available version
The module has requirements for installation
The module performs automated cluster security checks in line with the CIS Kubernetes Benchmark.
Version in use
The module implements checks according to CIS Kubernetes Benchmark v1.12 (canonical mapping for Kubernetes 1.32–1.34, also applied to 1.35).
The external module operator-trivy is available starting from Deckhouse Platform (DP) 1.76.
DP 1.74 and earlier include a built-in module with Trivy v0.55.2.
The upstream aquasecurity/trivy-checks bundle contains the k8s-cis-1.23 specification.
DP adds the controls of CIS Kubernetes Benchmark v1.12 and changes some checks, as described in DP customizations.
The module uses the following component versions:
| Component | Version |
|---|---|
| Trivy | v0.69.3 |
| Trivy Operator | v0.30.1 |
| k8s-node-collector | v0.3.1 |
The table lists the versions of the current module release. To see the images that are running in a particular cluster, run:
d8 k -n d8-operator-trivy get pods -o json | jq -r '.items[] | .metadata.name as $p | .spec.containers[] | "\($p)\t\(.name)\t\(.image)"'Check categories
CIS Kubernetes Benchmark checks are grouped into the following categories:
| Category | CIS Section | Identifier | Description |
|---|---|---|---|
| Control Plane | 1.x | AVD-KCV-* | API server, controller manager, and scheduler configuration |
| etcd | 2.x | AVD-KCV-* | etcd security settings |
| Control Plane | 3.x | — | Authentication and logging (manual checks) |
| Worker Nodes | 4.x | AVD-KCV-* | Kubelet configuration and file permissions |
| Policies | 5.x | AVD-KSV-* | RBAC, Pod Security Standards, and network policies |
Exclusions
In DP, some checks are disabled or excluded from reports. This is due to platform architecture and system component requirements.
CIS results depend on the cluster configuration and installed components, so passing all controls is not guaranteed.
Namespaces and objects excluded from reports
The complianceReports.skipSystemResources parameter excludes platform objects from compliance reports.
With the default value true, the module applies the following filters:
- ConfigAuditReport and RbacAssessmentReport are not created in the
d8-*,kube-*anddefaultnamespaces. - Cluster-scoped objects with the
heritage=deckhouseorkubernetes.io/bootstrapping=rbac-defaultslabel are skipped, for example DP ClusterRoles and the built-in Kubernetes roles such ascluster-admin.
InfraAssessmentReport is not affected, so the checks of CIS section 1.x keep covering the control plane components in kube-system.
Because nothing in the default namespace is evaluated, control 5.6.4 (default namespace) finds no objects there.
To exclude a namespace from compliance reports regardless of the parameter value, add the security.deckhouse.io/skip-compliance=true label to it:
d8 k label namespace <NAMESPACE> security.deckhouse.io/skip-compliance=true<NAMESPACE> is the name of the namespace.
ConfigAuditReport and RbacAssessmentReport are not created in a namespace with this label.
The exclusions for system namespaces and for the default namespace below, as well as the examples of DP roles, matter when complianceReports.skipSystemResources is false.
Globally disabled checks
The following check is disabled for all cluster objects.
CIS 1.2.1 — Anonymous auth (AVD-KCV-0001)
Check: Ensure that the --anonymous-auth argument is set to false.
Status: Completely disabled.
Reason: The Trivy/defsec CIS 1.2.1 implementation is based on inspecting the legacy --anonymous-auth flag.
On newer Kubernetes versions, anonymous authentication configuration may be managed differently (for example, via AuthenticationConfiguration), therefore a flag-based check may produce false FAIL/WARN results (including in kube-bench).
For auditing, rely on the actual kube-apiserver configuration and access controls in your environment.
Checks disabled for system namespaces
The following checks are disabled for objects in the kube-system namespace and in namespaces with the d8- prefix, which DP modules use.
In other namespaces, these checks run in full and help identify insecure configurations.
A Kubernetes cluster requires components that work at a low level: network management, storage, host resource monitoring. These components cannot run in an isolated container without privileges. The table lists the checks and examples of DP objects that need the corresponding privileges:
| CIS ID | Check ID | Check | Examples in DP |
|---|---|---|---|
| 5.2.2 | AVD-KSV-0017 | Minimize the admission of privileged containers | DaemonSet/d8-cni-cilium/agent (privileged: true), DaemonSet/d8-cloud-instance-manager/fencing-agent-* (privileged: true) |
| 5.2.3 | AVD-KSV-0010 | Minimize the admission of containers wishing to share the host process ID namespace | DaemonSet/d8-monitoring/node-exporter (hostPID: true), DaemonSet/d8-monitoring/ebpf-exporter (hostPID: true) |
| 5.2.5 | AVD-KSV-0009 | Minimize the admission of containers wishing to share the host network namespace | DaemonSet/d8-monitoring/node-exporter (hostNetwork: true) |
| 5.2.6 | AVD-KSV-0001 | Minimize the admission of containers with allowPrivilegeEscalation | DaemonSet/d8-cni-cilium/agent (allowPrivilegeEscalation: true), DaemonSet/d8-istio/ztunnel (allowPrivilegeEscalation: true) |
| 5.2.7 | AVD-KSV-0012 | Minimize the admission of root containers | DaemonSet/d8-istio/ztunnel (runAsUser: 0) |
| 5.2.8 | AVD-KSV-0022 | Minimize the admission of containers with the NET_RAW capability | DaemonSet/d8-istio/ztunnel (capability NET_RAW) |
| 5.2.11 | AVD-KSV-0023 | Minimize the admission of HostPath volumes | DaemonSet/d8-monitoring/node-exporter (hostPath: /, /etc/containerd, /var/run/node-exporter-textfile) |
| 5.2.12 | AVD-KSV-0024 | Minimize the admission of containers which use HostPorts | DaemonSet/d8-ingress-nginx/controller-* (hostPort: 80/443 in HostPort and Failover modes) |
Partial exclusions
The following exclusions apply to specific objects.
CIS 5.6.4 — Default namespace (AVD-KSV-0110)
Check: The default namespace should not be used.
Exclusion: Service kubernetes in the default namespace (default/service-kubernetes).
Reason: The kubernetes Service in the default namespace is the built-in Kubernetes API server Service.
Kubernetes creates it during cluster initialization, and it cannot be moved to another namespace.
Security context (AVD-KSV-0020, AVD-KSV-0021)
Checks: AVD-KSV-0020 (UID 10000 or less) and AVD-KSV-0021 (GID 10000 or less) in ConfigAuditReports. They are not part of the CIS report: CIS 5.6.3, which covers the security context, is a manual control, see Manual checks.
Exclusion: AVD-KSV-0020 and AVD-KSV-0021 for ReplicaSets in d8-* namespaces.
Reason: These checks require runAsUser and runAsGroup greater than 10000. Some DP system workloads run as root or do not set runAsUser and runAsGroup, because they work with host files and with volumes whose permissions are already set.
The exclusion matters only when complianceReports.skipSystemResources is false. With the default value, reports are not created in d8-* namespaces at all, see Namespaces and objects excluded from reports.
To list the workloads in system namespaces whose pod or container sets runAsUser or runAsGroup to 10000 or less, run:
d8 k get deploy,rs,ds,sts -A -o json | jq -r '
.items[]
| select(.metadata.namespace | test("^(d8-)|^(kube-system|kube-public|kube-node-lease)$"))
| . as $o
| ($o.spec.template.spec.securityContext.runAsUser // null) as $pu
| ($o.spec.template.spec.securityContext.runAsGroup // null) as $pg
| $o.spec.template.spec.containers[]?
| . as $c
| ($c.securityContext.runAsUser // null) as $cu
| ($c.securityContext.runAsGroup // null) as $cg
| select((($pu|tonumber? // 999999) <= 10000) or (($pg|tonumber? // 999999) <= 10000) or (($cu|tonumber? // 999999) <= 10000) or (($cg|tonumber? // 999999) <= 10000))
| [
$o.kind,
$o.metadata.namespace,
$o.metadata.name,
("pod.runAsUser=" + (($pu|tostring) // "null")),
("pod.runAsGroup=" + (($pg|tostring) // "null")),
("container=" + $c.name),
("container.runAsUser=" + (($cu|tostring) // "null")),
("container.runAsGroup=" + (($cg|tostring) // "null"))
] | @tsv'Checks excluded from metrics
The following checks are performed, but their results are not displayed in Prometheus metrics. This keeps the metrics focused on real security issues instead of false positives.
Results of these checks are still saved in ClusterComplianceReport and available for auditing.
CIS 5.1.2 — Access to secrets (AVD-KSV-0041)
Check: Minimize access to secrets.
What it checks: Finds ClusterRoles that grant any of the get, list, watch, create, update, patch, delete, deletecollection, impersonate or * verbs on the secrets resource.
Why excluded from metrics: The CIS Benchmark marks this recommendation as Manual. Whether access to secrets is legitimate cannot be determined automatically: any controller that works with TLS certificates or credentials needs such access.
Examples of DP ClusterRoles with legitimate access to secrets:
d8:ingress-nginx:kruise-role: Manages secrets for Ingress controllers.d8:node-manager:machine-controller-manager: Reads secrets and ConfigMaps formachine-controller-managerin thed8-cloud-instance-managernamespace.d8:operator-prometheus: Reads and creates secrets for the Prometheus operator.
To list the ClusterRoles with access to secrets, run:
d8 k get clusterroles -o json | jq -r '
.items[]
| select(any(.rules[]?;
((.resources // []) | index("secrets")) != null
and any((.verbs // [])[];
IN("get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "impersonate", "*"))
))
| .metadata.name'To list the bindings of a role, for example d8:operator-prometheus, run:
role_name="d8:operator-prometheus"
d8 k get clusterrolebindings,rolebindings -A | grep -F "$role_name"To get the results of the control from the report, run:
d8 k get clustercompliancereports.trivy.deckhouse.io cis -ojson | jq '.status.detailReport.results | map(select(.id == "5.1.2")) | .[].checks'Analyze the list and ensure that:
- All roles belong to known system components
- No user roles have excessive access
CIS 5.1.3 — Wildcard use in Roles (AVD-KSV-0044, AVD-KSV-0045, AVD-KSV-0046)
Check: Minimize wildcard use in Roles and ClusterRoles.
What it checks: Finds Roles and ClusterRoles that grant the * verb on all resources of all API groups or on sensitive resources such as secrets, pods and clusterroles, and ClusterRoles that grant access to all resources (*).
Why excluded from metrics: The CIS Benchmark marks this recommendation as Manual.
The standard Kubernetes role cluster-admin contains wildcards, and operators often require broad access to manage diverse resources.
Examples of legitimate wildcard use in DP:
cluster-admin: Full access for administrators.deckhouseClusterRole: Management of all DP modules.- CRD operators: Access to all resources in their API group.
To get the results of the control from the report, run:
d8 k get clustercompliancereports.trivy.deckhouse.io cis -ojson | jq '.status.detailReport.results | map(select(.id == "5.1.3")) | .[].checks'Ensure wildcards are used only in the following roles:
- Kubernetes built-in roles such as
cluster-admin - Roles of DP components (ClusterRoles with the
d8:prefix and Roles ind8-*namespaces) - Known operator roles
Known specifics
The following checks behave differently from the upstream trivy-checks bundle or can fail on a DP cluster.
Kubelet TLS certificate check (CIS 4.2.9)
Checks AVD-KCV-0088 and AVD-KCV-0089 may show FAIL status.
This is expected behavior: DP uses automatic kubelet certificate rotation (RotateKubeletServerCertificate) instead of static --tls-cert-file and --tls-private-key-file files.
The details are in the control-plane-manager module FAQ.
CNI file checks (CIS 1.1.9 / 1.1.10)
Checks AVD-KCV-0056 and AVD-KCV-0057 (CNI configuration file permissions and ownership) target the CNI configuration directory /etc/cni/net.d.
The upstream trivy-checks bundle audits the files that match /*/cni/*.
This pattern also covers the CNI plugin binaries in /opt/cni/bin (they must be 755 to be executable) and the IPAM state in /var/lib/cni/networks (regular files without secrets).
On a standard cluster, this produces a FAIL that does not point to any file.
DP overrides the upstream check so that the audit covers only /etc/cni/net.d, as the CIS recommendation requires.
AlwaysPullImages admission plugin (CIS 1.2.11)
The upstream check AVD-KCV-0012 requires --enable-admission-plugins=AlwaysPullImages on kube-apiserver.
The admission plugin prevents a pod in namespace B from starting an image that a pod in namespace A pulled with private registry credentials: without it, the kubelet starts the image from its local cache and does not pull it again.
Kubernetes 1.35 mitigates the same threat in the kubelet with the KubeletEnsureSecretPulledImages feature gate (alpha in 1.33–1.34, beta and enabled by default in 1.35 and later).
The kubelet verifies the image pull credentials of the requesting pod even for images that are already on the node, without the constant re-pull traffic that AlwaysPullImages causes.
The Kubernetes versions that DP supports are listed in Supported versions.
DP overrides the check as follows:
- On Kubernetes 1.35 and later, the check passes without the admission plugin.
- On earlier versions, the check requires
AlwaysPullImagesin--enable-admission-plugins. Enabling the kubelet feature gateKubeletEnsureSecretPulledImagesdoes not change the result.
The Kubernetes minor version is taken from the kube-apiserver container image tag (for example, :v1.35.3).
If the tag does not contain a version, the check requires the admission plugin on any Kubernetes version.
In the CIS report, the control is named “Ensure that image pull credentials are verified per pod (AlwaysPullImages or KubeletEnsureSecretPulledImages)”.
etcd data directory ownership (CIS 1.1.12)
The upstream check AVD-KCV-0059 requires the etcd data directory /var/lib/etcd to be owned by etcd:etcd.
In DP, the control plane runs as static pods under root, so /var/lib/etcd is owned by root:root.
That is not a finding: root:root is at least as restrictive as etcd:etcd.
Only root can read the database, and root is already the most privileged principal on the host.
Requiring etcd:etcd would add a dedicated etcd user and group on every master node with access to the directory that holds the whole secret store of the cluster (Secrets, tokens, the whole etcd keyspace), without improving confinement on a static-pod control plane.
DP overrides the check so that it passes when the data directory is owned by either root:root or etcd:etcd.
In the CIS report, the control is named “Ensure that the etcd data directory ownership is set to root:root or etcd:etcd”.
Manual checks
The following controls are not automated and require manual verification. The CIS Benchmark marks them as Manual.
| CIS ID | Check | Recommendations |
|---|---|---|
| 3.1.1 | Client certificate authentication should not be used for users | Configure OIDC authentication using the user-authn module |
| 3.2.1 | Ensure that a minimal audit policy is created | DP configures a basic audit policy by default, see built-in audit policies |
| 3.2.2 | Ensure that the audit policy covers key security concerns | Review your audit policy against your organization’s security requirements. To add rules, configure a custom audit policy |
| 5.3.1 | Ensure that the CNI in use supports NetworkPolicies | The cni-cilium module supports NetworkPolicy. With other CNIs, NetworkPolicy is implemented by the network-policy-engine module |
| 5.4.1 | Prefer using Secrets as files over Secrets as environment variables | Check how your applications receive secrets: mount them as files instead of passing them in environment variables |
| 5.4.2 | Consider external secret storage | Store secrets outside Kubernetes, for example in Stronghold, a Vault-compatible secret storage. To sync secrets into Kubernetes, use the secrets-store-integration module |
| 5.5.1 | Configure Image Provenance using ImagePolicyWebhook admission controller | Use the admission-policy-engine module with image signature verification policies |
| 5.6.1 | Create administrative boundaries between resources using namespaces | Review your namespace structure according to organizational requirements |
| 5.6.3 | Apply SecurityContext to your Pods and Containers | Set securityContext for pods and containers according to your security policy. To enforce it, use the admission-policy-engine module security policies |
DP customizations
The CIS report in DP differs from the report of upstream Trivy Operator.
The upstream trivy-checks bundle contains the k8s-cis-1.23 specification, and DP changes the specification and some of the checks on top of it.
What’s customized
DP makes the following changes:
- CIS Kubernetes Benchmark v1.12. The CIS report contains 131 controls numbered as in v1.12 (the upstream
k8s-cis-1.23specification has 116). Controls keep the upstream check identifiers where the check logic did not change. Control numbers differ from the upstream specification: for example, CIS section 5.7 became 5.6 in v1.12. - CIS 1.1.9 / 1.1.10 — CNI file checks. The audit covers only
/etc/cni/net.d. Details are in CNI file checks. - CIS 1.2.11 — AlwaysPullImages. On Kubernetes 1.35 and later, the check passes without the admission plugin.
On earlier versions, it requires
AlwaysPullImagesin--enable-admission-plugins; enabling the kubelet feature gateKubeletEnsureSecretPulledImagesdoes not change the result. Details are in AlwaysPullImages admission plugin. - CIS 1.1.12 — etcd data directory ownership. The check accepts
root:rootoretcd:etcd. Details are in etcd data directory ownership. - CIS 4.3.1 — kube-proxy metrics bound to localhost. Upstream
trivy-checkshas no check for this v1.12 control. DP adds checkAVD-KCV-0801, which fails when akube-proxycontainer is started with--metrics-bind-addressset to an address other than127.0.0.1,localhostor[::1]. The check reads only the command-line flag. AmetricsBindAddressset in the kube-proxy configuration file is not checked; verify it manually. With thecni-ciliummodule, Cilium replaces kube-proxy (kubeProxyReplacement), there is nokube-proxyDaemonSet, and the check passes.
Checks AVD-KCV-0801–AVD-KCV-0899 are DP checks that are not in upstream trivy-checks.
Verifying with kube-bench yourself
To compare the report with another implementation of the CIS audit, run kube-bench as a one-time Kubernetes Job.
kube-bench is also developed by Aqua Security and has profiles for newer CIS versions than trivy-checks.
The following Job runs kube-bench on a master node:
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench-master
namespace: kube-system
spec:
template:
spec:
hostPID: true
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
containers:
- name: kube-bench
image: aquasec/kube-bench:latest
command: ["kube-bench", "--benchmark", "cis-1.12", "run", "--targets", "master,etcd,controlplane,policies"]
volumeMounts:
- { name: var-lib-etcd, mountPath: /var/lib/etcd, readOnly: true }
- { name: var-lib-kubelet, mountPath: /var/lib/kubelet, readOnly: true }
- { name: etc-systemd, mountPath: /etc/systemd, readOnly: true }
- { name: etc-kubernetes, mountPath: /etc/kubernetes, readOnly: true }
- { name: usr-bin, mountPath: /usr/local/mount-from-host/bin, readOnly: true }
restartPolicy: Never
volumes:
- { name: var-lib-etcd, hostPath: { path: /var/lib/etcd } }
- { name: var-lib-kubelet, hostPath: { path: /var/lib/kubelet } }
- { name: etc-systemd, hostPath: { path: /etc/systemd } }
- { name: etc-kubernetes, hostPath: { path: /etc/kubernetes } }
- { name: usr-bin, hostPath: { path: /usr/bin } }
backoffLimit: 0Save the manifest as kube-bench-master.yaml and apply it:
d8 k apply -f kube-bench-master.yamlSave the Job output to a file:
d8 k -n kube-system logs job/kube-bench-master | tee kube-bench-master.txtTo check worker nodes, change the nodeSelector and the target list (--targets node) and run the Job on each worker node.
The kube-bench repository contains ready-made manifests job-master.yaml and job-node.yaml.
Comparing kube-bench and DP output
kube-bench and the DP CIS report use the same control IDs (1.1.9, 4.3.1 and so on), so you can compare them control by control.
In DP, control plane and node checks (AVD-KCV-*) run commands on the nodes through k8s-node-collector, and policy checks (AVD-KSV-*) analyze Kubernetes objects.
The results differ for the following controls:
| Control | DP behavior | kube-bench behavior |
|---|---|---|
| 1.1.9 / 1.1.10 (CNI files) | Scoped to /etc/cni/net.d |
Checks the directory from the kubelet --cni-conf-dir flag and the IPAM state in /var/lib/cni/networks. The control is unscored, so a failed test is reported as WARN |
| 1.1.12 (etcd data directory ownership) | PASS on root:root or etcd:etcd |
Requires strictly etcd:etcd, FAIL on the static-pod root:root |
| 1.2.11 (AlwaysPullImages) | PASS on Kubernetes 1.35 and later without the admission plugin | Requires the admission plugin regardless of the Kubernetes version. The control is unscored, so a failed test is reported as WARN |
| 4.3.1 (kube-proxy metrics) | PASS when Cilium replaces kube-proxy | FAIL because no kube-proxy process is found |
| 5.1.2 / 5.1.3 (secrets / wildcards) | Not emitted to metrics (still in the report) | Uses its own audit commands. The controls are unscored, so a failed test is reported as WARN |
Viewing results
You can view the results in Deckhouse Console or with d8 k.
Deckhouse Console
CIS Benchmark results are available in System → System management → Security → Compliance Reports.
Command line
Get all results:
d8 k get clustercompliancereports.trivy.deckhouse.io cis -ojson | jq '.status.detailReport.results'Get only failed checks:
d8 k get clustercompliancereports.trivy.deckhouse.io cis -ojson | jq '.status.detailReport.results | map(select(.checks | map(.success) | all | not))'Get results for a specific check:
d8 k get clustercompliancereports.trivy.deckhouse.io cis -ojson | jq '.status.detailReport.results | map(select(.id == "5.6.4"))'Check schedule
CIS Benchmark checks run on operator startup and on the schedule set by scanPeriods.complianceRescanCron (default 0 */6 * * *, every 6 hours).