Skip to content

🐛 fix(controller): make KollectClusterScope enforceable at runtime (RBAC + watch) - #306

Merged
konih merged 4 commits into
mainfrom
fix/clusterscope-watch
Aug 18, 2026
Merged

🐛 fix(controller): make KollectClusterScope enforceable at runtime (RBAC + watch)#306
konih merged 4 commits into
mainfrom
fix/clusterscope-watch

Conversation

@konih

@konih konih commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Independent of #304 — mergeable in any order, no textual overlap.

Two commits, both about the ceiling actually taking effect in a real cluster.

1. The manager has no RBAC to read KollectClusterScope

scope.LoadCluster lists KollectClusterScope on every KollectClusterTarget reconcile, in enforceClusterScopePolicy for KollectClusterInventory, and in both cluster-kind validating webhooks — but no kubebuilder marker ever declared that access. Neither config/rbac/role.yaml nor the chart ClusterRole grants it. kollectscopes is granted; its cluster-scoped sibling is not.

In an RBAC-enforcing cluster the manager cache cannot start an informer for a type it may not list, so LoadCluster fails: cluster-target reconcile errors and requeues forever, and since both cluster webhooks are failurePolicy=fail, admission rejects every KollectClusterTarget and KollectClusterInventory write with load KollectClusterScope. envtest does not enforce RBAC and the e2e suites do not exercise the cluster kinds, which is why nothing caught it.

Fixed with markers on both controllers, task manifests, the chart ClusterRole (rendered only when not tenantMode, which is where cluster kinds are served), and hack/test/cluster_scope_rbac_test.sh — a regression lock shaped like the existing core_events_rbac_test.sh, wired into CI. Verified red with the fix stashed.

2. Ceiling edits did not re-reconcile targets

The controller watched KollectProfile and Namespace but not the ceiling, and no SyncPeriod is set — so editing a KollectClusterScope changed nothing until an unrelated event or the 10h resync. Tightening allowedGVKs left offending targets collecting; widening it left correctly degraded targets stuck. With #304 adding the reconcile-time GVK check, that lag is the difference between a ceiling that is enforced and one that is merely declared.

The map function does not filter to the enforced scope on purpose: LoadCluster resolves the ceiling as the lowest-named object of all of them, so any scope write can change which one is enforced. Fan-out is bounded by the number of cluster targets.

Left alone deliberately: KollectClusterInventory shares enforceClusterScopePolicy and has the identical lag on sinkRefs; the namespaced KollectTarget/KollectScope pair has it too. Neither regresses here.

Gates: new RBAC lock red-to-green, task lint (golangci v2 + arch-lint) clean, task verify ok, task lint:shell clean, task helm-test 40/40, task scrub ok, controller unit tests green. envtest suites are CI-only on this host and fail identically on unmodified main here. The actionlint style finding at ci.yaml:207 is pre-existing and only shifted by the added step.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rnal/controller/kollectclustertarget_controller.go 87.50% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

konih added 3 commits August 18, 2026 11:24
`scope.LoadCluster` lists KollectClusterScope on every KollectClusterTarget
reconcile, in `enforceClusterScopePolicy` for KollectClusterInventory, and in
both cluster-kind validating webhooks — but no kubebuilder marker ever declared
that access, so neither `config/rbac/role.yaml` nor the chart ClusterRole
granted it. `kollectscopes` (the namespaced ceiling) was granted; its
cluster-scoped sibling was not.

In an RBAC-enforcing cluster the manager cache cannot start an informer for a
type it may not list, so `LoadCluster` fails: cluster-target reconcile returns an
error and requeues forever, and because both cluster webhooks are
failurePolicy=fail, admission rejects every KollectClusterTarget and
KollectClusterInventory write with "load KollectClusterScope". envtest does not
enforce RBAC and the e2e suites do not exercise the cluster kinds, which is why
no gate caught it.

- Markers on both controllers that reach LoadCluster, mirroring how
  `kollectscopes` is declared on the two namespaced controllers.
- `config/rbac/role.yaml` regenerated via `task manifests` (one line).
- Chart ClusterRole extended to match. It is hand-maintained and rendered only
  when `not tenantMode`, which is exactly where the cluster kinds are served, so
  the namespaced role.yaml template deliberately stays untouched.
- `hack/test/cluster_scope_rbac_test.sh`, a regression lock in the shape of
  core_events_rbac_test.sh: it asserts the markers, the generated role, and the
  chart template together. Verified red with the fix stashed.

Gates: new lock red→green, lint:shell clean, helm-test 40/40, verify ok, scrub
ok. The actionlint style finding at ci.yaml:207 is pre-existing on main and only
shifted by the added step.
…e writes

The KollectClusterTarget controller watched KollectProfile and Namespace but not
the ceiling itself, and the manager sets no SyncPeriod, so editing a
KollectClusterScope changed nothing until an unrelated event or the 10h resync.
Tightening `allowedGVKs` left offending targets collecting; widening it left
correctly degraded targets stuck Degraded. With PR #304 adding the reconcile-time
GVK check, that lag is now the difference between a ceiling that is enforced and
one that is merely declared.

The map function deliberately does not filter to the currently enforced scope:
`scope.LoadCluster` resolves the ceiling as the lowest-named KollectClusterScope
of all of them, so creating, renaming, or deleting any of them can change which
object is enforced. Every cluster target is enqueued on any scope write. The
fan-out is bounded by the number of cluster targets — a platform-level,
cluster-scoped kind — and reconcile is idempotent.

Scope of this commit is KollectClusterTarget only. KollectClusterInventory shares
`enforceClusterScopePolicy` and has the identical lag on `sinkRefs`, and the
namespaced KollectTarget/KollectScope pair has it too; both are left alone here
rather than fixed silently, and neither regresses.

Test first: mapClusterScopeToClusterTargets returns one namespace-free request
per cluster target for a scope object that is not the enforced one, and nil for a
non-scope object. Red as undefined before the change.

Gates: lint (golangci v2 + arch-lint) clean, verify ok, scrub ok, controller unit
tests green. envtest suites remain CI-only on this host.
The watch in the previous commit changes the failure mode of a missing
`kollectclusterscopes` grant. Today the grant is missing everywhere and surfaces
lazily: `LoadCluster` errors per reconcile. Once the controller registers a watch
on the type, the informer starts with the manager, and a cache that cannot sync a
watched type fails the controller's Start — `cmd/main.go` exits non-zero on
`mgr.Start`, so the pod crash-loops.

That is safe on this branch, where the grant and the watch land together, and
safe for a normal `helm upgrade`. It is not safe for the skew this project's
install model invites: RBAC is Helm-managed while operators pin `image.tag`, so a
new image against an un-bumped chart now crash-loops rather than degrading one
controller.

Placed under "Upgrade the operator" next to the existing image-pinning guidance,
away from the behaviour-changes section that the docs branch for #304 edits, so
the two do not collide on rebase.

Gates: lint:markdown 0 issues, scrub ok.
@konih
konih force-pushed the fix/clusterscope-watch branch from 84479ee to 6bfb811 Compare August 18, 2026 09:24
… grant

The OLM ClusterServiceVersion template carries a hand-maintained copy of
the manager rules. Adding kollectclusterscopes to config/rbac/role.yaml
without it left the bundle short of a permission the manager now needs to
watch, so an OLM-installed operator would fail its cache sync and exit.

hack/test/dist_olm_bundle_test.sh caught the drift; it is the existing
lock for this surface, so cluster_scope_rbac_test.sh points at it rather
than duplicating the assertion.
@sonarqubecloud

Copy link
Copy Markdown

@konih
konih merged commit aa12fde into main Aug 18, 2026
37 checks passed
@konih
konih deleted the fix/clusterscope-watch branch August 18, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant