feat: scoped multi-namespace RBAC mode (Role per namespace, no ClusterRole)#1162
feat: scoped multi-namespace RBAC mode (Role per namespace, no ClusterRole)#1162michal-marszalek-h2oai wants to merge 2 commits into
Conversation
Add a third RBAC posture between watch-globally (ClusterRole) and single namespace: give Reloader an explicit list of namespaces to watch. The chart creates a namespace-scoped Role + RoleBinding in each listed namespace (no ClusterRole), and one install covers them all. Go: - new --namespaces flag / options.Namespaces - resolveWatchNamespaces() picks list -> KUBERNETES_NAMESPACE -> all - controller creation loops over the watched namespaces - namespaces-to-ignore is now only honored in global mode (watchGlobally=true); in single-namespace and scoped modes the watched set is already explicit Helm: - new reloader.namespaces value (active when watchGlobally=false); accepts either a YAML list or a comma-separated string for consistency with the sibling namespace options - reloader-watchNamespaces helper (release ns always auto-included, deduped) - shared reloader-namespaced-rules template reused per namespace - role.yaml/rolebinding.yaml range over the list; deployment passes --namespaces - --namespaces-to-ignore only rendered when watchGlobally=true - fail guard for watchGlobally=true + namespaces set Tests: unit test for resolveWatchNamespaces; scoped-namespaces e2e case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pe-rbac # Conflicts: # internal/pkg/cmd/reloader.go # internal/pkg/cmd/reloader_test.go
|
Hi @msafwankarim @Felix-Stakater 👋 Could a maintainer please click "Approve and run workflows" so CI can execute? Happy to address any review feedback once the checks are green. Thanks! 🙏 |
|
/loadtest |
Load Test Results (Full A/B Comparison)Comparing: Artifacts: Download |
|
👋 Heads-up on the failing E2E tests check in the latest run — I dug into it and I'm confident it's a pre-existing flaky test, not a regression from this PR. The failure is the CSI spec
Happy to open a separate PR to de-flake the CSI deployment specs (capture the reload baseline before triggering the change). A re-run should get this green in the meantime. |
What
Adds a scoped multi-namespace mode: a third RBAC posture between the existing
watchGlobally: true(cluster-wide, needs aClusterRole) and single-namespace mode. You give Reloader an explicit list of namespaces, and it watches exactly those — with a namespace-scopedRole+RoleBindingcreated in each, noClusterRole, from a single install.Why
Today watching N namespaces means either granting cluster-wide RBAC (
watchGlobally: true) or running N separate Reloader installs. Many clusters (multi-tenant / restricted RBAC) forbidClusterRolegrants, and one-install-per-namespace is wasteful. This closes that gap: one install, scoped permissions, no cluster-scoped objects.How
Helm
reloader.namespacesvalue, active whenwatchGlobally: false. Accepts either a YAML list (["team-a","team-b"]) or a comma-separated string ("team-a,team-b") for consistency withignoreNamespaces/namespaceSelector.role.yaml/rolebinding.yamlrange over the deduped namespace set, emitting oneRole+RoleBindingper namespace. The rule set is factored into a sharedreloader-namespaced-ruleshelper to avoid duplication. The RoleBinding subject is the ServiceAccount in the release namespace (standard cross-namespace binding).deployment.yamlpasses--namespaces=<csv>and omitsKUBERNETES_NAMESPACEin scoped mode.failguard rejects the contradictorywatchGlobally: true+namespacescombination.Binary
--namespacesflag (StringSlice, consistent with the other namespace flags).resolveWatchNamespaces()picks the watched set with precedence:--namespaces→KUBERNETES_NAMESPACE→ all namespaces.--namespaces-to-ignoreis now only honored in global mode (watchGlobally: true); in single-namespace and scoped modes the watched set is already explicit, mirroring how--namespace-selectoris already gated.Backward compatibility
Fully backward compatible. With
reloader.namespacesempty (the default), rendered output is identical to today for bothwatchGlobally: trueandwatchGlobally: false(verified by diffing rendered manifests against the previous templates — the only delta is one cosmetic blank line removed by a cleanerapiVersionblock).Testing
make build,go vet ./...,gofmt— clean.resolveWatchNamespaces(all precedence cases).test/e2e/flags/watch_globally_test.go: reloads in a listed namespace and in the auto-included release namespace; does not reload in an unlisted namespace.helm template/helm lintverified across modes: scoped mode produces aRole+RoleBindingper namespace, zeroClusterRole, correct--namespacesarg; list and string inputs render identically; thefailguard fires on the bad combo.Example: