diff --git a/k8s/bases/infrastructure/cluster-security-exceptions/controller-rbac.yaml b/k8s/bases/infrastructure/cluster-security-exceptions/controller-rbac.yaml index b6c595281..bae5e7a4b 100644 --- a/k8s/bases/infrastructure/cluster-security-exceptions/controller-rbac.yaml +++ b/k8s/bases/infrastructure/cluster-security-exceptions/controller-rbac.yaml @@ -23,8 +23,6 @@ spec: posture: - controlID: C-0053 action: ignore - - controlID: C-0007 - action: ignore - controlID: C-0037 action: ignore - controlID: C-0031 @@ -36,16 +34,19 @@ spec: # NOTE: RBAC-object controls are NOT suppressible here. Kubescape anchors # their findings on the RBAC Role/ClusterRole/binding object, not on a # namespaced workload, so a namespaceSelector match never applies (proven: - # the namespaced velero-server Role kept failing despite `velero` being - # listed below). They are handled by explicit kind+name `match.resources` CRs: + # the namespaced velero-server Role kept failing C-0187 despite `velero` + # being listed below; and 19 of C-0007's 26 findings terminate on + # cluster-scoped ClusterRoleBindings whose namespace is empty). They are + # handled by explicit kind+name `match.resources` CRs: # - C-0187 (wildcard RBAC, CIS-5.1.3) -> wildcard-rbac.yaml # - C-0015 (list Kubernetes secrets) -> secret-reader-rbac.yaml + # - C-0007 (delete-capable roles) -> delete-rbac.yaml # - C-0002 (exec into container) -> exec-into-container-rbac.yaml - # Headlamp is fixed at the root (SA scoped to read-only cluster-reader); Flux - # and Velero are exempted by-design in wildcard-rbac.yaml. - # The other RBAC controls still listed here (C-0053, C-0007, C-0037, C-0031, - # C-0063, C-0035, C-0188) have the same namespaceSelector limitation and - # should migrate to kind+name matching too - tracked separately. + # The other RBAC controls kept here (C-0053, C-0037, C-0031, C-0063, + # C-0035, C-0188) share this limitation; migrate them the same way if they + # surface failing objects. Headlamp is fixed at the root (SA scoped to + # read-only cluster-reader); Flux and Velero are exempted by-design in + # wildcard-rbac.yaml. - controlID: C-0188 action: ignore match: diff --git a/k8s/bases/infrastructure/cluster-security-exceptions/delete-rbac.yaml b/k8s/bases/infrastructure/cluster-security-exceptions/delete-rbac.yaml new file mode 100644 index 000000000..c6eff79c2 --- /dev/null +++ b/k8s/bases/infrastructure/cluster-security-exceptions/delete-rbac.yaml @@ -0,0 +1,125 @@ +--- +# Delete-capable-RBAC exception (Kubescape C-0007, "Roles with delete +# capabilities") for the infrastructure controllers and platform/app service +# accounts that require delete permissions BY DESIGN — GitOps reconcilers, +# operators, backup/restore, storage, virtualisation, cert/DB/secret managers, +# and tenant deployers all delete the resources they own. +# +# Why this is a separate CR matched by kind+name, and NOT a namespaceSelector +# entry in controller-rbac.yaml (mirrors wildcard-rbac.yaml / C-0187): Kubescape +# attaches an RBAC finding to the Role/ClusterRole/binding object, not to a +# namespaced workload, so a namespaceSelector never matches it. 19 of C-0007's +# 26 findings terminate on cluster-scoped ClusterRoleBindings whose namespace is +# empty — a namespace match can NEVER cover those. Matching each offending +# binding by kind+name is the mechanism that actually works. +# +# Deliberately explicit (one entry per binding) so C-0007 still flags any NEW or +# accidental delete-capable grant elsewhere — that residual signal is wanted. +# Names are regex-compared, so the one revision-hashed entry (the Crossplane +# provider CRB) matches by pattern and survives provider re-installs; the +# tofu-controller `tf-runner` RoleBinding is created on demand per Terraform +# apply (the exception no-ops while it is absent). Keep this list reconciled +# against a fresh scan (it can only be re-verified live once the scanner mount +# fix lands — see the kubescape HelmRelease postRenderer / helm-charts#862). +apiVersion: kubescape.io/v1beta1 +kind: ClusterSecurityException +metadata: + name: delete-capable-rbac-by-design +spec: + reason: >- + Infrastructure controllers, operators and tenant deployers legitimately + require delete permissions by design (GitOps reconciliation, operator + lifecycle, backup/restore, storage, virtualisation, cert/DB/secret + management). Each offending Role/binding is matched by kind+name so C-0007 + still catches any new or accidental delete-capable grant. + posture: + - controlID: C-0007 + action: ignore + match: + resources: + # --- cluster-scoped: ClusterRoleBinding -> ClusterRole (19) --- + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cdi-sa # CDI (KubeVirt storage import) + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cert-manager-controller-issuers + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cert-manager-controller-clusterissuers + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cert-manager-controller-certificates + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cert-manager-controller-challenges + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cloudnative-pg # CNPG Postgres operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: cluster-reconciler-flux-system # Flux kustomize/helm controllers + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: flux-operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: coroot-operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: crossplane-admin # Crossplane core (Group crossplane-masters) + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + # The trailing hash is the provider-revision hash, rewritten on every + # provider re-install, so match it by pattern — exception name matching + # is regex-based (kubescape/opa-utils exceptions comparator regexCompare), + # the same mechanism that lets the literal names above match. + name: "^crossplane:provider:provider-upjet-github-[0-9a-f]+:system$" + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: flagger # progressive delivery + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: "kro:controller" # KRO resource-graph operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: ksail-operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: kube-prometheus-stack-operator + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: kubevirt-apiserver + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: kubevirt-controller + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: longhorn-bind # Longhorn distributed storage + - apiGroup: rbac.authorization.k8s.io + kind: ClusterRoleBinding + name: reloader-reloader-role-binding # config/secret change reloader + # --- namespaced: RoleBinding -> Role/ClusterRole (7) --- + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: ascoachingogvaner # tenant deployer SA (edit) + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: "kyverno:admission-controller" + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: "kyverno:cleanup-controller" + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: longhorn + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + # FRAGILE: tofu-controller creates this on demand per Terraform apply; + # it is absent between runs (the exception simply no-ops while absent). + name: tf-runner + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: velero-server # backup/restore + - apiGroup: rbac.authorization.k8s.io + kind: RoleBinding + name: wedding-app # tenant deployer SA (edit) diff --git a/k8s/bases/infrastructure/cluster-security-exceptions/kustomization.yaml b/k8s/bases/infrastructure/cluster-security-exceptions/kustomization.yaml index b7ec0c387..2b585463b 100644 --- a/k8s/bases/infrastructure/cluster-security-exceptions/kustomization.yaml +++ b/k8s/bases/infrastructure/cluster-security-exceptions/kustomization.yaml @@ -6,6 +6,7 @@ resources: - batch-workloads.yaml - cilium-network-policies.yaml - controller-rbac.yaml + - delete-rbac.yaml - exec-into-container-rbac.yaml - gitops-managed-cronjobs.yaml - health-probes.yaml diff --git a/k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml b/k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml index 7ba709750..c5fc89d12 100644 --- a/k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml +++ b/k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml @@ -73,9 +73,6 @@ data: { "controlID": "^C-0053$" }, - { - "controlID": "^C-0007$" - }, { "controlID": "^C-0037$" }, @@ -150,6 +147,42 @@ data: } ] }, + { + "name": "delete-capable-rbac-by-design", + "policyType": "postureExceptionPolicy", + "actions": [ + "alertOnly" + ], + "reason": "Controllers, operators and tenant deployers require delete permissions by design (C-0007). Mirrors the operator CSE (delete-rbac.yaml) by kind+name: C-0007 findings terminate on the RBAC binding objects themselves (19 of 26 on cluster-scoped ClusterRoleBindings whose namespace is empty), so a namespace designator can never cover them.", + "resources": [ + { + "designatorType": "Attributes", + "attributes": { + "kind": "^ClusterRoleBinding$", + "name": "^(cdi-sa|cert-manager-controller-(issuers|clusterissuers|certificates|challenges)|cloudnative-pg|cluster-reconciler-flux-system|flux-operator|coroot-operator|crossplane-admin|crossplane:provider:provider-upjet-github-[0-9a-f]+:system|flagger|kro:controller|ksail-operator|kube-prometheus-stack-operator|kubevirt-apiserver|kubevirt-controller|longhorn-bind|reloader-reloader-role-binding)$" + } + }, + { + "designatorType": "Attributes", + "attributes": { + "kind": "^RoleBinding$", + "name": "^(ascoachingogvaner|kyverno:admission-controller|kyverno:cleanup-controller|longhorn|tf-runner|velero-server|wedding-app)$" + } + }, + { + "designatorType": "Attributes", + "attributes": { + "kind": "^Group$", + "name": "^crossplane-masters$" + } + } + ], + "posturePolicies": [ + { + "controlID": "^C-0007$" + } + ] + }, { "name": "health-probes", "policyType": "postureExceptionPolicy",