Skip to content

CKV2_K8S_5 passes when a ClusterRole/Role has one resourceNames-scoped secrets rule alongside a separate, unrestricted secrets rule #7616

Description

@fcanogab

Description

CKV2_K8S_5 ("No ServiceAccount/Node should be able to read all secrets") is intended to fail when a ServiceAccount/Node is bound (via RoleBinding/ClusterRoleBinding) to a Role/ClusterRole that grants get/list/watch (or *) on secrets (or *) with no resourceNames restriction.

The check's "safe" condition currently evaluates rules.resourceNames exists as an attribute over the entire Role/ClusterRole object — i.e. "does any rule block in this object have resourceNames?" — rather than requiring that the same rule block which grants the risky secrets access also has resourceNames.

As a result, if a ClusterRole has:

  • Rule A: broad, unrestricted access to secrets (get, list, watch, etc., no resourceNames)
  • Rule B: a separate, legitimately scoped rule for secrets with resourceNames: [...] (e.g. reading a specific pull secret)

...the presence of Rule B's resourceNames appears to satisfy the check for the whole object, and CKV2_K8S_5 passes even though Rule A still grants unrestricted read/write access to every Secret in scope. This is effectively a false negative for a pattern I'd expect this check to be designed to catch — a controller with both a scoped, least-privilege grant and a separate broad grant in the same object.

This appears related to (but distinct from) #7110 and #6765, which both point at the same underlying limitation in how CKV2_K8S_1-5 correlate attributes across a rules list rather than per rule entry.

Reproduction

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: manager-role
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
- apiGroups: [""]
  resourceNames: ["pull-secret"]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: manager-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: manager-role
subjects:
- kind: ServiceAccount
  name: manager-controller
  namespace: my-operator-namespace
checkov -f test.yaml --framework kubernetes --check CKV2_K8S_5

Actual Result

Check: CKV2_K8S_5: "No ServiceAccount/Node should be able to read all secrets"
	PASSED for resource: ClusterRoleBinding.default.manager-rolebinding
	File: /test.yaml:...

Expected Result

Should FAIL, since the first rule block grants unrestricted secrets access (create/delete/get/list/patch/update/watch, no resourceNames) regardless of the second, unrelated, scoped rule.

Additional context

This is a very common real-world pattern for Kubernetes operators: they often need resourceNames-scoped access to one or two known Secrets (e.g. a pull secret) and broad create/manage access to Secrets they generate dynamically with names unknown ahead of time. Because that broad access genuinely can't use resourceNames, this combination is likely to appear in many legitimate operator ClusterRoles — making this a meaningful blind spot rather than a rare edge case.

I understand fixing this properly likely means converting CKV2_K8S_5 from a declarative YAML graph check into a Python-based graph check that iterates rules entries individually (similar to how WildcardRoles.py / CKV_K8S_49 iterates conf.get("rules")), while still preserving the RoleBinding/ClusterRoleBindingRole/ClusterRole graph traversal. Happy to attempt a PR for this, but wanted to check preference first:

  1. Rewrite CKV2_K8S_5 in place with per-rule-entry correlation (my preference, for consistency with how CKV2_K8S_5 fails when resources: ["secrets"] even if resourceNames are specified #7110 was resolved), or
  2. Add this as a new, separate check instead, or
  3. Something else the team has already planned for the CKV2_K8S_1-5 family (given the overlap with CKV2_K8S_{1-5} do not check apiGroups of ClusterRoleBindings #6765).

Checkov Version: 3.2.334 (reproduced against the current ReadAllSecrets.yaml definition on main)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions