Skip to content

feat: scoped multi-namespace RBAC mode (Role per namespace, no ClusterRole)#1162

Open
michal-marszalek-h2oai wants to merge 1 commit into
stakater:masterfrom
michal-marszalek-h2oai:951-namespace-scope-rbac
Open

feat: scoped multi-namespace RBAC mode (Role per namespace, no ClusterRole)#1162
michal-marszalek-h2oai wants to merge 1 commit into
stakater:masterfrom
michal-marszalek-h2oai:951-namespace-scope-rbac

Conversation

@michal-marszalek-h2oai

Copy link
Copy Markdown

What

Adds a scoped multi-namespace mode: a third RBAC posture between the existing watchGlobally: true (cluster-wide, needs a ClusterRole) and single-namespace mode. You give Reloader an explicit list of namespaces, and it watches exactly those — with a namespace-scoped Role + RoleBinding created in each, no ClusterRole, 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) forbid ClusterRole grants, and one-install-per-namespace is wasteful. This closes that gap: one install, scoped permissions, no cluster-scoped objects.

How

Helm

  • New reloader.namespaces value, active when watchGlobally: false. Accepts either a YAML list (["team-a","team-b"]) or a comma-separated string ("team-a,team-b") for consistency with ignoreNamespaces / namespaceSelector.
  • The release namespace is always auto-included (needed for the meta-info ConfigMap, HA leases, and events).
  • role.yaml / rolebinding.yaml range over the deduped namespace set, emitting one Role + RoleBinding per namespace. The rule set is factored into a shared reloader-namespaced-rules helper to avoid duplication. The RoleBinding subject is the ServiceAccount in the release namespace (standard cross-namespace binding).
  • deployment.yaml passes --namespaces=<csv> and omits KUBERNETES_NAMESPACE in scoped mode.
  • A fail guard rejects the contradictory watchGlobally: true + namespaces combination.

Binary

  • New --namespaces flag (StringSlice, consistent with the other namespace flags).
  • resolveWatchNamespaces() picks the watched set with precedence: --namespacesKUBERNETES_NAMESPACE → all namespaces.
  • Controller creation loops over the watched namespaces (each informer is already namespace-scoped, so no informer/handler changes were needed).
  • --namespaces-to-ignore is now only honored in global mode (watchGlobally: true); in single-namespace and scoped modes the watched set is already explicit, mirroring how --namespace-selector is already gated.

Backward compatibility

Fully backward compatible. With reloader.namespaces empty (the default), rendered output is identical to today for both watchGlobally: true and watchGlobally: false (verified by diffing rendered manifests against the previous templates — the only delta is one cosmetic blank line removed by a cleaner apiVersion block).

Testing

  • make build, go vet ./..., gofmt — clean.
  • Unit test for resolveWatchNamespaces (all precedence cases).
  • New scoped-namespaces e2e case in 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 lint verified across modes: scoped mode produces a Role+RoleBinding per namespace, zero ClusterRole, correct --namespaces arg; list and string inputs render identically; the fail guard fires on the bad combo.

Example:

helm upgrade --install reloader stakater/reloader \
  --namespace reloader-system \
  --set reloader.watchGlobally=false \
  --set "reloader.namespaces={team-a,team-b}"
# Roles in reloader-system, team-a, team-b — no ClusterRole.

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>
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