From 18bf3be897ba736da59803c1be81d19b56657093 Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 13:07:43 -0400 Subject: [PATCH 1/7] feat(rbac): complete namespaced mode to reduce cluster-wide permissions Extends the existing --watch-namespaces support so the operator can run without cluster-wide RBAC on the resources it manages: - Generalize MakeScopedRBACObjects/Names to take a component so the collector and fluentd controllers can create namespaced Role/RoleBinding (previously only fluent-bit honored namespaced mode) - Add a Namespaced flag to the Collector and Fluentd reconcilers, wired to --watch-namespaces, mirroring the FluentBit controller - Clean up the per-instance (Cluster)RoleBinding on deletion (fills the existing TODOs); the shared (Cluster)Role is intentionally preserved - Expose operator.watchNamespaces as a first-class Helm value Refs fluent/fluent-operator#1281 Co-authored-by: Cursor Signed-off-by: Josh Baird --- .../templates/fluent-operator-deployment.yaml | 3 + charts/fluent-operator/values.yaml | 4 + cmd/fluent-manager/main.go | 14 +-- controllers/collector_controller.go | 96 ++++++++++++++++--- controllers/fluent_controller_finalizer.go | 58 ++++++++++- controllers/fluentbit_controller.go | 31 +++--- controllers/fluentd_controller.go | 39 +++++--- pkg/operator/rbac.go | 16 +++- 8 files changed, 209 insertions(+), 52 deletions(-) diff --git a/charts/fluent-operator/templates/fluent-operator-deployment.yaml b/charts/fluent-operator/templates/fluent-operator-deployment.yaml index 6089a2fd1..2417ac4e9 100644 --- a/charts/fluent-operator/templates/fluent-operator-deployment.yaml +++ b/charts/fluent-operator/templates/fluent-operator-deployment.yaml @@ -55,6 +55,9 @@ spec: {{- with .Values.operator.disableComponentControllers }} - --disable-component-controllers={{ . }} {{- end }} + {{- with .Values.operator.watchNamespaces }} + - --watch-namespaces={{ . }} + {{- end }} {{- with .Values.operator.livenessProbe }} livenessProbe: {{- toYaml . | nindent 10 }} diff --git a/charts/fluent-operator/values.yaml b/charts/fluent-operator/values.yaml index 73a4c025b..f3de5ed4e 100644 --- a/charts/fluent-operator/values.yaml +++ b/charts/fluent-operator/values.yaml @@ -103,6 +103,10 @@ operator: # myExampleLabel: someValue # -- Disable specific component controllers. Value can be "fluent-bit" or "fluentd" to disable that controller disableComponentControllers: "" + # -- Comma separated list of namespaces the operator should watch and manage resources in. When set, the + # operator scopes its cache and creates namespaced Roles/RoleBindings (instead of cluster-wide) for the + # agents it manages, allowing the operator's own RBAC to be reduced. Defaults to cluster scope when empty. + watchNamespaces: "" # -- Extra arguments for the Fluent Operator controller extraArgs: [] diff --git a/cmd/fluent-manager/main.go b/cmd/fluent-manager/main.go index 407022742..b7b994f76 100644 --- a/cmd/fluent-manager/main.go +++ b/cmd/fluent-manager/main.go @@ -232,9 +232,10 @@ func main() { } if err = (&controllers.CollectorReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Collector"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Collector"), + Scheme: mgr.GetScheme(), + Namespaced: namespacedController, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Collector") os.Exit(1) @@ -266,9 +267,10 @@ func main() { } if err = (&controllers.FluentdReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Fluentd"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Fluentd"), + Scheme: mgr.GetScheme(), + Namespaced: namespacedController, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Fluentd") os.Exit(1) diff --git a/controllers/collector_controller.go b/controllers/collector_controller.go index b8c9ccfa5..8ed77b674 100644 --- a/controllers/collector_controller.go +++ b/controllers/collector_controller.go @@ -43,8 +43,9 @@ import ( // CollectorReconciler reconciles a FluentBit object type CollectorReconciler struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme + Log logr.Logger + Scheme *runtime.Scheme + Namespaced bool } // collectorSecretRequeueInterval is how long to wait before re-checking for the @@ -99,6 +100,8 @@ func (r *CollectorReconciler) collectorsForSecret(ctx context.Context, obj clien // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=create;get;list;watch;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;delete;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get;list;watch;patch // +kubebuilder:rbac:groups=core,resources=pods,verbs=get // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch @@ -156,19 +159,30 @@ func (r *CollectorReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Install RBAC resources for the filter plugin kubernetes - cr, sa, crb := operator.MakeRBACObjects( - co.Name, - co.Namespace, - "collector", - co.Spec.RBACRules, - co.Spec.ServiceAccountAnnotations, - ) - // Deploy Fluent Bit Collector ClusterRole - if _, err := controllerutil.CreateOrPatch(ctx, r.Client, cr, r.mutate(cr, &co)); err != nil { + var role, sa, binding client.Object + if r.Namespaced { + role, sa, binding = operator.MakeScopedRBACObjects( + co.Name, + co.Namespace, + "collector", + co.Spec.RBACRules, + co.Spec.ServiceAccountAnnotations, + ) + } else { + role, sa, binding = operator.MakeRBACObjects( + co.Name, + co.Namespace, + "collector", + co.Spec.RBACRules, + co.Spec.ServiceAccountAnnotations, + ) + } + // Deploy Fluent Bit Collector (Cluster)Role + if _, err := controllerutil.CreateOrPatch(ctx, r.Client, role, r.mutate(role, &co)); err != nil { return ctrl.Result{}, err } - // Deploy Fluent Bit Collector ClusterRoleBinding - if _, err := controllerutil.CreateOrPatch(ctx, r.Client, crb, r.mutate(crb, &co)); err != nil { + // Deploy Fluent Bit Collector (Cluster)RoleBinding + if _, err := controllerutil.CreateOrPatch(ctx, r.Client, binding, r.mutate(binding, &co)); err != nil { return ctrl.Result{}, err } // Deploy Fluent Bit Collector ServiceAccount @@ -235,6 +249,36 @@ func (r *CollectorReconciler) mutate(obj client.Object, co *fluentbitv1alpha2.Co o.Subjects = expected.Subjects return nil } + case *rbacv1.Role: + // The Role is shared across all Collector instances in the namespace, so + // no per-instance controller reference is set on it. + expected, _, _ := operator.MakeScopedRBACObjects(co.Name, + co.Namespace, + "collector", + co.Spec.RBACRules, + co.Spec.ServiceAccountAnnotations, + ) + + return func() error { + o.Rules = expected.Rules + return nil + } + case *rbacv1.RoleBinding: + _, _, expected := operator.MakeScopedRBACObjects(co.Name, + co.Namespace, + "collector", + co.Spec.RBACRules, + co.Spec.ServiceAccountAnnotations, + ) + + return func() error { + o.RoleRef = expected.RoleRef + o.Subjects = expected.Subjects + if err := ctrl.SetControllerReference(co, o, r.Scheme); err != nil { + return err + } + return nil + } case *appsv1.StatefulSet: expected := operator.MakefbStatefulset(*co) @@ -274,7 +318,31 @@ func (r *CollectorReconciler) delete(ctx context.Context, co *fluentbitv1alpha2. if err := r.Delete(ctx, &sa); err != nil && !errors.IsNotFound(err) { return err } - // TODO: clusterrole, clusterrolebinding + + // Only the per-instance (Cluster)RoleBinding is removed here; the (Cluster)Role + // is shared across all Collector instances and must not be deleted. + if r.Namespaced { + _, _, rbName := operator.MakeScopedRBACNames(co.Name, "collector") + rolebinding := rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: rbName, + Namespace: co.Namespace, + }, + } + if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { + return err + } + } else { + _, _, crbName := operator.MakeRBACNames(co.Name, "collector") + crb := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: crbName, + }, + } + if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { + return err + } + } sts := appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/fluent_controller_finalizer.go b/controllers/fluent_controller_finalizer.go index b1b0745a9..c3e37339e 100644 --- a/controllers/fluent_controller_finalizer.go +++ b/controllers/fluent_controller_finalizer.go @@ -76,7 +76,31 @@ func (r *FluentdReconciler) delete(ctx context.Context, fd *fluentdv1alpha1.Flue if err := r.Delete(ctx, &sa); err != nil && !errors.IsNotFound(err) { return err } - // TODO: clusterrole, clusterrolebinding + + // Only the per-instance (Cluster)RoleBinding is removed here; the (Cluster)Role + // is shared across all Fluentd instances and must not be deleted. + if r.Namespaced { + _, _, rbName := operator.MakeScopedRBACNames(fd.Name, "fluentd") + rolebinding := rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: rbName, + Namespace: fd.Namespace, + }, + } + if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { + return err + } + } else { + _, _, crbName := operator.MakeRBACNames(fd.Name, "fluentd") + crb := rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: crbName, + }, + } + if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { + return err + } + } sts := appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ @@ -157,6 +181,38 @@ func (r *FluentdReconciler) mutate(obj client.Object, fd *fluentdv1alpha1.Fluent o.Subjects = expected.Subjects return nil } + case *rbacv1.Role: + // The Role is shared across all Fluentd instances in the namespace, so + // no per-instance controller reference is set on it. + expected, _, _ := operator.MakeScopedRBACObjects( + fd.Name, + fd.Namespace, + "fluentd", + fd.Spec.RBACRules, + fd.Spec.ServiceAccountAnnotations, + ) + + return func() error { + o.Rules = expected.Rules + return nil + } + case *rbacv1.RoleBinding: + _, _, expected := operator.MakeScopedRBACObjects( + fd.Name, + fd.Namespace, + "fluentd", + fd.Spec.RBACRules, + fd.Spec.ServiceAccountAnnotations, + ) + + return func() error { + o.RoleRef = expected.RoleRef + o.Subjects = expected.Subjects + if err := ctrl.SetControllerReference(fd, o, r.Scheme); err != nil { + return err + } + return nil + } case *appsv1.StatefulSet: expected := operator.MakeStatefulSet(*fd) diff --git a/controllers/fluentbit_controller.go b/controllers/fluentbit_controller.go index 8a4ef26b5..23880c67b 100644 --- a/controllers/fluentbit_controller.go +++ b/controllers/fluentbit_controller.go @@ -106,6 +106,8 @@ func (r *FluentBitReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( role, sa, binding = operator.MakeScopedRBACObjects( fb.Name, fb.Namespace, + "fluent-bit", + fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations, ) } else { @@ -218,7 +220,7 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *rbacv1.Role: - expected, _, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, fb.Spec.ServiceAccountAnnotations) + expected, _, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) return func() error { o.Rules = expected.Rules @@ -239,7 +241,7 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *corev1.ServiceAccount: - _, expected, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, fb.Spec.ServiceAccountAnnotations) + _, expected, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) return func() error { o.Annotations = expected.Annotations @@ -249,7 +251,7 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *rbacv1.RoleBinding: - _, _, expected := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, fb.Spec.ServiceAccountAnnotations) + _, _, expected := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) return func() error { o.Subjects = expected.Subjects o.RoleRef = expected.RoleRef @@ -289,28 +291,31 @@ func (r *FluentBitReconciler) delete(ctx context.Context, fb *fluentbitv1alpha2. } if r.Namespaced { - roleName, _, roleBindingName := operator.MakeScopedRBACNames(fb.Name) - role := rbacv1.Role{ + _, _, roleBindingName := operator.MakeScopedRBACNames(fb.Name, "fluent-bit") + // Only the RoleBinding is per-instance; the Role is shared across all + // FluentBit instances in the namespace, so it must not be deleted here. + rolebinding := rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: roleName, + Name: roleBindingName, Namespace: fb.Namespace, }, } - if err := r.Delete(ctx, &role); err != nil && !errors.IsNotFound(err) { + if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { return err } - - rolebinding := rbacv1.RoleBinding{ + } else { + _, _, crbName := operator.MakeRBACNames(fb.Name, "fluent-bit") + // Only the ClusterRoleBinding is per-instance; the ClusterRole is shared + // across all FluentBit instances, so it must not be deleted here. + crb := rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: roleBindingName, - Namespace: fb.Namespace, + Name: crbName, }, } - if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { + if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { return err } } - // TODO: clusterrole, clusterrolebinding ds := appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/fluentd_controller.go b/controllers/fluentd_controller.go index 6c99050b1..38c708ae7 100644 --- a/controllers/fluentd_controller.go +++ b/controllers/fluentd_controller.go @@ -43,8 +43,9 @@ const ( // FluentdReconciler reconciles a Fluentd object type FluentdReconciler struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme + Log logr.Logger + Scheme *runtime.Scheme + Namespaced bool } // +kubebuilder:rbac:groups=fluentd.fluent.io,resources=fluentds,verbs=get;list;watch;update @@ -54,6 +55,7 @@ type FluentdReconciler struct { // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get // +kubebuilder:rbac:groups=core,resources=serviceaccounts;services,verbs=get;list;watch;create;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=create;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;delete;get;list;watch;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -100,19 +102,30 @@ func (r *FluentdReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // Install RBAC resources for the filter plugin kubernetes - cr, sa, crb := operator.MakeRBACObjects( - fd.Name, - fd.Namespace, - fluentdLowercase, - fd.Spec.RBACRules, - fd.Spec.ServiceAccountAnnotations, - ) - // Deploy Fluentd ClusterRole - if _, err := controllerutil.CreateOrPatch(ctx, r.Client, cr, r.mutate(cr, &fd)); err != nil { + var role, sa, binding client.Object + if r.Namespaced { + role, sa, binding = operator.MakeScopedRBACObjects( + fd.Name, + fd.Namespace, + fluentdLowercase, + fd.Spec.RBACRules, + fd.Spec.ServiceAccountAnnotations, + ) + } else { + role, sa, binding = operator.MakeRBACObjects( + fd.Name, + fd.Namespace, + fluentdLowercase, + fd.Spec.RBACRules, + fd.Spec.ServiceAccountAnnotations, + ) + } + // Deploy Fluentd (Cluster)Role + if _, err := controllerutil.CreateOrPatch(ctx, r.Client, role, r.mutate(role, &fd)); err != nil { return ctrl.Result{}, err } - // Deploy Fluentd ClusterRoleBinding - if _, err := controllerutil.CreateOrPatch(ctx, r.Client, crb, r.mutate(crb, &fd)); err != nil { + // Deploy Fluentd (Cluster)RoleBinding + if _, err := controllerutil.CreateOrPatch(ctx, r.Client, binding, r.mutate(binding, &fd)); err != nil { return ctrl.Result{}, err } // Deploy Fluentd ServiceAccount diff --git a/pkg/operator/rbac.go b/pkg/operator/rbac.go index c2e1536ea..6e270e321 100644 --- a/pkg/operator/rbac.go +++ b/pkg/operator/rbac.go @@ -62,10 +62,12 @@ func MakeRBACObjects( func MakeScopedRBACObjects( name, - namespace string, + namespace, + component string, + additionalRules []rbacv1.PolicyRule, saAnnotations map[string]string, ) (*rbacv1.Role, *corev1.ServiceAccount, *rbacv1.RoleBinding) { - rName, saName, rbName := MakeScopedRBACNames(name) + rName, saName, rbName := MakeScopedRBACNames(name, component) r := rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: rName, @@ -80,6 +82,10 @@ func MakeScopedRBACObjects( }, } + if additionalRules != nil { + r.Rules = append(r.Rules, additionalRules...) + } + sa := corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: saName, @@ -116,8 +122,8 @@ func MakeRBACNames(name, component string) (string, string, string) { return cr, name, crb } -func MakeScopedRBACNames(name string) (string, string, string) { - r := "fluent:fluent-operator" - rb := fmt.Sprintf("fluent-operator-fluent-bit-%s", name) +func MakeScopedRBACNames(name, component string) (string, string, string) { + r := fmt.Sprintf("fluent-operator-%s", component) + rb := fmt.Sprintf("fluent-operator-%s-%s", component, name) return r, name, rb } From 3326ee2a3971386e91275c6f5eb7a16ee49eac62 Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 13:25:36 -0400 Subject: [PATCH 2/7] refactor(rbac): extract shared scope helpers to reduce duplication Collapse the duplicated namespaced-vs-cluster RBAC branching and the per-instance binding deletion across the FluentBit, Collector, and Fluentd controllers into operator.MakeRBACObjectsForScope and operator.DeletePerInstanceBinding. Co-authored-by: Cursor Signed-off-by: Josh Baird --- controllers/collector_controller.go | 56 +++++---------------- controllers/fluent_controller_finalizer.go | 25 +--------- controllers/fluentbit_controller.go | 55 +++++---------------- controllers/fluentd_controller.go | 31 ++++-------- pkg/operator/rbac.go | 57 +++++++++++++++++++--- 5 files changed, 84 insertions(+), 140 deletions(-) diff --git a/controllers/collector_controller.go b/controllers/collector_controller.go index 8ed77b674..14e276176 100644 --- a/controllers/collector_controller.go +++ b/controllers/collector_controller.go @@ -158,34 +158,21 @@ func (r *CollectorReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Install RBAC resources for the filter plugin kubernetes - var role, sa, binding client.Object - if r.Namespaced { - role, sa, binding = operator.MakeScopedRBACObjects( - co.Name, - co.Namespace, - "collector", - co.Spec.RBACRules, - co.Spec.ServiceAccountAnnotations, - ) - } else { - role, sa, binding = operator.MakeRBACObjects( - co.Name, - co.Namespace, - "collector", - co.Spec.RBACRules, - co.Spec.ServiceAccountAnnotations, - ) - } - // Deploy Fluent Bit Collector (Cluster)Role + // Reconcile the RBAC the agent needs, scoped to a namespace or the cluster. + role, sa, binding := operator.MakeRBACObjectsForScope( + r.Namespaced, + co.Name, + co.Namespace, + "collector", + co.Spec.RBACRules, + co.Spec.ServiceAccountAnnotations, + ) if _, err := controllerutil.CreateOrPatch(ctx, r.Client, role, r.mutate(role, &co)); err != nil { return ctrl.Result{}, err } - // Deploy Fluent Bit Collector (Cluster)RoleBinding if _, err := controllerutil.CreateOrPatch(ctx, r.Client, binding, r.mutate(binding, &co)); err != nil { return ctrl.Result{}, err } - // Deploy Fluent Bit Collector ServiceAccount if _, err := controllerutil.CreateOrPatch(ctx, r.Client, sa, r.mutate(sa, &co)); err != nil { return ctrl.Result{}, err } @@ -319,29 +306,8 @@ func (r *CollectorReconciler) delete(ctx context.Context, co *fluentbitv1alpha2. return err } - // Only the per-instance (Cluster)RoleBinding is removed here; the (Cluster)Role - // is shared across all Collector instances and must not be deleted. - if r.Namespaced { - _, _, rbName := operator.MakeScopedRBACNames(co.Name, "collector") - rolebinding := rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: rbName, - Namespace: co.Namespace, - }, - } - if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { - return err - } - } else { - _, _, crbName := operator.MakeRBACNames(co.Name, "collector") - crb := rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: crbName, - }, - } - if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { - return err - } + if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, co.Name, co.Namespace, "collector"); err != nil { + return err } sts := appsv1.StatefulSet{ diff --git a/controllers/fluent_controller_finalizer.go b/controllers/fluent_controller_finalizer.go index c3e37339e..f5d805a00 100644 --- a/controllers/fluent_controller_finalizer.go +++ b/controllers/fluent_controller_finalizer.go @@ -77,29 +77,8 @@ func (r *FluentdReconciler) delete(ctx context.Context, fd *fluentdv1alpha1.Flue return err } - // Only the per-instance (Cluster)RoleBinding is removed here; the (Cluster)Role - // is shared across all Fluentd instances and must not be deleted. - if r.Namespaced { - _, _, rbName := operator.MakeScopedRBACNames(fd.Name, "fluentd") - rolebinding := rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: rbName, - Namespace: fd.Namespace, - }, - } - if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { - return err - } - } else { - _, _, crbName := operator.MakeRBACNames(fd.Name, "fluentd") - crb := rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: crbName, - }, - } - if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { - return err - } + if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, fd.Name, fd.Namespace, "fluentd"); err != nil { + return err } sts := appsv1.StatefulSet{ diff --git a/controllers/fluentbit_controller.go b/controllers/fluentbit_controller.go index 23880c67b..98d0e672d 100644 --- a/controllers/fluentbit_controller.go +++ b/controllers/fluentbit_controller.go @@ -100,25 +100,15 @@ func (r *FluentBitReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Install RBAC resources for the filter plugin kubernetes - var role, sa, binding client.Object - if r.Namespaced { - role, sa, binding = operator.MakeScopedRBACObjects( - fb.Name, - fb.Namespace, - "fluent-bit", - fb.Spec.RBACRules, - fb.Spec.ServiceAccountAnnotations, - ) - } else { - role, sa, binding = operator.MakeRBACObjects( - fb.Name, - fb.Namespace, - "fluent-bit", - fb.Spec.RBACRules, - fb.Spec.ServiceAccountAnnotations, - ) - } + // Reconcile the RBAC the agent needs, scoped to a namespace or the cluster. + role, sa, binding := operator.MakeRBACObjectsForScope( + r.Namespaced, + fb.Name, + fb.Namespace, + "fluent-bit", + fb.Spec.RBACRules, + fb.Spec.ServiceAccountAnnotations, + ) if _, err := controllerutil.CreateOrPatch(ctx, r.Client, role, r.mutate(role, &fb)); err != nil { return ctrl.Result{}, err } @@ -290,31 +280,8 @@ func (r *FluentBitReconciler) delete(ctx context.Context, fb *fluentbitv1alpha2. return err } - if r.Namespaced { - _, _, roleBindingName := operator.MakeScopedRBACNames(fb.Name, "fluent-bit") - // Only the RoleBinding is per-instance; the Role is shared across all - // FluentBit instances in the namespace, so it must not be deleted here. - rolebinding := rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleBindingName, - Namespace: fb.Namespace, - }, - } - if err := r.Delete(ctx, &rolebinding); err != nil && !errors.IsNotFound(err) { - return err - } - } else { - _, _, crbName := operator.MakeRBACNames(fb.Name, "fluent-bit") - // Only the ClusterRoleBinding is per-instance; the ClusterRole is shared - // across all FluentBit instances, so it must not be deleted here. - crb := rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: crbName, - }, - } - if err := r.Delete(ctx, &crb); err != nil && !errors.IsNotFound(err) { - return err - } + if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, fb.Name, fb.Namespace, "fluent-bit"); err != nil { + return err } ds := appsv1.DaemonSet{ diff --git a/controllers/fluentd_controller.go b/controllers/fluentd_controller.go index 38c708ae7..8fd0c50d1 100644 --- a/controllers/fluentd_controller.go +++ b/controllers/fluentd_controller.go @@ -101,34 +101,21 @@ func (r *FluentdReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // Install RBAC resources for the filter plugin kubernetes - var role, sa, binding client.Object - if r.Namespaced { - role, sa, binding = operator.MakeScopedRBACObjects( - fd.Name, - fd.Namespace, - fluentdLowercase, - fd.Spec.RBACRules, - fd.Spec.ServiceAccountAnnotations, - ) - } else { - role, sa, binding = operator.MakeRBACObjects( - fd.Name, - fd.Namespace, - fluentdLowercase, - fd.Spec.RBACRules, - fd.Spec.ServiceAccountAnnotations, - ) - } - // Deploy Fluentd (Cluster)Role + // Reconcile the RBAC the agent needs, scoped to a namespace or the cluster. + role, sa, binding := operator.MakeRBACObjectsForScope( + r.Namespaced, + fd.Name, + fd.Namespace, + fluentdLowercase, + fd.Spec.RBACRules, + fd.Spec.ServiceAccountAnnotations, + ) if _, err := controllerutil.CreateOrPatch(ctx, r.Client, role, r.mutate(role, &fd)); err != nil { return ctrl.Result{}, err } - // Deploy Fluentd (Cluster)RoleBinding if _, err := controllerutil.CreateOrPatch(ctx, r.Client, binding, r.mutate(binding, &fd)); err != nil { return ctrl.Result{}, err } - // Deploy Fluentd ServiceAccount if _, err := controllerutil.CreateOrPatch(ctx, r.Client, sa, r.mutate(sa, &fd)); err != nil { return ctrl.Result{}, err } diff --git a/pkg/operator/rbac.go b/pkg/operator/rbac.go index 6e270e321..1201580ab 100644 --- a/pkg/operator/rbac.go +++ b/pkg/operator/rbac.go @@ -1,11 +1,14 @@ package operator import ( + "context" "fmt" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" ) func MakeRBACObjects( @@ -82,9 +85,7 @@ func MakeScopedRBACObjects( }, } - if additionalRules != nil { - r.Rules = append(r.Rules, additionalRules...) - } + r.Rules = append(r.Rules, additionalRules...) sa := corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ @@ -122,8 +123,52 @@ func MakeRBACNames(name, component string) (string, string, string) { return cr, name, crb } +// MakeScopedRBACNames returns the namespaced Role/ServiceAccount/RoleBinding +// names. They follow the same convention as the cluster-scoped names. func MakeScopedRBACNames(name, component string) (string, string, string) { - r := fmt.Sprintf("fluent-operator-%s", component) - rb := fmt.Sprintf("fluent-operator-%s-%s", component, name) - return r, name, rb + return MakeRBACNames(name, component) +} + +// MakeRBACObjectsForScope builds the RBAC objects an agent needs, returning the +// namespaced (Role/RoleBinding) variants when namespaced is true and the +// cluster-scoped (ClusterRole/ClusterRoleBinding) variants otherwise. +func MakeRBACObjectsForScope( + namespaced bool, + name, namespace, component string, + additionalRules []rbacv1.PolicyRule, + saAnnotations map[string]string, +) (role, sa, binding client.Object) { + if namespaced { + r, s, rb := MakeScopedRBACObjects(name, namespace, component, additionalRules, saAnnotations) + return r, s, rb + } + cr, s, crb := MakeRBACObjects(name, namespace, component, additionalRules, saAnnotations) + return cr, s, crb +} + +// DeletePerInstanceBinding removes the per-instance RoleBinding (namespaced) or +// ClusterRoleBinding (cluster-scoped) created for an agent. The (Cluster)Role is +// shared across all instances of a component and is intentionally left in place. +func DeletePerInstanceBinding( + ctx context.Context, + c client.Client, + namespaced bool, + name, namespace, component string, +) error { + var binding client.Object + if namespaced { + _, _, rbName := MakeScopedRBACNames(name, component) + binding = &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: rbName, Namespace: namespace}, + } + } else { + _, _, crbName := MakeRBACNames(name, component) + binding = &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: crbName}, + } + } + if err := c.Delete(ctx, binding); err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil } From 4cc6c8a636aa7d794b3288c9c0d42a229bb69c3d Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 13:30:57 -0400 Subject: [PATCH 3/7] style: wrap long RBAC helper calls to satisfy lll linter Wrap the MakeScopedRBACObjects and DeletePerInstanceBinding call sites that exceeded the 120-character line limit. Co-authored-by: Cursor Signed-off-by: Josh Baird --- controllers/collector_controller.go | 4 +++- controllers/fluent_controller_finalizer.go | 4 +++- controllers/fluentbit_controller.go | 28 ++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/controllers/collector_controller.go b/controllers/collector_controller.go index 14e276176..719cd59ea 100644 --- a/controllers/collector_controller.go +++ b/controllers/collector_controller.go @@ -306,7 +306,9 @@ func (r *CollectorReconciler) delete(ctx context.Context, co *fluentbitv1alpha2. return err } - if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, co.Name, co.Namespace, "collector"); err != nil { + if err := operator.DeletePerInstanceBinding( + ctx, r.Client, r.Namespaced, co.Name, co.Namespace, "collector", + ); err != nil { return err } diff --git a/controllers/fluent_controller_finalizer.go b/controllers/fluent_controller_finalizer.go index f5d805a00..1bfc188d2 100644 --- a/controllers/fluent_controller_finalizer.go +++ b/controllers/fluent_controller_finalizer.go @@ -77,7 +77,9 @@ func (r *FluentdReconciler) delete(ctx context.Context, fd *fluentdv1alpha1.Flue return err } - if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, fd.Name, fd.Namespace, "fluentd"); err != nil { + if err := operator.DeletePerInstanceBinding( + ctx, r.Client, r.Namespaced, fd.Name, fd.Namespace, "fluentd", + ); err != nil { return err } diff --git a/controllers/fluentbit_controller.go b/controllers/fluentbit_controller.go index 98d0e672d..152632249 100644 --- a/controllers/fluentbit_controller.go +++ b/controllers/fluentbit_controller.go @@ -210,7 +210,13 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *rbacv1.Role: - expected, _, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) + expected, _, _ := operator.MakeScopedRBACObjects( + fb.Name, + fb.Namespace, + "fluent-bit", + fb.Spec.RBACRules, + fb.Spec.ServiceAccountAnnotations, + ) return func() error { o.Rules = expected.Rules @@ -231,7 +237,13 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *corev1.ServiceAccount: - _, expected, _ := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) + _, expected, _ := operator.MakeScopedRBACObjects( + fb.Name, + fb.Namespace, + "fluent-bit", + fb.Spec.RBACRules, + fb.Spec.ServiceAccountAnnotations, + ) return func() error { o.Annotations = expected.Annotations @@ -241,7 +253,13 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *rbacv1.RoleBinding: - _, _, expected := operator.MakeScopedRBACObjects(fb.Name, fb.Namespace, "fluent-bit", fb.Spec.RBACRules, fb.Spec.ServiceAccountAnnotations) + _, _, expected := operator.MakeScopedRBACObjects( + fb.Name, + fb.Namespace, + "fluent-bit", + fb.Spec.RBACRules, + fb.Spec.ServiceAccountAnnotations, + ) return func() error { o.Subjects = expected.Subjects o.RoleRef = expected.RoleRef @@ -280,7 +298,9 @@ func (r *FluentBitReconciler) delete(ctx context.Context, fb *fluentbitv1alpha2. return err } - if err := operator.DeletePerInstanceBinding(ctx, r.Client, r.Namespaced, fb.Name, fb.Namespace, "fluent-bit"); err != nil { + if err := operator.DeletePerInstanceBinding( + ctx, r.Client, r.Namespaced, fb.Name, fb.Namespace, "fluent-bit", + ); err != nil { return err } From ec8bf046e201b6798e801d82515f4bdf4d297fbf Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 13:49:44 -0400 Subject: [PATCH 4/7] fix(rbac): only clean up the per-instance RoleBinding in namespaced mode The cluster-scoped ClusterRoleBinding references a shared ClusterRole and the operator is not granted delete on cluster-scoped RBAC, so attempting to delete it during finalization failed (forbidden), blocking the finalizer and leaving owned resources (e.g. the Fluentd StatefulSet) uncollected. Restore the prior cluster-mode behavior of leaving the ClusterRoleBinding in place and only delete the per-instance RoleBinding in namespaced mode. Co-authored-by: Cursor Signed-off-by: Josh Baird --- pkg/operator/rbac.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pkg/operator/rbac.go b/pkg/operator/rbac.go index 1201580ab..22e8e87aa 100644 --- a/pkg/operator/rbac.go +++ b/pkg/operator/rbac.go @@ -146,28 +146,25 @@ func MakeRBACObjectsForScope( return cr, s, crb } -// DeletePerInstanceBinding removes the per-instance RoleBinding (namespaced) or -// ClusterRoleBinding (cluster-scoped) created for an agent. The (Cluster)Role is -// shared across all instances of a component and is intentionally left in place. +// DeletePerInstanceBinding removes the per-instance RoleBinding created for an +// agent when running in namespaced mode. In cluster-scoped mode nothing is +// deleted: the per-instance ClusterRoleBinding references a shared ClusterRole, +// and the operator is intentionally not granted delete on cluster-scoped RBAC +// (keeping its footprint minimal), so the binding is left in place. func DeletePerInstanceBinding( ctx context.Context, c client.Client, namespaced bool, name, namespace, component string, ) error { - var binding client.Object - if namespaced { - _, _, rbName := MakeScopedRBACNames(name, component) - binding = &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: rbName, Namespace: namespace}, - } - } else { - _, _, crbName := MakeRBACNames(name, component) - binding = &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: crbName}, - } + if !namespaced { + return nil + } + _, _, rbName := MakeScopedRBACNames(name, component) + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: rbName, Namespace: namespace}, } - if err := c.Delete(ctx, binding); err != nil && !apierrors.IsNotFound(err) { + if err := c.Delete(ctx, rb); err != nil && !apierrors.IsNotFound(err) { return err } return nil From 0deb715309928cdbf2b39a9a362f3cf88ccc19bd Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 14:05:38 -0400 Subject: [PATCH 5/7] fix(rbac): don't set owner reference on the shared FluentBit Role In namespaced mode the FluentBit Role (fluent-operator-fluent-bit) is shared across all FluentBit instances in a namespace. Setting a per-instance controller reference on it would fail reconciliation for a second instance or garbage-collect the shared Role when one instance is deleted, breaking siblings. Drop the owner reference to match the Collector and Fluentd Role handling and the delete logic that preserves the shared Role. Co-authored-by: Cursor Signed-off-by: Josh Baird --- controllers/fluentbit_controller.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/fluentbit_controller.go b/controllers/fluentbit_controller.go index 152632249..7b9e8663c 100644 --- a/controllers/fluentbit_controller.go +++ b/controllers/fluentbit_controller.go @@ -210,6 +210,8 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return nil } case *rbacv1.Role: + // The Role is shared across all FluentBit instances in the namespace, so + // no per-instance controller reference is set on it. expected, _, _ := operator.MakeScopedRBACObjects( fb.Name, fb.Namespace, @@ -220,9 +222,6 @@ func (r *FluentBitReconciler) mutate(obj client.Object, fb *fluentbitv1alpha2.Fl return func() error { o.Rules = expected.Rules - if err := ctrl.SetControllerReference(fb, o, r.Scheme); err != nil { - return err - } return nil } case *rbacv1.ClusterRole: From fd11b29ee322e41b63331805a371cdd57c053784 Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 14:49:00 -0400 Subject: [PATCH 6/7] fix(rbac): drop delete on roles (least-privilege) The controllers only delete the per-instance RoleBinding (via DeletePerInstanceBinding) in namespaced mode and never delete the shared Role, so granting delete on roles is unnecessary. Split the kubebuilder markers for fluent-bit, collector, and fluentd so roles keeps create;get;list;watch;patch while rolebindings retains delete, and regenerate config/rbac/role.yaml and manifests/setup/setup.yaml. Co-authored-by: Cursor Signed-off-by: Josh Baird --- config/rbac/role.yaml | 2 +- controllers/collector_controller.go | 2 +- controllers/fluentbit_controller.go | 2 +- controllers/fluentd_controller.go | 3 ++- manifests/setup/setup.yaml | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index abcbd7d48..77a5102e3 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -123,6 +123,7 @@ rules: resources: - clusterrolebindings - clusterroles + - roles verbs: - create - get @@ -133,7 +134,6 @@ rules: - rbac.authorization.k8s.io resources: - rolebindings - - roles verbs: - create - delete diff --git a/controllers/collector_controller.go b/controllers/collector_controller.go index 719cd59ea..0fcb6ee5c 100644 --- a/controllers/collector_controller.go +++ b/controllers/collector_controller.go @@ -100,7 +100,7 @@ func (r *CollectorReconciler) collectorsForSecret(ctx context.Context, obj clien // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=create;get;list;watch;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;watch;patch -// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;delete;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;get;list;watch;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get;list;watch;patch // +kubebuilder:rbac:groups=core,resources=pods,verbs=get // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch diff --git a/controllers/fluentbit_controller.go b/controllers/fluentbit_controller.go index 7b9e8663c..686fdc842 100644 --- a/controllers/fluentbit_controller.go +++ b/controllers/fluentbit_controller.go @@ -53,7 +53,7 @@ type FluentBitReconciler struct { // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=create;get;list;watch;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;watch;patch -// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;delete;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;get;list;watch;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get;list;watch;patch // +kubebuilder:rbac:groups=core,resources=pods,verbs=get diff --git a/controllers/fluentd_controller.go b/controllers/fluentd_controller.go index 8fd0c50d1..0c8e140af 100644 --- a/controllers/fluentd_controller.go +++ b/controllers/fluentd_controller.go @@ -55,7 +55,8 @@ type FluentdReconciler struct { // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get // +kubebuilder:rbac:groups=core,resources=serviceaccounts;services,verbs=get;list;watch;create;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=create;get;list;watch;patch -// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;delete;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;get;list;watch;patch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get;list;watch;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. diff --git a/manifests/setup/setup.yaml b/manifests/setup/setup.yaml index 1fe6a6bae..ce5fa516b 100644 --- a/manifests/setup/setup.yaml +++ b/manifests/setup/setup.yaml @@ -43662,6 +43662,7 @@ rules: resources: - clusterrolebindings - clusterroles + - roles verbs: - create - get @@ -43672,7 +43673,6 @@ rules: - rbac.authorization.k8s.io resources: - rolebindings - - roles verbs: - create - delete From cdeb72dd1d7ce7537268138657999427269856d6 Mon Sep 17 00:00:00 2001 From: Josh Baird Date: Thu, 18 Jun 2026 17:42:39 -0400 Subject: [PATCH 7/7] fix(helm): quote the rendered --watch-namespaces arg Render the flag via printf|quote so YAML quoting is applied to values that need it (e.g. a space-separated "ns1, ns2" list), without embedding literal quotes into the container argv. Co-authored-by: Cursor Signed-off-by: Josh Baird --- .../fluent-operator/templates/fluent-operator-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/fluent-operator/templates/fluent-operator-deployment.yaml b/charts/fluent-operator/templates/fluent-operator-deployment.yaml index 2417ac4e9..1c67cde8d 100644 --- a/charts/fluent-operator/templates/fluent-operator-deployment.yaml +++ b/charts/fluent-operator/templates/fluent-operator-deployment.yaml @@ -56,7 +56,7 @@ spec: - --disable-component-controllers={{ . }} {{- end }} {{- with .Values.operator.watchNamespaces }} - - --watch-namespaces={{ . }} + - {{ printf "--watch-namespaces=%s" . | quote }} {{- end }} {{- with .Values.operator.livenessProbe }} livenessProbe: