From 29e23c85776a8fd44e658fe555d963f7f004adb1 Mon Sep 17 00:00:00 2001 From: Tim Thacker Date: Sun, 27 Sep 2026 21:51:23 +1000 Subject: [PATCH 1/2] fix(aws): let the managed EKS scan list CronJobs Co-Authored-By: Claude Opus 5.5 (1M context) --- aws-integration-setup/scripts/setup-eks-managed-scan.sh | 2 +- .../terraform-v6/modules/k8s-resources/main.tf | 4 +--- .../terraform-v6/modules/k8s-resources/managed_scan.tf | 2 +- .../modules/k8s-resources/tests/k8s_resources.tftest.hcl | 2 +- aws-integration-setup/terraform/README.md | 4 ++-- aws-integration-setup/terraform/modules/k8s-resources/main.tf | 4 +--- .../terraform/modules/k8s-resources/managed_scan.tf | 2 +- .../modules/k8s-resources/tests/k8s_resources.tftest.hcl | 2 +- 8 files changed, 9 insertions(+), 13 deletions(-) diff --git a/aws-integration-setup/scripts/setup-eks-managed-scan.sh b/aws-integration-setup/scripts/setup-eks-managed-scan.sh index 7684674..d7e76a8 100644 --- a/aws-integration-setup/scripts/setup-eks-managed-scan.sh +++ b/aws-integration-setup/scripts/setup-eks-managed-scan.sh @@ -40,7 +40,7 @@ RBAC_RESOURCES=( nodes namespaces pods services persistentvolumeclaims persistentvolumes configmaps resourcequotas limitranges serviceaccounts deployments.apps daemonsets.apps statefulsets.apps replicasets.apps - jobs.batch + jobs.batch cronjobs.batch ingresses.networking.k8s.io networkpolicies.networking.k8s.io endpointslices.discovery.k8s.io roles.rbac.authorization.k8s.io rolebindings.rbac.authorization.k8s.io diff --git a/aws-integration-setup/terraform-v6/modules/k8s-resources/main.tf b/aws-integration-setup/terraform-v6/modules/k8s-resources/main.tf index a9d66f5..e21fda8 100644 --- a/aws-integration-setup/terraform-v6/modules/k8s-resources/main.tf +++ b/aws-integration-setup/terraform-v6/modules/k8s-resources/main.tf @@ -50,9 +50,7 @@ resource "kubernetes_cluster_role" "nullify_readonly_role" { } # Kinds the collector lists, kept identical to the managed-scan ClusterRole - # (managed_scan.tf). Secrets are omitted: list returns values. "jobs" is - # granted ahead of the monorepo's fix/k8s-deployment-matching-review stack - # (#12807 / #12830). + # (managed_scan.tf). Secrets are omitted: list returns values. rule { api_groups = [""] resources = [ diff --git a/aws-integration-setup/terraform-v6/modules/k8s-resources/managed_scan.tf b/aws-integration-setup/terraform-v6/modules/k8s-resources/managed_scan.tf index a047f5a..5c052ab 100644 --- a/aws-integration-setup/terraform-v6/modules/k8s-resources/managed_scan.tf +++ b/aws-integration-setup/terraform-v6/modules/k8s-resources/managed_scan.tf @@ -13,7 +13,7 @@ locals { }, { api_groups = ["batch"] - resources = ["jobs"] + resources = ["jobs", "cronjobs"] }, { api_groups = ["networking.k8s.io"] diff --git a/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl b/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl index 39ebcda..f6cc0ef 100644 --- a/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl +++ b/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl @@ -158,7 +158,7 @@ run "managed_scan_rbac_only" { } assert { - condition = tolist(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].api_groups) == tolist(["batch"]) && toset(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].resources) == toset(["jobs"]) + condition = tolist(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].api_groups) == tolist(["batch"]) && toset(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].resources) == toset(["jobs", "cronjobs"]) error_message = "Rule 2 must grant exactly the batch API group kinds the scanner lists" } diff --git a/aws-integration-setup/terraform/README.md b/aws-integration-setup/terraform/README.md index 9427fe4..ca3a328 100644 --- a/aws-integration-setup/terraform/README.md +++ b/aws-integration-setup/terraform/README.md @@ -86,7 +86,7 @@ terraform/ | Cluster endpoint | Any, including private-only | Public endpoint that admits Nullify's egress IPs | | AWS setup | `enable_kubernetes_integration = true`, S3 bucket or access point | `eks-managed-scan-access`: one access entry per cluster | | Kubernetes setup | `k8s-resources` (default) or the `nullify-k8s-collector` Helm chart | A list-only `nullify-readonly` ClusterRole and ClusterRoleBinding, applied by `k8s-resources` with `enable_collector = false, enable_managed_scan_rbac = true` | -| Kinds read | The managed scan's kinds, plus cronjobs, ingressclasses, horizontalpodautoscalers, poddisruptionbudgets, storageclasses, csidrivers, runtimeclasses, priorityclasses, flowschemas, certificatesigningrequests and customresourcedefinitions. `k8s-resources` and chart 0.4.0 and later grant the same set, `list` only. Charts before 0.4.0 grant an older set; see that version's `templates/clusterrole.yaml` | See [RBAC granted to Nullify](#rbac-granted-to-nullify-authorization--rbac) | +| Kinds read | The managed scan's kinds, plus ingressclasses, horizontalpodautoscalers, poddisruptionbudgets, storageclasses, csidrivers, runtimeclasses, priorityclasses, flowschemas, certificatesigningrequests and customresourcedefinitions. `k8s-resources` and chart 0.4.0 and later grant the same set, `list` only. Charts before 0.4.0 grant an older set; see that version's `templates/clusterrole.yaml` | See [RBAC granted to Nullify](#rbac-granted-to-nullify-authorization--rbac) | | Upgrades | You update the image | None | Both modes can run on the same role: IRSA uses `sts:AssumeRoleWithWebIdentity`, the managed scan uses `sts:AssumeRole` with the external ID. @@ -103,7 +103,7 @@ The access entry carries the Kubernetes group `nullify-readonly`, bound to the C |---|---| | core | nodes, namespaces, pods, services, persistentvolumeclaims, persistentvolumes, configmaps, resourcequotas, limitranges, serviceaccounts | | apps | deployments, daemonsets, statefulsets, replicasets | -| batch | jobs | +| batch | jobs, cronjobs | | networking.k8s.io | ingresses, networkpolicies | | discovery.k8s.io | endpointslices | | rbac.authorization.k8s.io | roles, rolebindings, clusterroles, clusterrolebindings | diff --git a/aws-integration-setup/terraform/modules/k8s-resources/main.tf b/aws-integration-setup/terraform/modules/k8s-resources/main.tf index a9d66f5..e21fda8 100644 --- a/aws-integration-setup/terraform/modules/k8s-resources/main.tf +++ b/aws-integration-setup/terraform/modules/k8s-resources/main.tf @@ -50,9 +50,7 @@ resource "kubernetes_cluster_role" "nullify_readonly_role" { } # Kinds the collector lists, kept identical to the managed-scan ClusterRole - # (managed_scan.tf). Secrets are omitted: list returns values. "jobs" is - # granted ahead of the monorepo's fix/k8s-deployment-matching-review stack - # (#12807 / #12830). + # (managed_scan.tf). Secrets are omitted: list returns values. rule { api_groups = [""] resources = [ diff --git a/aws-integration-setup/terraform/modules/k8s-resources/managed_scan.tf b/aws-integration-setup/terraform/modules/k8s-resources/managed_scan.tf index a047f5a..5c052ab 100644 --- a/aws-integration-setup/terraform/modules/k8s-resources/managed_scan.tf +++ b/aws-integration-setup/terraform/modules/k8s-resources/managed_scan.tf @@ -13,7 +13,7 @@ locals { }, { api_groups = ["batch"] - resources = ["jobs"] + resources = ["jobs", "cronjobs"] }, { api_groups = ["networking.k8s.io"] diff --git a/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl b/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl index 39ebcda..f6cc0ef 100644 --- a/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl +++ b/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl @@ -158,7 +158,7 @@ run "managed_scan_rbac_only" { } assert { - condition = tolist(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].api_groups) == tolist(["batch"]) && toset(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].resources) == toset(["jobs"]) + condition = tolist(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].api_groups) == tolist(["batch"]) && toset(kubernetes_cluster_role_v1.nullify_readonly[0].rule[2].resources) == toset(["jobs", "cronjobs"]) error_message = "Rule 2 must grant exactly the batch API group kinds the scanner lists" } From 43ad1acceeff4e9b5844a19a5cd657d7a66f7fc0 Mon Sep 17 00:00:00 2001 From: Tim Thacker Date: Sun, 27 Sep 2026 22:01:23 +1000 Subject: [PATCH 2/2] fix(aws): count cronjobs in the managed-scan kind tests and CloudFormation docs Co-Authored-By: Claude Opus 5.5 (1M context) --- aws-integration-setup/cloudformation/README.md | 6 +++--- .../modules/k8s-resources/tests/k8s_resources.tftest.hcl | 4 ++-- .../modules/k8s-resources/tests/k8s_resources.tftest.hcl | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws-integration-setup/cloudformation/README.md b/aws-integration-setup/cloudformation/README.md index 49ffc09..dee17b6 100644 --- a/aws-integration-setup/cloudformation/README.md +++ b/aws-integration-setup/cloudformation/README.md @@ -230,13 +230,13 @@ On an existing stack, add that line to the `update-stack` command above. ## Managed EKS scan (no in-cluster agent) -Nullify lists Kubernetes resources in your EKS cluster from Nullify's cloud-scan compute, using the read-only role this template creates. Nothing runs in the cluster. The scan only calls `list`, on 26 kinds: +Nullify lists Kubernetes resources in your EKS cluster from Nullify's cloud-scan compute, using the read-only role this template creates. Nothing runs in the cluster. The scan only calls `list`, on 27 kinds: | API group | Resources | |---|---| | core | nodes, namespaces, pods, services, persistentvolumeclaims, persistentvolumes, configmaps, resourcequotas, limitranges, serviceaccounts | | apps | deployments, daemonsets, statefulsets, replicasets | -| batch | jobs | +| batch | jobs, cronjobs | | networking.k8s.io | ingresses, networkpolicies | | discovery.k8s.io | endpointslices | | rbac.authorization.k8s.io | roles, rolebindings, clusterroles, clusterrolebindings | @@ -343,7 +343,7 @@ Other flags: `--role-arn` instead of `--customer-name`, `--authorization rbac` ( `setup-eks-managed-scan.sh verify` checks that: - the authentication mode supports access entries, the access entry exists with **only** the expected group, and **no** EKS access policy is associated; -- `kubectl auth can-i list --all-namespaces --as nullify-verify --as-group nullify-readonly` answers `yes` for all 26 resources, and `create pods`, `get`/`list secrets`, `get`/`create pods/exec`, `get`/`list pods/log`, `get`/`create pods/attach`, `get`/`create pods/portforward`, `get nodes/proxy`, and `escalate` / `bind` on clusterroles do not. `impersonate` users / serviceaccounts / groups is required `no` when `kubectl auth can-i` accepts those checks; otherwise it is skipped; +- `kubectl auth can-i list --all-namespaces --as nullify-verify --as-group nullify-readonly` answers `yes` for all 27 resources, and `create pods`, `get`/`list secrets`, `get`/`create pods/exec`, `get`/`list pods/log`, `get`/`create pods/attach`, `get`/`create pods/portforward`, `get nodes/proxy`, and `escalate` / `bind` on clusterroles do not. `impersonate` users / serviceaccounts / groups is required `no` when `kubectl auth can-i` accepts those checks; otherwise it is skipped; - `publicAccessCidrs` admits Nullify's egress IPs and does not contain `0.0.0.0/0`. `can-i` exercises Kubernetes RBAC only. It cannot see access-policy grants or prove that Nullify can reach the endpoint. Finish by confirming the cluster connects on the Nullify configure page. diff --git a/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl b/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl index f6cc0ef..fe17fd2 100644 --- a/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl +++ b/aws-integration-setup/terraform-v6/modules/k8s-resources/tests/k8s_resources.tftest.hcl @@ -140,8 +140,8 @@ run "managed_scan_rbac_only" { } assert { - condition = sum([for rule in slice(kubernetes_cluster_role_v1.nullify_readonly[0].rule, 0, 7) : length(rule.resources)]) == 26 - error_message = "The managed-scan ClusterRole must list exactly the 26 config kinds; secrets are omitted because list returns values" + condition = sum([for rule in slice(kubernetes_cluster_role_v1.nullify_readonly[0].rule, 0, 7) : length(rule.resources)]) == 27 + error_message = "The managed-scan ClusterRole must list exactly the 27 config kinds; secrets are omitted because list returns values" } # Pins the set, not just the size: swapping one kind for another keeps every diff --git a/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl b/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl index f6cc0ef..fe17fd2 100644 --- a/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl +++ b/aws-integration-setup/terraform/modules/k8s-resources/tests/k8s_resources.tftest.hcl @@ -140,8 +140,8 @@ run "managed_scan_rbac_only" { } assert { - condition = sum([for rule in slice(kubernetes_cluster_role_v1.nullify_readonly[0].rule, 0, 7) : length(rule.resources)]) == 26 - error_message = "The managed-scan ClusterRole must list exactly the 26 config kinds; secrets are omitted because list returns values" + condition = sum([for rule in slice(kubernetes_cluster_role_v1.nullify_readonly[0].rule, 0, 7) : length(rule.resources)]) == 27 + error_message = "The managed-scan ClusterRole must list exactly the 27 config kinds; secrets are omitted because list returns values" } # Pins the set, not just the size: swapping one kind for another keeps every