Skip to content

feat(helm): make cluster-scoped RBAC optional - #3459

Merged
purp merged 1 commit into
NVIDIA:mainfrom
ansjindal:feat/optional-cluster-scoped-rbac
Sep 28, 2026
Merged

purp merged 1 commit into
NVIDIA:mainfrom
ansjindal:feat/optional-cluster-scoped-rbac

Conversation

@ansjindal

@ansjindal ansjindal commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Makes the gateway chart's cluster-scoped RBAC optional so a cluster-admin can apply the ClusterRole and ClusterRoleBinding once and a namespace-admin can install and upgrade the OpenShell release without cluster-scoped permissions. Both new flags default to true, so existing installs are unchanged.

Related Issue

Fixes #3043

Changes

  • Add an rbac values block following the shape proposed on the issue: rbac.create, rbac.clusterScoped.create, rbac.clusterScoped.clusterRoleName, rbac.clusterScoped.clusterRoleBindingName.
  • Gate clusterrole.yaml and clusterrolebinding.yaml on rbac.clusterScoped.create, independent of server.drivers.kubernetes.workspaceMode.
  • Gate the namespaced gateway RBAC — the sandbox role.yaml / rolebinding.yaml and the gateway peer-role.yaml — on rbac.create. This is not merely a parent switch: in shared mode, Kubernetes escalation prevention stops an installer holding only the built-in admin ClusterRole from creating a Role that grants agents.x-k8s.io verbs it does not itself hold, so such an installer needs rbac.create=false with all gateway RBAC applied out of band. The certgen hook and credential driver RBAC keep their existing flags.
  • Add helpers that treat a missing rbac block as enabled, so helm upgrade --reuse-values from a pre-change release does not drop RBAC — the same pattern as openshell.workspaceResourcesEnabled.
  • Drive the ClusterRoleBinding roleRef from clusterRoleName so a separately applied ClusterRole can carry a cluster-admin-chosen name.
  • Add ci/values-namespace-admin.yaml, picked up automatically by the helm:lint overlay loop.
  • Document the cluster-admin / namespace-admin split, which flag an installer needs for its workspace mode and permissions, the cluster-scoped vs namespaced object inventory, and the migration path for an existing release, in the chart README and docs/kubernetes/setup.mdx. This also corrects the documented ClusterRole name, which is openshell-node-reader-<namespace>.
  • Extend deploy/helm/test-split-ownership.sh to assert a rbac.clusterScoped.create=false render emits no cluster-scoped objects while keeping the gateway ServiceAccount.

Notes for reviewers

Migration. Helm deletes objects that leave a release manifest, so setting the flag on a release that already owns the cluster-scoped objects deletes them, and the gateway loses TokenReview until a cluster-admin re-applies them. The docs prescribe annotating them with helm.sh/resource-policy=keep first; this was tested and hands ownership over with no gap.

peer-role.yaml. HA gateway rebalancing (#1868) landed while this PR was open and added an ungated namespaced Role/RoleBinding bound to the gateway ServiceAccount. Leaving it ungated would make rbac.create's contract false, so it is gated here too. It never blocked an install — the peer Role grants only pods get, which a built-in admin installer can create — so this is a contract fix, not a runtime one.

Rebase. Rebased onto current main, which required honoring two upstream changes to the same files: the reduced gateway Secret privileges (#3616, assertion deleted rather than updated) and the removed NetworkPolicy acknowledgement (#3677, dropped from the documented commands).

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added/updated (if applicable) — no existing E2E lane covers chart RBAC topology; verified on a live cluster instead, described below

mise run pre-commit (fmt + lint) exits 0 and fmt produces no diff. The lint half covers all 15 subtasks, including helm:lint across all CI overlays, helm:docs:check, helm:test, license:check, clippy, and markdown/mermaid.

Unit tests — new cases in clusterrole_test.yaml, clusterrolebinding_test.yaml, and a new rbac_test.yaml: default still renders every object; each flag omits the right ones; omission is independent of workspace mode; name overrides land in metadata.name and roleRef; serviceAccount.create=false still yields a correct binding subject; and legacy values with no rbac key still create RBAC. Gateway suite is 203 tests / 17 suites, workspace 6 / 1, plus test-split-ownership.sh, whose new assertion was negative-controlled by flipping the flag to confirm it fails.

Live cluster (k3d, agent-sandbox v1.0.2, gateway image pinned to the per-commit build for main HEAD, --kube-as-user impersonation of an identity holding only the built-in admin ClusterRole in one namespace, asserted unable to get or create ClusterRoles/ClusterRoleBindings). Three suites, all green:

  1. Namespace-admin install. Negative control reproduces the issue exactly (clusterroles ... is forbidden ... at the cluster scope). With rbac.clusterScoped.create=false, a cluster-admin applies the two cluster-scoped objects and the namespace-admin installs and upgrades; the release still owns the namespaced RBAC; the pre-created binding matches the release's ServiceAccount, which can create TokenReviews. With rbac.create=false, the installer needs no RBAC write permission at all and helm get manifest confirms the release claims no RBAC. serviceAccount.create=false with a pre-created ServiceAccount resolves correctly in the ClusterRoleBinding subject, RoleBinding subject, and pod serviceAccountName. Gateway logs are clean.
  2. Backward compatibility, 15/15. Each case starts from a release installed with the pre-change chart from main and upgrades it with this branch: default upgrade, --reuse-values, a pre-change values file, --set-json rbac=null, helm rollback to the pre-change revision, and uninstall leaving no cluster-scoped residue. Cluster-scoped objects survive and stay release-owned in every case.
  3. Workspace modes, 23/23. managed and operator install and upgrade as a namespace-admin with rbac.clusterScoped.create=false, roll out, and log no permission errors. Mode-specific ClusterRole grants were verified to land (managed gets namespace create/delete, cluster-wide sandboxes, serviceaccount create; operator gets cluster-wide sandboxes but not namespace create), with live kubectl auth can-i on the gateway SA matching per mode. In shared mode both documented remedies were verified, and the plain-admin failure it is documented against was reproduced.

Cluster-scope audit — every CI overlay plus defaults was scanned against 16 cluster-scoped kinds (SCC, ClusterIssuer, GatewayClass, CRD, PV, StorageClass, webhooks, and others). Each renders exactly ClusterRole + ClusterRoleBinding and nothing else, and zero with the flag off, confirming the object inventory the docs publish.

Not covered: OpenShift SCC admission and Route behavior on a real OpenShift cluster. The three OpenShift overlays pass render-level checks and lint, but the live path needs an OpenShift cluster; test:e2e-kubernetes may be worth applying.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable) — packaging change; no architecture/ doc covers chart RBAC topology

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@ansjindal
ansjindal force-pushed the feat/optional-cluster-scoped-rbac branch from c5c9173 to e876b74 Compare September 18, 2026 12:23
@ansjindal

Copy link
Copy Markdown
Contributor Author

I have read the DCO document and I hereby sign the DCO.

@ansjindal

Copy link
Copy Markdown
Contributor Author

recheck

@purp purp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

This PR implements the accepted cluster-scoped RBAC split in #3043, and the initial code review found no blocking defects. The default and legacy-values paths remain enabled, the custom-name and service-account cases are covered, and the operator-facing Helm and Kubernetes documentation is updated.

Blocking findings:

  • No blocking findings remain

Carried findings:

  • None

Non-blocking suggestions:

  • None
Gator metadata
  • Validation: Implements accepted issue #3043 with a focused Helm packaging and RBAC change
  • Docs: Fern Kubernetes setup documentation and chart documentation are updated
  • Checks: Current baseline branch, Helm lint, Trivy, and DCO checks are green; required Kubernetes E2E dispatch is pending
  • E2E: test:e2e-kubernetes required for the Helm Kubernetes RBAC topology change
  • Head SHA: e876b74eb9a83ecda9d1b1e349981cfea84c6cef
  • Base SHA: 4b2cb7f007da2f701422daf394643b0fd9d4daef
  • Merge base SHA: 4b2cb7f007da2f701422daf394643b0fd9d4daef
  • Patch ID: b3b39191943f12e797db7e99d09a9bff104c0f06
  • Gator payload: 9
  • Review mode: initial
  • Previous reviewed SHA: none
  • Review budget exhausted: no
  • Maintainer decision required: no
  • Next state: gator:in-review until the required Kubernetes E2E workflow is confirmed queued

@purp purp added gator:in-review Gator is reviewing or awaiting PR review feedback test:e2e-kubernetes Requires Kubernetes end-to-end coverage labels Sep 19, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e-kubernetes applied for e876b74. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute Kubernetes HA and credential-driver E2E after building the required gateway, sandbox, and supervisor images once. This is an optional proof-of-life suite; failures are visible in the workflow run but do not publish a required CI gate status.

@purp purp added gator:blocked Gator is blocked by process or repository gates test:e2e Requires end-to-end coverage and removed gator:in-review Gator is reviewing or awaiting PR review feedback labels Sep 19, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied for e876b74. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@purp purp added gator:approval-needed Gator completed review; maintainer approval needed and removed gator:blocked Gator is blocked by process or repository gates labels Sep 21, 2026
@purp

purp commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

gator-agent

Maintainer Review Nudge

This PR has been in gator:approval-needed for more than 48 business hours with no maintainer approval.

@NVIDIA/openshell-maintainers @NVIDIA/openshell-codeowners @mrunalp @sjenning @derekwaynecarr, can someone review and either approve, request changes, or close this out?

@johntmyers johntmyers added this to the OpenShell 0.1.0 milestone Sep 23, 2026
@purp purp modified the milestones: OpenShell 0.1.0, OpenShell 0.1.1 Sep 23, 2026
@purp

purp commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Will approve. Slated for v0.1.1 next week. Do not merge until then.

@ansjindal
ansjindal force-pushed the feat/optional-cluster-scoped-rbac branch 2 times, most recently from 409361b to 6802f19 Compare September 28, 2026 03:00
The gateway chart always rendered the ClusterRole and ClusterRoleBinding, so
every install and upgrade required cluster-admin even when only namespaced
objects were needed. Installers that are namespace-admin GitOps or platform
controllers could not run the release at all, and clusters where cluster-scoped
RBAC is owned by a separate team had no supported way to split the install.

Add an rbac values block so a cluster-admin can apply the cluster-scoped objects
once and a namespace-admin can install and upgrade the release without
cluster-scoped permissions:

  rbac:
    create: true
    clusterScoped:
      create: true
      clusterRoleName: ""
      clusterRoleBindingName: ""

rbac.clusterScoped.create gates the ClusterRole and ClusterRoleBinding, and is
independent of the workspace mode. rbac.create additionally gates the namespaced
sandbox Role and RoleBinding, which matters because Kubernetes escalation
prevention stops an installer holding only the built-in admin role from creating
a Role that grants agents.x-k8s.io verbs it does not itself hold. The certgen
hook and credential driver RBAC keep their existing flags.

Both flags default to true, so current installs are unchanged. The helpers treat
a missing rbac block as enabled so upgrades with --reuse-values do not drop RBAC,
matching the existing workspaceResources pattern. The ClusterRoleBinding roleRef
follows clusterRoleName so a separately applied ClusterRole can carry a name the
cluster-admin chooses.

Document the migration for a release that already owns the cluster-scoped
objects: Helm deletes objects that leave the manifest, so annotate them with
helm.sh/resource-policy=keep before setting the flag, otherwise the gateway
loses TokenReview until a cluster-admin re-applies them.

Signed-off-by: ansjindal <ansjindal@nvidia.com>
@ansjindal
ansjindal force-pushed the feat/optional-cluster-scoped-rbac branch from 6802f19 to 93278a3 Compare September 28, 2026 03:20
@drew drew modified the milestones: OpenShell 0.1.1, OpenShell 0.1.5 Sep 28, 2026
@purp
purp self-requested a review September 28, 2026 05:49
@purp
purp enabled auto-merge September 28, 2026 05:49

@purp purp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚢

@purp
purp added this pull request to the merge queue Sep 28, 2026
@purp purp removed the gator:approval-needed Gator completed review; maintainer approval needed label Sep 28, 2026
Merged via the queue into NVIDIA:main with commit d009f30 Sep 28, 2026
166 of 168 checks passed
@drew drew modified the milestones: OpenShell 0.1.5, OpenShell 0.1.3 Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage test:e2e-kubernetes Requires Kubernetes end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(helm): gate cluster-scoped RBAC so the gateway chart can be installed without cluster-admin

4 participants