diff --git a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml index e34c0f4e23e..17ed396db57 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml +++ b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -47,3 +48,4 @@ rules: - get - patch - update +{{- end }} diff --git a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml index 7bf204cc8b9..e9142ae63f7 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml +++ b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -15,3 +16,4 @@ subjects: - kind: ServiceAccount name: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }} namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml index 78191cbc52d..b09c68601f9 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml +++ b/docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml @@ -72,6 +72,17 @@ manager: ## tolerations: [] +## RBAC configuration +## +rbac: + ## Cluster-scoped RBAC resources (ClusterRole/ClusterRoleBinding) + ## + clusterScope: + # Set to false to skip cluster-scoped RBAC, useful when the operator + # should be restricted to a single namespace or when cluster-wide + # permissions are managed externally. + enabled: true + ## Helper RBAC roles for managing custom resources ## rbacHelpers: diff --git a/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-role.yaml b/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-role.yaml index 502b3899761..51a64071eb8 100644 --- a/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-role.yaml +++ b/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-role.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -56,3 +57,4 @@ rules: verbs: - create - patch +{{- end }} diff --git a/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml b/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml index 7bf204cc8b9..e9142ae63f7 100644 --- a/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml +++ b/docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -15,3 +16,4 @@ subjects: - kind: ServiceAccount name: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }} namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/docs/book/src/getting-started/testdata/project/dist/chart/values.yaml b/docs/book/src/getting-started/testdata/project/dist/chart/values.yaml index a1d7b288c26..4443b9b4e8a 100644 --- a/docs/book/src/getting-started/testdata/project/dist/chart/values.yaml +++ b/docs/book/src/getting-started/testdata/project/dist/chart/values.yaml @@ -72,6 +72,17 @@ manager: ## tolerations: [] +## RBAC configuration +## +rbac: + ## Cluster-scoped RBAC resources (ClusterRole/ClusterRoleBinding) + ## + clusterScope: + # Set to false to skip cluster-scoped RBAC, useful when the operator + # should be restricted to a single namespace or when cluster-wide + # permissions are managed externally. + enabled: true + ## Helper RBAC roles for managing custom resources ## rbacHelpers: diff --git a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml index e34c0f4e23e..17ed396db57 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -47,3 +48,4 @@ rules: - get - patch - update +{{- end }} diff --git a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml index 7bf204cc8b9..e9142ae63f7 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml @@ -1,3 +1,4 @@ +{{- if .Values.rbac.clusterScope.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -15,3 +16,4 @@ subjects: - kind: ServiceAccount name: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }} namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml index 78191cbc52d..b09c68601f9 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml @@ -72,6 +72,17 @@ manager: ## tolerations: [] +## RBAC configuration +## +rbac: + ## Cluster-scoped RBAC resources (ClusterRole/ClusterRoleBinding) + ## + clusterScope: + # Set to false to skip cluster-scoped RBAC, useful when the operator + # should be restricted to a single namespace or when cluster-wide + # permissions are managed externally. + enabled: true + ## Helper RBAC roles for managing custom resources ## rbacHelpers: diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go index a84f19196aa..cfca2aa09d0 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go @@ -1491,7 +1491,12 @@ func (t *HelmTemplater) addConditionalWrappers(yamlContent string, resource *uns // Metrics RBAC depends on metrics being enabled return fmt.Sprintf("{{- if .Values.metrics.enable }}\n%s{{- end }}\n", yamlContent) } - // Essential RBAC (controller-manager, leader-election, manager roles) - always enabled + // Cluster-scoped RBAC (ClusterRole/ClusterRoleBinding) - conditional on rbac.clusterScope + // This allows operators to be deployed without cluster-wide permissions when needed + if kind == kindClusterRole || kind == kindClusterRoleBinding { + return fmt.Sprintf("{{- if .Values.rbac.clusterScope.enabled }}\n%s{{- end }}\n", yamlContent) + } + // Namespace-scoped RBAC (Role/RoleBinding, ServiceAccount) - always enabled // These are required for the controller to function properly return yamlContent case kind == kindValidatingWebhook || kind == kindMutatingWebhook: diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go index 0926415886a..7e73ff4c36f 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go @@ -394,8 +394,8 @@ metadata: Expect(result).To(ContainSubstring("{{- end }}")) }) - It("should NOT add conditionals to essential resources", func() { - // Test essential RBAC + It("should add rbac.clusterScope conditional for essential ClusterRole", func() { + // Test essential ClusterRole gets rbac.clusterScope conditional clusterRoleResource := &unstructured.Unstructured{} clusterRoleResource.SetAPIVersion("rbac.authorization.k8s.io/v1") clusterRoleResource.SetKind("ClusterRole") @@ -408,7 +408,26 @@ metadata: result := templater.ApplyHelmSubstitutions(content, clusterRoleResource) - // Should NOT wrap essential RBAC with conditionals + // Should wrap ClusterRole with rbac.clusterScope conditional + Expect(result).To(ContainSubstring("{{- if .Values.rbac.clusterScope.enabled }}")) + Expect(result).To(ContainSubstring("{{- end }}")) + }) + + It("should NOT add conditionals to namespace-scoped RBAC resources", func() { + // Test namespace-scoped Role does NOT get conditional + roleResource := &unstructured.Unstructured{} + roleResource.SetAPIVersion("rbac.authorization.k8s.io/v1") + roleResource.SetKind("Role") + roleResource.SetName("test-project-leader-election-role") + + content := `apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-project-leader-election-role` + + result := templater.ApplyHelmSubstitutions(content, roleResource) + + // Should NOT wrap namespace-scoped Role with conditionals Expect(result).NotTo(ContainSubstring("{{- if .Values")) }) @@ -570,6 +589,24 @@ metadata: Expect(result).To(ContainSubstring("{{- if .Values.rbacHelpers.enable }}")) Expect(result).To(ContainSubstring("{{- end }}")) }) + + It("should add rbac.clusterScope conditional for essential ClusterRoleBinding", func() { + bindingResource := &unstructured.Unstructured{} + bindingResource.SetAPIVersion("rbac.authorization.k8s.io/v1") + bindingResource.SetKind("ClusterRoleBinding") + bindingResource.SetName("test-project-manager-rolebinding") + + content := `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: test-project-manager-rolebinding` + + result := templater.ApplyHelmSubstitutions(content, bindingResource) + + // Should wrap ClusterRoleBinding with rbac.clusterScope conditional + Expect(result).To(ContainSubstring("{{- if .Values.rbac.clusterScope.enabled }}")) + Expect(result).To(ContainSubstring("{{- end }}")) + }) }) Context("chart.fullname templating", func() { diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go index 427967fd6d1..e74fe6d02bf 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go @@ -113,7 +113,18 @@ manager: f.addDeploymentConfig(&buf) // RBAC configuration - buf.WriteString(`## Helper RBAC roles for managing custom resources + buf.WriteString(`## RBAC configuration +## +rbac: + ## Cluster-scoped RBAC resources (ClusterRole/ClusterRoleBinding) + ## + clusterScope: + # Set to false to skip cluster-scoped RBAC, useful when the operator + # should be restricted to a single namespace or when cluster-wide + # permissions are managed externally. + enabled: true + +## Helper RBAC roles for managing custom resources ## rbacHelpers: # Install convenience admin/editor/viewer roles for CRDs diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic_test.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic_test.go index ee3a82ab0bd..29618b61b7d 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic_test.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic_test.go @@ -55,6 +55,9 @@ var _ = Describe("HelmValuesBasic", func() { Expect(content).To(ContainSubstring("envOverrides: {}")) Expect(content).To(ContainSubstring("metrics:")) Expect(content).To(ContainSubstring("prometheus:")) + Expect(content).To(ContainSubstring("rbac:")) + Expect(content).To(ContainSubstring("clusterScope:")) + Expect(content).To(ContainSubstring("enabled: true")) Expect(content).To(ContainSubstring("rbacHelpers:")) Expect(content).To(ContainSubstring("imagePullSecrets: []")) }) @@ -92,6 +95,9 @@ var _ = Describe("HelmValuesBasic", func() { Expect(content).To(ContainSubstring("args: []")) Expect(content).To(ContainSubstring("metrics:")) Expect(content).To(ContainSubstring("prometheus:")) + Expect(content).To(ContainSubstring("rbac:")) + Expect(content).To(ContainSubstring("clusterScope:")) + Expect(content).To(ContainSubstring("enabled: true")) Expect(content).To(ContainSubstring("rbacHelpers:")) Expect(content).To(ContainSubstring("imagePullSecrets: []")) }) @@ -320,6 +326,29 @@ var _ = Describe("HelmValuesBasic", func() { }) }) + Context("rbac.clusterScope configuration", func() { + BeforeEach(func() { + valuesTemplate = &HelmValuesBasic{ + HasWebhooks: false, + } + valuesTemplate.InjectProjectName("test-project") + err := valuesTemplate.SetTemplateDefaults() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should have rbac.clusterScope.enabled set to true by default", func() { + content := valuesTemplate.GetBody() + Expect(content).To(ContainSubstring("rbac:")) + Expect(content).To(ContainSubstring("clusterScope:")) + Expect(content).To(ContainSubstring("enabled: true")) + }) + + It("should include description comment for clusterScope", func() { + content := valuesTemplate.GetBody() + Expect(content).To(ContainSubstring("Cluster-scoped RBAC resources")) + }) + }) + Context("Port configuration", func() { Context("with default ports", func() { BeforeEach(func() { diff --git a/testdata/project-v4-with-plugins/dist/chart/values.yaml b/testdata/project-v4-with-plugins/dist/chart/values.yaml index 63f0e479b77..daf2d73ef5e 100644 --- a/testdata/project-v4-with-plugins/dist/chart/values.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/values.yaml @@ -80,6 +80,17 @@ manager: ## tolerations: [] +## RBAC configuration +## +rbac: + ## Cluster-scoped RBAC resources (ClusterRole/ClusterRoleBinding) + ## + clusterScope: + # Set to false to skip cluster-scoped RBAC, useful when the operator + # should be restricted to a single namespace or when cluster-wide + # permissions are managed externally. + enabled: true + ## Helper RBAC roles for managing custom resources ## rbacHelpers: