From 9ca44ec23ca38d734ada21a1a974354819e5d0c1 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Wed, 25 Mar 2026 17:05:06 +0100 Subject: [PATCH 01/13] chore: Describe RBAC rules, remove unnecessary rules --- .../opensearch-operator/templates/roles.yaml | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index e890523..3b92f05 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -5,6 +5,8 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: + # For automatic cluster domain detection: nodes are listed/watched to find a node to + # proxy through, and nodes/proxy is used to read kubelet info that contains the cluster domain. - apiGroups: - "" resources: @@ -12,19 +14,25 @@ rules: verbs: - list - watch - # For automatic cluster domain detection - apiGroups: - "" resources: - nodes/proxy verbs: - get + # Manage core workload resources created per OpenSearchCluster. + # All resources are applied via Server-Side Apply (create + patch) and tracked for + # orphan cleanup (list + delete). The controller watches all of these via .owns() (watch). + # get is required by the ReconciliationPaused strategy, which calls client.get() instead + # of apply_patch() when reconciliation is paused. + # update is NOT needed: SSA uses patch (HTTP PATCH), not update (HTTP PUT). + # - configmaps: per-rolegroup configuration files mounted into pods + # - serviceaccounts: per-rolegroup ServiceAccounts for workload pods + # - services: per-rolegroup and discovery Services - apiGroups: - "" resources: - configmaps - - endpoints - - pods - serviceaccounts - services verbs: @@ -33,8 +41,10 @@ rules: - get - list - patch - - update - watch + # RoleBindings bind the product ClusterRole to each per-rolegroup ServiceAccount so that + # workload pods have the permissions they need at runtime. + # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). - apiGroups: - rbac.authorization.k8s.io resources: @@ -45,8 +55,9 @@ rules: - get - list - patch - - update - watch + # StatefulSets drive the OpenSearch node pods. + # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). - apiGroups: - apps resources: @@ -57,8 +68,9 @@ rules: - get - list - patch - - update - watch + # PodDisruptionBudgets limit voluntary disruptions during rolling upgrades and maintenance. + # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). - apiGroups: - policy resources: @@ -69,7 +81,6 @@ rules: - get - list - patch - - update - watch - apiGroups: - apiextensions.k8s.io @@ -86,17 +97,22 @@ rules: - list - watch {{- end }} + # Listeners (stackable-listener-operator CRD) expose OpenSearch endpoints via a + # cluster-level abstraction. Applied via SSA, tracked for orphan cleanup, watched via .owns(). + # get is also used directly in dereference.rs to fetch the discovery service Listener. - apiGroups: - listeners.stackable.tech resources: - listeners verbs: + - create + - delete - get - list - - watch - patch - - create - - delete + - watch + # Events are emitted by the controller to report reconciliation results (e.g. errors, + # status changes) visible via kubectl describe / kubectl get events. - apiGroups: - events.k8s.io resources: @@ -104,6 +120,9 @@ rules: verbs: - create - patch + # The primary CRD: the controller watches OpenSearchCluster objects to trigger reconciliation + # and reads them during reconcile. patch is NOT needed here — the operator only writes + # to the /status subresource (see rule below). - apiGroups: - {{ include "operator.name" . }}.stackable.tech resources: @@ -111,14 +130,17 @@ rules: verbs: - get - list - - patch - watch + # Status subresource: the controller calls apply_patch_status() after each reconcile to + # update conditions (Available, Degraded, etc.) on the OpenSearchCluster object. - apiGroups: - {{ include "operator.name" . }}.stackable.tech resources: - {{ include "operator.name" . }}clusters/status verbs: - patch + # The operator creates per-rolegroup RoleBindings that bind the product ClusterRole to + # workload ServiceAccounts. bind permission on the product ClusterRole is required for that. - apiGroups: - rbac.authorization.k8s.io resources: @@ -135,6 +157,10 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: + # OpenSearch pods need read access to their own namespace resources at runtime: + # - configmaps: read configuration (e.g. opensearch.yml, log4j2.properties) + # - secrets: read TLS certificates and credentials mounted into the pod + # - serviceaccounts: read own ServiceAccount metadata (e.g. for token projection) - apiGroups: - "" resources: @@ -143,6 +169,7 @@ rules: - serviceaccounts verbs: - get + # OpenSearch pods emit Kubernetes Events (e.g. via the Stackable logging framework). - apiGroups: - events.k8s.io resources: @@ -150,6 +177,9 @@ rules: verbs: - create - patch + # Required on OpenShift: allows OpenSearch pods to run with the nonroot-v2 + # SecurityContextConstraint, which permits running as a non-root UID without a specific + # seccomp profile. - apiGroups: - security.openshift.io resources: From 4f152cea23cac0608cfc5f2562605e94d7afd46e Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:43:01 +0200 Subject: [PATCH 02/13] chore: Add missing comment on rule --- deploy/helm/opensearch-operator/templates/roles.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 3b92f05..1789bf5 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -82,6 +82,8 @@ rules: - list - patch - watch + # Required for maintaining the CRDs within the operator (including the conversion webhook info). + # Also for the startup condition check before the controller can run. - apiGroups: - apiextensions.k8s.io resources: From 18e39884d9677afe67d3f702b196f48b82a95267 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:43:23 +0200 Subject: [PATCH 03/13] chore: Remove the get for customresourcedefinitions for the operator clusterrole --- deploy/helm/opensearch-operator/templates/roles.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 1789bf5..babe54f 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -89,7 +89,6 @@ rules: resources: - customresourcedefinitions verbs: - - get # Required to maintain the CRD. The operator needs to do this, as it needs to enter e.g. it's # generated certificate in the conversion webhook. {{- if .Values.maintenance.customResourceDefinitions.maintain }} From 30f1b198e00c846dcf3866f8d90ebc4204460ab6 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:46:12 +0200 Subject: [PATCH 04/13] chore: Remove the nodes list/watch rule for the operator clusterrole --- deploy/helm/opensearch-operator/templates/roles.yaml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index babe54f..9cce26c 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -5,15 +5,7 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: - # For automatic cluster domain detection: nodes are listed/watched to find a node to - # proxy through, and nodes/proxy is used to read kubelet info that contains the cluster domain. - - apiGroups: - - "" - resources: - - nodes - verbs: - - list - - watch + # For automatic cluster domain detection. - apiGroups: - "" resources: From c672fbb92d04bbf540483b0728d6a78ae487c87f Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:53:45 +0200 Subject: [PATCH 05/13] chore: Remove the configmaps/secrets/serviceaccounts get rule for the product clusterrole --- deploy/helm/opensearch-operator/templates/roles.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 9cce26c..253b245 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -150,18 +150,6 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: - # OpenSearch pods need read access to their own namespace resources at runtime: - # - configmaps: read configuration (e.g. opensearch.yml, log4j2.properties) - # - secrets: read TLS certificates and credentials mounted into the pod - # - serviceaccounts: read own ServiceAccount metadata (e.g. for token projection) - - apiGroups: - - "" - resources: - - configmaps - - secrets - - serviceaccounts - verbs: - - get # OpenSearch pods emit Kubernetes Events (e.g. via the Stackable logging framework). - apiGroups: - events.k8s.io From 097076322fff73fda42f18e70a98a1649ad6860e Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:57:43 +0200 Subject: [PATCH 06/13] chore: Always allow customresourcedefinitions list/watch. Required for startup condition regardless of CRD maintenance --- deploy/helm/opensearch-operator/templates/roles.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 253b245..14bd11f 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -86,10 +86,10 @@ rules: {{- if .Values.maintenance.customResourceDefinitions.maintain }} - create - patch + {{- end }} # Required for startup condition - list - watch - {{- end }} # Listeners (stackable-listener-operator CRD) expose OpenSearch endpoints via a # cluster-level abstraction. Applied via SSA, tracked for orphan cleanup, watched via .owns(). # get is also used directly in dereference.rs to fetch the discovery service Listener. From 6121eba7cde5f92054d967655b7ff7399adbc643 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 10:58:39 +0200 Subject: [PATCH 07/13] fix: Gate the openshift rules --- deploy/helm/opensearch-operator/templates/roles.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 14bd11f..4b61dc4 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -158,6 +158,7 @@ rules: verbs: - create - patch +{{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} # Required on OpenShift: allows OpenSearch pods to run with the nonroot-v2 # SecurityContextConstraint, which permits running as a non-root UID without a specific # seccomp profile. @@ -169,3 +170,4 @@ rules: - nonroot-v2 verbs: - use +{{ end }} From 833009b876dc080f2112a5faf9413cec010b5e75 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 11:04:11 +0200 Subject: [PATCH 08/13] chore: Simplify RBAC rule comments --- .../opensearch-operator/templates/roles.yaml | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 4b61dc4..fdebb43 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -13,14 +13,7 @@ rules: verbs: - get # Manage core workload resources created per OpenSearchCluster. - # All resources are applied via Server-Side Apply (create + patch) and tracked for - # orphan cleanup (list + delete). The controller watches all of these via .owns() (watch). - # get is required by the ReconciliationPaused strategy, which calls client.get() instead - # of apply_patch() when reconciliation is paused. - # update is NOT needed: SSA uses patch (HTTP PATCH), not update (HTTP PUT). - # - configmaps: per-rolegroup configuration files mounted into pods - # - serviceaccounts: per-rolegroup ServiceAccounts for workload pods - # - services: per-rolegroup and discovery Services + # Applied via SSA, tracked for orphan cleanup, and owned by the controller. - apiGroups: - "" resources: @@ -34,9 +27,8 @@ rules: - list - patch - watch - # RoleBindings bind the product ClusterRole to each per-rolegroup ServiceAccount so that - # workload pods have the permissions they need at runtime. - # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). + # RoleBinding created per role group to bind the product ClusterRole to the workload + # ServiceAccount. Applied via SSA, tracked for orphan cleanup, and owned by the controller. - apiGroups: - rbac.authorization.k8s.io resources: @@ -48,8 +40,8 @@ rules: - list - patch - watch - # StatefulSets drive the OpenSearch node pods. - # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). + # StatefulSet created per role group. Applied via SSA, tracked for orphan cleanup, and + # owned by the controller. - apiGroups: - apps resources: @@ -61,8 +53,8 @@ rules: - list - patch - watch - # PodDisruptionBudgets limit voluntary disruptions during rolling upgrades and maintenance. - # Applied via SSA, tracked for orphan cleanup, and watched via .owns(). + # PodDisruptionBudget created per role group. Applied via SSA, tracked for orphan cleanup, + # and owned by the controller. - apiGroups: - policy resources: @@ -90,9 +82,8 @@ rules: # Required for startup condition - list - watch - # Listeners (stackable-listener-operator CRD) expose OpenSearch endpoints via a - # cluster-level abstraction. Applied via SSA, tracked for orphan cleanup, watched via .owns(). - # get is also used directly in dereference.rs to fetch the discovery service Listener. + # Listener created per role group for external access. Applied via SSA, tracked for orphan + # cleanup, and owned by the controller. - apiGroups: - listeners.stackable.tech resources: @@ -104,8 +95,7 @@ rules: - list - patch - watch - # Events are emitted by the controller to report reconciliation results (e.g. errors, - # status changes) visible via kubectl describe / kubectl get events. + # Required to report reconciliation results and warnings back to the OpenSearchCluster object. - apiGroups: - events.k8s.io resources: @@ -113,9 +103,7 @@ rules: verbs: - create - patch - # The primary CRD: the controller watches OpenSearchCluster objects to trigger reconciliation - # and reads them during reconcile. patch is NOT needed here — the operator only writes - # to the /status subresource (see rule below). + # Primary CRD: watched by Controller::new() and read during reconciliation. - apiGroups: - {{ include "operator.name" . }}.stackable.tech resources: @@ -124,16 +112,14 @@ rules: - get - list - watch - # Status subresource: the controller calls apply_patch_status() after each reconcile to - # update conditions (Available, Degraded, etc.) on the OpenSearchCluster object. + # Status subresource: updated at the end of every reconciliation. - apiGroups: - {{ include "operator.name" . }}.stackable.tech resources: - {{ include "operator.name" . }}clusters/status verbs: - patch - # The operator creates per-rolegroup RoleBindings that bind the product ClusterRole to - # workload ServiceAccounts. bind permission on the product ClusterRole is required for that. + # Required to bind the product ClusterRole to the per-rolegroup ServiceAccount. - apiGroups: - rbac.authorization.k8s.io resources: @@ -150,7 +136,7 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: - # OpenSearch pods emit Kubernetes Events (e.g. via the Stackable logging framework). + # Allows OpenSearch pods to emit Kubernetes events. - apiGroups: - events.k8s.io resources: @@ -159,9 +145,7 @@ rules: - create - patch {{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} - # Required on OpenShift: allows OpenSearch pods to run with the nonroot-v2 - # SecurityContextConstraint, which permits running as a non-root UID without a specific - # seccomp profile. + # Required on OpenShift to allow the OpenSearch pods to run as a non-root user. - apiGroups: - security.openshift.io resources: From e9090cf4ff68707b91ae538cfc96221534d6ab9a Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 11:05:44 +0200 Subject: [PATCH 09/13] chore: Remove the events.k8s.io rule from the product ClusterRole unless the products actually emit Kubernetes events --- deploy/helm/opensearch-operator/templates/roles.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index fdebb43..792449b 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -136,14 +136,6 @@ metadata: labels: {{- include "operator.labels" . | nindent 4 }} rules: - # Allows OpenSearch pods to emit Kubernetes events. - - apiGroups: - - events.k8s.io - resources: - - events - verbs: - - create - - patch {{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} # Required on OpenShift to allow the OpenSearch pods to run as a non-root user. - apiGroups: From 6479f5b999ca9d445c472a9e7c531529d1238555 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 11:09:34 +0200 Subject: [PATCH 10/13] chore: Group rbac.authorization.k8s.io rules together --- .../opensearch-operator/templates/roles.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/roles.yaml index 792449b..5bd7182 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/roles.yaml @@ -40,6 +40,15 @@ rules: - list - patch - watch + # Required to bind the product ClusterRole to the per-rolegroup ServiceAccount. + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + resourceNames: + - {{ include "operator.name" . }}-clusterrole # StatefulSet created per role group. Applied via SSA, tracked for orphan cleanup, and # owned by the controller. - apiGroups: @@ -119,15 +128,6 @@ rules: - {{ include "operator.name" . }}clusters/status verbs: - patch - # Required to bind the product ClusterRole to the per-rolegroup ServiceAccount. - - apiGroups: - - rbac.authorization.k8s.io - resources: - - clusterroles - verbs: - - bind - resourceNames: - - {{ include "operator.name" . }}-clusterrole --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole From 02af32174bdc3d4894e01e528de61ccfafbbb38d Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 11:12:03 +0200 Subject: [PATCH 11/13] chore: Split the roles.yaml into separate files for clusterrole-operator.yaml and clusterrole-product.yaml --- .../{roles.yaml => clusterrole-operator.yaml} | 20 +----------------- .../templates/clusterrole-product.yaml | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) rename deploy/helm/opensearch-operator/templates/{roles.yaml => clusterrole-operator.yaml} (87%) create mode 100644 deploy/helm/opensearch-operator/templates/clusterrole-product.yaml diff --git a/deploy/helm/opensearch-operator/templates/roles.yaml b/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml similarity index 87% rename from deploy/helm/opensearch-operator/templates/roles.yaml rename to deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml index 5bd7182..fec9ef3 100644 --- a/deploy/helm/opensearch-operator/templates/roles.yaml +++ b/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml @@ -1,3 +1,4 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -128,22 +129,3 @@ rules: - {{ include "operator.name" . }}clusters/status verbs: - patch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ include "operator.name" . }}-clusterrole - labels: - {{- include "operator.labels" . | nindent 4 }} -rules: -{{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} - # Required on OpenShift to allow the OpenSearch pods to run as a non-root user. - - apiGroups: - - security.openshift.io - resources: - - securitycontextconstraints - resourceNames: - - nonroot-v2 - verbs: - - use -{{ end }} diff --git a/deploy/helm/opensearch-operator/templates/clusterrole-product.yaml b/deploy/helm/opensearch-operator/templates/clusterrole-product.yaml new file mode 100644 index 0000000..8fa9248 --- /dev/null +++ b/deploy/helm/opensearch-operator/templates/clusterrole-product.yaml @@ -0,0 +1,21 @@ +--- +# Product ClusterRole: bound (via per OpenSearchCluster RoleBinding) to the ServiceAccount that +# OpenSearch workload pods run as. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "operator.name" . }}-clusterrole + labels: + {{- include "operator.labels" . | nindent 4 }} +rules: +{{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} + # Required on OpenShift to allow the OpenSearch pods to run as a non-root user. + - apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - nonroot-v2 + verbs: + - use +{{ end }} From 35a08394faf687e6a1d493b3004385311fa9f332 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 11:13:29 +0200 Subject: [PATCH 12/13] chore: Fix comment --- .../opensearch-operator/templates/clusterrole-operator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml b/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml index fec9ef3..819a6e3 100644 --- a/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml +++ b/deploy/helm/opensearch-operator/templates/clusterrole-operator.yaml @@ -113,7 +113,7 @@ rules: verbs: - create - patch - # Primary CRD: watched by Controller::new() and read during reconciliation. + # Primary CRD: watched by the controller and read during reconciliation. - apiGroups: - {{ include "operator.name" . }}.stackable.tech resources: From c7b7ff34158df07de2e7279cf5835642636c2544 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 2 Apr 2026 12:02:30 +0200 Subject: [PATCH 13/13] chore: Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a2301..9175fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- Document Helm deployed RBAC permissions and remove unnecessary permissions ([#129]). + +[#129]: https://github.com/stackabletech/opensearch-operator/pull/129 + ## [26.3.0] - 2026-03-16 ## [26.3.0-rc1] - 2026-03-16