From 4601b6fd52e593b3a9a1b5f1041ad4e47c605449 Mon Sep 17 00:00:00 2001 From: Greg Pontejos <242696964+gpontejos-cs@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:21:59 -0600 Subject: [PATCH 1/4] fix: Ensure IAR and KAC ImagePullSecrets are updated during reconciles --- .../admission/falconadmission_controller.go | 5 +- .../falconadmission_controller_test.go | 346 +++++++++++++ internal/controller/admission/rbac.go | 56 +- .../falconimage_controller.go | 5 +- .../falconimage_controller_test.go | 485 +++++++++++++++++- .../controller/falcon_image_analyzer/rbac.go | 30 +- 6 files changed, 895 insertions(+), 32 deletions(-) diff --git a/internal/controller/admission/falconadmission_controller.go b/internal/controller/admission/falconadmission_controller.go index f2cbb9321..5c6bb85c1 100644 --- a/internal/controller/admission/falconadmission_controller.go +++ b/internal/controller/admission/falconadmission_controller.go @@ -225,7 +225,8 @@ func (r *FalconAdmissionReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } - if err := r.reconcileServiceAccount(ctx, req, log, falconAdmission); err != nil { + serviceAccountUpdated, err := r.reconcileServiceAccount(ctx, req, log, falconAdmission) + if err != nil { return ctrl.Result{}, err } @@ -291,7 +292,7 @@ func (r *FalconAdmissionReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } - if configUpdated || clusterNameConfigUpdated || serviceUpdated || webhookUpdated { + if configUpdated || clusterNameConfigUpdated || serviceUpdated || webhookUpdated || serviceAccountUpdated { err = r.admissionDeploymentUpdate(ctx, req, log, falconAdmission) if err != nil { return ctrl.Result{}, err diff --git a/internal/controller/admission/falconadmission_controller_test.go b/internal/controller/admission/falconadmission_controller_test.go index be774661d..c05bdd9c3 100644 --- a/internal/controller/admission/falconadmission_controller_test.go +++ b/internal/controller/admission/falconadmission_controller_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -73,6 +74,10 @@ var _ = Describe("FalconAdmission controller", func() { Expect(err).To(Not(HaveOccurred())) falconAdmission = &falconv1alpha1.FalconAdmission{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconAdmission", + }, ObjectMeta: metav1.ObjectMeta{ Name: controllerName, Namespace: namespaceName, @@ -492,5 +497,346 @@ var _ = Describe("FalconAdmission controller", func() { Expect(falconClientContainer.Resources.Requests.Memory().String()).To(Equal("128Mi")) Expect(falconClientContainer.Resources.Limits.Memory().String()).To(Equal("128Mi")) }) + + // Testing reconcileServiceAccount return value + It("should return false when creating a new service account", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission") + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Calling reconcileServiceAccount when no service account exists") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when creating a new service account") + + By("Verifying the service account was created") + Eventually(func() error { + found := &corev1.ServiceAccount{} + return k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + }, 5*time.Second, time.Second).Should(Succeed()) + }) + + It("should return false when only annotations are updated", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission") + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Updating annotations in FalconAdmission spec") + falconAdmission.Spec.AdmissionConfig.ServiceAccount.Annotations = map[string]string{ + "new-annotation": "new-value", + } + + By("Calling reconcileServiceAccount with updated annotations") + updated, err = falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when only annotations are updated") + + By("Verifying annotations were updated") + Eventually(func() map[string]string { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return found.ObjectMeta.Annotations + }, 5*time.Second, time.Second).Should(HaveKey("new-annotation")) + }) + + It("should return true when imagePullSecrets are updated", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission") + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with default imagePullSecrets") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying initial imagePullSecrets") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + })) + + By("Adding a new imagePullSecret to FalconAdmission spec") + falconAdmission.Spec.AdmissionConfig.ImagePullSecrets = []corev1.LocalObjectReference{ + {Name: "additional-secret"}, + } + + By("Calling reconcileServiceAccount with updated imagePullSecrets") + updated, err = falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeTrue(), "reconcileServiceAccount should return true when imagePullSecrets are updated") + + By("Verifying imagePullSecrets were updated") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + {Name: "additional-secret"}, + })) + }) + + It("should return false when no changes are made to service account", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission") + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Calling reconcileServiceAccount again without any changes") + updated, err = falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when no changes are made") + }) + + It("should return true when imagePullSecrets are removed", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission with extra imagePullSecret") + falconAdmission.Spec.AdmissionConfig.ImagePullSecrets = []corev1.LocalObjectReference{ + {Name: "extra-secret"}, + } + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with multiple imagePullSecrets") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying initial imagePullSecrets include extra secret") + Eventually(func() int { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return len(found.ImagePullSecrets) + }, 5*time.Second, time.Second).Should(Equal(2)) + + By("Removing extra imagePullSecret from FalconAdmission spec") + falconAdmission.Spec.AdmissionConfig.ImagePullSecrets = []corev1.LocalObjectReference{} + + By("Calling reconcileServiceAccount with removed imagePullSecret") + updated, err = falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeTrue(), "reconcileServiceAccount should return true when imagePullSecrets are removed") + + By("Verifying imagePullSecrets were reduced to default only") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + })) + }) + + It("should not trigger reconciliation loop when external annotations are added", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconAdmission with operator-managed annotations") + falconAdmission.Spec.AdmissionConfig.ServiceAccount.Annotations = map[string]string{ + "operator-managed-annotation": "value1", + } + err := k8sClient.Create(ctx, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconAdmission{} + return k8sClient.Get(ctx, admissionNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconAdmissionReconciler := &FalconAdmissionReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with operator-managed annotations") + updated, err := falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying service account was created with operator-managed annotation") + Eventually(func() map[string]string { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return found.Annotations + }, 5*time.Second, time.Second).Should(HaveKeyWithValue("operator-managed-annotation", "value1")) + + By("Simulating external system (like OpenShift) adding annotations to the service account") + serviceAccount := &corev1.ServiceAccount{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + + serviceAccount.Annotations["openshift.io/sa.scc.mcs"] = "s0:c27,c4" + serviceAccount.Annotations["openshift.io/sa.scc.supplemental-groups"] = "1000710000/10000" + serviceAccount.Annotations["openshift.io/sa.scc.uid-range"] = "1000710000/10000" + err = k8sClient.Update(ctx, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + + By("Verifying external annotations were added") + Eventually(func() bool { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, found) + return len(found.Annotations) == 4 // 1 operator-managed + 3 openshift + }, 5*time.Second, time.Second).Should(BeTrue()) + + By("Calling reconcileServiceAccount again - should not trigger update for external annotations") + updated, err = falconAdmissionReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: admissionNamespacedName, + }, log, falconAdmission) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when only external annotations exist") + + By("Verifying external annotations are preserved after reconciliation") + serviceAccount = &corev1.ServiceAccount{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.AdmissionServiceAccountName, + Namespace: namespaceName, + }, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.mcs", "s0:c27,c4")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.supplemental-groups", "1000710000/10000")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.uid-range", "1000710000/10000")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("operator-managed-annotation", "value1")) + }) }) }) diff --git a/internal/controller/admission/rbac.go b/internal/controller/admission/rbac.go index 4b3125917..4d5804c2b 100644 --- a/internal/controller/admission/rbac.go +++ b/internal/controller/admission/rbac.go @@ -23,8 +23,9 @@ const ( admissionControllerRoleBindingName = "falcon-admission-controller-role-binding" ) -func (r *FalconAdmissionReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconAdmission *falconv1alpha1.FalconAdmission) error { - update := false +func (r *FalconAdmissionReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconAdmission *falconv1alpha1.FalconAdmission) (bool, error) { + serviceAccountUpdated := false + imagePullSecretsUpdated := false existingServiceAccount := &corev1.ServiceAccount{} imagePullSecrets := []corev1.LocalObjectReference{{Name: common.FalconPullSecretName}} @@ -44,33 +45,58 @@ func (r *FalconAdmissionReconciler) reconcileServiceAccount(ctx context.Context, if err != nil && apierrors.IsNotFound(err) { err = k8sutils.Create(r.Client, r.Scheme, ctx, req, log, falconAdmission, &falconAdmission.Status, serviceAccount) if err != nil { - return err + return false, err } - return nil + return serviceAccountUpdated, nil } else if err != nil { log.Error(err, "Failed to get FalconAdmission ServiceAccount") - return err + return serviceAccountUpdated, err + } + + // Set GVK on existingServiceAccount since it's not populated when retrieved from the API server + existingServiceAccount.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ServiceAccount")) + + // Check if any annotations from serviceAccount need to be added to existingServiceAccount + if serviceAccount.ObjectMeta.Annotations != nil { + if existingServiceAccount.ObjectMeta.Annotations == nil { + existingServiceAccount.ObjectMeta.Annotations = make(map[string]string) + } + for key, value := range serviceAccount.ObjectMeta.Annotations { + if existingValue, exists := existingServiceAccount.ObjectMeta.Annotations[key]; !exists || existingValue != value { + existingServiceAccount.ObjectMeta.Annotations[key] = value + serviceAccountUpdated = true + } + } } - if !reflect.DeepEqual(serviceAccount.ObjectMeta.Annotations, existingServiceAccount.ObjectMeta.Annotations) { - existingServiceAccount.ObjectMeta.Annotations = serviceAccount.ObjectMeta.Annotations - update = true + // Check if any labels from serviceAccount need to be added to existingServiceAccount + if serviceAccount.ObjectMeta.Labels != nil { + if existingServiceAccount.ObjectMeta.Labels == nil { + existingServiceAccount.ObjectMeta.Labels = make(map[string]string) + } + for key, value := range serviceAccount.ObjectMeta.Labels { + if existingValue, exists := existingServiceAccount.ObjectMeta.Labels[key]; !exists || existingValue != value { + existingServiceAccount.ObjectMeta.Labels[key] = value + serviceAccountUpdated = true + } + } } - if !reflect.DeepEqual(serviceAccount.ObjectMeta.Labels, existingServiceAccount.ObjectMeta.Labels) { - existingServiceAccount.ObjectMeta.Labels = serviceAccount.ObjectMeta.Labels - update = true + + if !reflect.DeepEqual(serviceAccount.ImagePullSecrets, existingServiceAccount.ImagePullSecrets) { + existingServiceAccount.ImagePullSecrets = serviceAccount.ImagePullSecrets + imagePullSecretsUpdated = true } - if update { + if serviceAccountUpdated || imagePullSecretsUpdated { err = k8sutils.Update(r.Client, ctx, req, log, falconAdmission, &falconAdmission.Status, existingServiceAccount) if err != nil { - return err + return serviceAccountUpdated, err } } - return nil - + // Only trigger a pod restart if imagePullSecrets are updated + return imagePullSecretsUpdated, nil } func (r *FalconAdmissionReconciler) reconcileClusterRoleBinding(ctx context.Context, req ctrl.Request, log logr.Logger, falconAdmission *falconv1alpha1.FalconAdmission) error { diff --git a/internal/controller/falcon_image_analyzer/falconimage_controller.go b/internal/controller/falcon_image_analyzer/falconimage_controller.go index 5b2a49075..81dbfd72e 100644 --- a/internal/controller/falcon_image_analyzer/falconimage_controller.go +++ b/internal/controller/falcon_image_analyzer/falconimage_controller.go @@ -213,7 +213,8 @@ func (r *FalconImageAnalyzerReconciler) Reconcile(ctx context.Context, req ctrl. } } - if err := r.reconcileServiceAccount(ctx, req, log, falconImageAnalyzer); err != nil { + serviceAccountUpdated, err := r.reconcileServiceAccount(ctx, req, log, falconImageAnalyzer) + if err != nil { return ctrl.Result{}, err } @@ -246,7 +247,7 @@ func (r *FalconImageAnalyzerReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, err } - if configUpdated { + if configUpdated || serviceAccountUpdated { err = r.imageAnalyzerDeploymentUpdate(ctx, req, log, falconImageAnalyzer) if err != nil { return ctrl.Result{}, err diff --git a/internal/controller/falcon_image_analyzer/falconimage_controller_test.go b/internal/controller/falcon_image_analyzer/falconimage_controller_test.go index 2518fe348..248c31c0b 100644 --- a/internal/controller/falcon_image_analyzer/falconimage_controller_test.go +++ b/internal/controller/falcon_image_analyzer/falconimage_controller_test.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -70,8 +71,9 @@ var _ = Describe("FalconImageAnalyzer controller", func() { // Delete cluster level resources clusterRoleBinding := &rbacv1.ClusterRoleBinding{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: imageClusterRoleBindingName}, clusterRoleBinding)).To(Succeed()) - Expect(k8sClient.Delete(ctx, clusterRoleBinding)).To(Succeed()) + if err := k8sClient.Get(ctx, types.NamespacedName{Name: imageClusterRoleBindingName}, clusterRoleBinding); err == nil { + Expect(k8sClient.Delete(ctx, clusterRoleBinding)).To(Succeed()) + } // Delete FalconImageAnalyzer custom resource falconImageAnalyzerCR := &falconv1alpha1.FalconImageAnalyzer{} @@ -292,5 +294,484 @@ var _ = Describe("FalconImageAnalyzer controller", func() { err = k8sClient.Delete(ctx, testSecret) Expect(err).To(Not(HaveOccurred())) }) + + // Testing reconcileServiceAccount return value + It("should return false when creating a new service account", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Calling reconcileServiceAccount when no service account exists") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when creating a new service account") + + By("Verifying the service account was created") + Eventually(func() error { + found := &corev1.ServiceAccount{} + return k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + }, 5*time.Second, time.Second).Should(Succeed()) + }) + + It("should return false when only annotations are updated", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Updating annotations in FalconImageAnalyzer spec") + falconImageAnalyzer.Spec.ImageAnalyzerConfig.ServiceAccount.Annotations = map[string]string{ + "new-annotation": "new-value", + } + + By("Calling reconcileServiceAccount with updated annotations") + updated, err = falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when only annotations are updated") + + By("Verifying annotations were updated") + Eventually(func() map[string]string { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return found.ObjectMeta.Annotations + }, 5*time.Second, time.Second).Should(HaveKey("new-annotation")) + }) + + It("should return true when imagePullSecrets are updated", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with default imagePullSecrets") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying initial imagePullSecrets") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + })) + + By("Adding a new imagePullSecret to FalconImageAnalyzer spec") + falconImageAnalyzer.Spec.ImageAnalyzerConfig.ImagePullSecrets = []corev1.LocalObjectReference{ + {Name: "additional-secret"}, + } + + By("Calling reconcileServiceAccount with updated imagePullSecrets") + updated, err = falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeTrue(), "reconcileServiceAccount should return true when imagePullSecrets are updated") + + By("Verifying imagePullSecrets were updated") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + {Name: "additional-secret"}, + })) + }) + + It("should return false when no changes are made to service account", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Calling reconcileServiceAccount again without any changes") + updated, err = falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when no changes are made") + }) + + It("should return true when imagePullSecrets are removed", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer with extra imagePullSecret") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + ImageAnalyzerConfig: falconv1alpha1.FalconImageAnalyzerConfigSpec{ + ImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "extra-secret"}, + }, + }, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with multiple imagePullSecrets") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying initial imagePullSecrets include extra secret") + Eventually(func() int { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return len(found.ImagePullSecrets) + }, 5*time.Second, time.Second).Should(Equal(2)) + + By("Removing extra imagePullSecret from FalconImageAnalyzer spec") + falconImageAnalyzer.Spec.ImageAnalyzerConfig.ImagePullSecrets = []corev1.LocalObjectReference{} + + By("Calling reconcileServiceAccount with removed imagePullSecret") + updated, err = falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeTrue(), "reconcileServiceAccount should return true when imagePullSecrets are removed") + + By("Verifying imagePullSecrets were reduced to default only") + Eventually(func() []corev1.LocalObjectReference { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return found.ImagePullSecrets + }, 5*time.Second, time.Second).Should(Equal([]corev1.LocalObjectReference{ + {Name: common.FalconPullSecretName}, + })) + }) + + It("should not trigger reconciliation loop when external annotations are added", func() { + log := zap.New(zap.UseDevMode(true)) + + By("Creating the custom resource for the Kind FalconImageAnalyzer") + falconImageAnalyzer := &falconv1alpha1.FalconImageAnalyzer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "falcon.crowdstrike.com/v1alpha1", + Kind: "FalconImageAnalyzer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ImageAnalyzerName, + Namespace: testNamespace.Name, + }, + Spec: falconv1alpha1.FalconImageAnalyzerSpec{ + InstallNamespace: imageAnalyzerNamespacedName.Namespace, + FalconAPI: &falconv1alpha1.FalconAPI{ + ClientId: "test-client-id", + ClientSecret: "test-client-secret", + CloudRegion: "us-1", + }, + Registry: falconv1alpha1.RegistrySpec{ + Type: "crowdstrike", + }, + Image: imageAnalyzerImage, + ImageAnalyzerConfig: falconv1alpha1.FalconImageAnalyzerConfigSpec{ + ServiceAccount: falconv1alpha1.FalconImageAnalyzerServiceAccount{ + Annotations: map[string]string{ + "operator-managed-annotation": "value1", + }, + }, + }, + }, + } + err := k8sClient.Create(ctx, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &falconv1alpha1.FalconImageAnalyzer{} + return k8sClient.Get(ctx, imageAnalyzerNamespacedName, found) + }, 20*time.Second, time.Second).Should(Succeed()) + + By("Creating the reconciler") + falconImageAnalyzerReconciler := &FalconImageAnalyzerReconciler{ + Client: k8sClient, + Reader: k8sReader, + Scheme: k8sClient.Scheme(), + } + + By("Creating initial service account with operator-managed annotations") + updated, err := falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse()) + + By("Verifying service account was created with operator-managed annotation") + Eventually(func() map[string]string { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return found.Annotations + }, 5*time.Second, time.Second).Should(HaveKeyWithValue("operator-managed-annotation", "value1")) + + By("Simulating external system (like OpenShift) adding annotations to the service account") + serviceAccount := &corev1.ServiceAccount{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + + serviceAccount.Annotations["openshift.io/sa.scc.mcs"] = "s0:c27,c4" + serviceAccount.Annotations["openshift.io/sa.scc.supplemental-groups"] = "1000710000/10000" + serviceAccount.Annotations["openshift.io/sa.scc.uid-range"] = "1000710000/10000" + err = k8sClient.Update(ctx, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + + By("Verifying external annotations were added") + Eventually(func() bool { + found := &corev1.ServiceAccount{} + _ = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, found) + return len(found.Annotations) == 4 // 1 operator-managed + 3 openshift + }, 5*time.Second, time.Second).Should(BeTrue()) + + By("Calling reconcileServiceAccount again - should not trigger update for external annotations") + updated, err = falconImageAnalyzerReconciler.reconcileServiceAccount(ctx, reconcile.Request{ + NamespacedName: imageAnalyzerNamespacedName, + }, log, falconImageAnalyzer) + + Expect(err).To(Not(HaveOccurred())) + Expect(updated).To(BeFalse(), "reconcileServiceAccount should return false when only external annotations exist") + + By("Verifying external annotations are preserved after reconciliation") + serviceAccount = &corev1.ServiceAccount{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Name: common.ImageServiceAccountName, + Namespace: testNamespace.Name, + }, serviceAccount) + Expect(err).To(Not(HaveOccurred())) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.mcs", "s0:c27,c4")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.supplemental-groups", "1000710000/10000")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("openshift.io/sa.scc.uid-range", "1000710000/10000")) + Expect(serviceAccount.Annotations).To(HaveKeyWithValue("operator-managed-annotation", "value1")) + }) }) }) diff --git a/internal/controller/falcon_image_analyzer/rbac.go b/internal/controller/falcon_image_analyzer/rbac.go index 48ff31b2d..7830e66b0 100644 --- a/internal/controller/falcon_image_analyzer/rbac.go +++ b/internal/controller/falcon_image_analyzer/rbac.go @@ -21,8 +21,9 @@ const ( imageClusterRoleBindingName = "falcon-operator-image-controller-rolebinding" ) -func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) error { - update := false +func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) (bool, error) { + serviceAccountUpdated := false + imagePullSecretsUpdated := false existingServiceAccount := &corev1.ServiceAccount{} imagePullSecrets := []corev1.LocalObjectReference{{Name: common.FalconPullSecretName}} @@ -42,15 +43,18 @@ func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Cont if err != nil && apierrors.IsNotFound(err) { err = k8sutils.Create(r.Client, r.Scheme, ctx, req, log, falconImageAnalyzer, &falconImageAnalyzer.Status, serviceAccount) if err != nil { - return err + return serviceAccountUpdated, err } - return nil + return serviceAccountUpdated, nil } else if err != nil { log.Error(err, "Failed to get FalconImageAnalyzer ServiceAccount") - return err + return serviceAccountUpdated, err } + // Set GVK on existingServiceAccount since it's not populated when retrieved from the API server + existingServiceAccount.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ServiceAccount")) + // Check if any annotations from serviceAccount need to be added to existingServiceAccount if serviceAccount.ObjectMeta.Annotations != nil { if existingServiceAccount.ObjectMeta.Annotations == nil { @@ -59,7 +63,7 @@ func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Cont for key, value := range serviceAccount.ObjectMeta.Annotations { if existingValue, exists := existingServiceAccount.ObjectMeta.Annotations[key]; !exists || existingValue != value { existingServiceAccount.ObjectMeta.Annotations[key] = value - update = true + serviceAccountUpdated = true } } } @@ -72,20 +76,24 @@ func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Cont for key, value := range serviceAccount.ObjectMeta.Labels { if existingValue, exists := existingServiceAccount.ObjectMeta.Labels[key]; !exists || existingValue != value { existingServiceAccount.ObjectMeta.Labels[key] = value - update = true + serviceAccountUpdated = true } } } - if update { + if !reflect.DeepEqual(serviceAccount.ImagePullSecrets, existingServiceAccount.ImagePullSecrets) { + existingServiceAccount.ImagePullSecrets = serviceAccount.ImagePullSecrets + imagePullSecretsUpdated = true + } + + if serviceAccountUpdated || imagePullSecretsUpdated { err = k8sutils.Update(r.Client, ctx, req, log, falconImageAnalyzer, &falconImageAnalyzer.Status, existingServiceAccount) if err != nil { - return err + return serviceAccountUpdated, err } } - return nil - + return imagePullSecretsUpdated, nil } func (r *FalconImageAnalyzerReconciler) reconcileClusterRoleBinding(ctx context.Context, req ctrl.Request, log logr.Logger, falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) error { From f240dc49b805ab34ba6132559f5786d50b365822 Mon Sep 17 00:00:00 2001 From: gpontejos-cs <242696964+gpontejos-cs@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:45:06 -0600 Subject: [PATCH 2/4] Update internal/controller/admission/falconadmission_controller.go Co-authored-by: Jung Choi <17916440+mr-jungchoi@users.noreply.github.com> --- internal/controller/admission/falconadmission_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/admission/falconadmission_controller.go b/internal/controller/admission/falconadmission_controller.go index 5c6bb85c1..ee201f2b7 100644 --- a/internal/controller/admission/falconadmission_controller.go +++ b/internal/controller/admission/falconadmission_controller.go @@ -225,7 +225,7 @@ func (r *FalconAdmissionReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } - serviceAccountUpdated, err := r.reconcileServiceAccount(ctx, req, log, falconAdmission) + serviceAccountUpdateRequiresRestart, err := r.reconcileServiceAccount(ctx, req, log, falconAdmission) if err != nil { return ctrl.Result{}, err } From c26f896a3d1cc008e8dd01384e28601aa5406d8f Mon Sep 17 00:00:00 2001 From: gpontejos-cs <242696964+gpontejos-cs@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:45:16 -0600 Subject: [PATCH 3/4] Update internal/controller/falcon_image_analyzer/falconimage_controller.go Co-authored-by: Jung Choi <17916440+mr-jungchoi@users.noreply.github.com> --- .../controller/falcon_image_analyzer/falconimage_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/falcon_image_analyzer/falconimage_controller.go b/internal/controller/falcon_image_analyzer/falconimage_controller.go index 81dbfd72e..1ab1eb717 100644 --- a/internal/controller/falcon_image_analyzer/falconimage_controller.go +++ b/internal/controller/falcon_image_analyzer/falconimage_controller.go @@ -213,7 +213,7 @@ func (r *FalconImageAnalyzerReconciler) Reconcile(ctx context.Context, req ctrl. } } - serviceAccountUpdated, err := r.reconcileServiceAccount(ctx, req, log, falconImageAnalyzer) + serviceAccountUpdateRequiresRestart, err := r.reconcileServiceAccount(ctx, req, log, falconImageAnalyzer) if err != nil { return ctrl.Result{}, err } From 933161d88be926a130620ab2ca1fdccd783cceb5 Mon Sep 17 00:00:00 2001 From: Greg Pontejos <242696964+gpontejos-cs@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:02:55 -0600 Subject: [PATCH 4/4] Adding comments for clarity and updating service account reconcile vars --- internal/controller/admission/falconadmission_controller.go | 2 +- internal/controller/admission/rbac.go | 2 ++ .../controller/falcon_image_analyzer/falconimage_controller.go | 2 +- internal/controller/falcon_image_analyzer/rbac.go | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/controller/admission/falconadmission_controller.go b/internal/controller/admission/falconadmission_controller.go index ee201f2b7..b7c0650a1 100644 --- a/internal/controller/admission/falconadmission_controller.go +++ b/internal/controller/admission/falconadmission_controller.go @@ -292,7 +292,7 @@ func (r *FalconAdmissionReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } - if configUpdated || clusterNameConfigUpdated || serviceUpdated || webhookUpdated || serviceAccountUpdated { + if configUpdated || clusterNameConfigUpdated || serviceUpdated || webhookUpdated || serviceAccountUpdateRequiresRestart { err = r.admissionDeploymentUpdate(ctx, req, log, falconAdmission) if err != nil { return ctrl.Result{}, err diff --git a/internal/controller/admission/rbac.go b/internal/controller/admission/rbac.go index 4d5804c2b..117f749b2 100644 --- a/internal/controller/admission/rbac.go +++ b/internal/controller/admission/rbac.go @@ -23,6 +23,8 @@ const ( admissionControllerRoleBindingName = "falcon-admission-controller-role-binding" ) +// reconcileServiceAccount only returns true when a service account update requires a deployment restart. +// Only updates to image pull secrets should trigger a deployment restart. func (r *FalconAdmissionReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconAdmission *falconv1alpha1.FalconAdmission) (bool, error) { serviceAccountUpdated := false imagePullSecretsUpdated := false diff --git a/internal/controller/falcon_image_analyzer/falconimage_controller.go b/internal/controller/falcon_image_analyzer/falconimage_controller.go index 1ab1eb717..69b11135b 100644 --- a/internal/controller/falcon_image_analyzer/falconimage_controller.go +++ b/internal/controller/falcon_image_analyzer/falconimage_controller.go @@ -247,7 +247,7 @@ func (r *FalconImageAnalyzerReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, err } - if configUpdated || serviceAccountUpdated { + if configUpdated || serviceAccountUpdateRequiresRestart { err = r.imageAnalyzerDeploymentUpdate(ctx, req, log, falconImageAnalyzer) if err != nil { return ctrl.Result{}, err diff --git a/internal/controller/falcon_image_analyzer/rbac.go b/internal/controller/falcon_image_analyzer/rbac.go index 7830e66b0..2cce980ff 100644 --- a/internal/controller/falcon_image_analyzer/rbac.go +++ b/internal/controller/falcon_image_analyzer/rbac.go @@ -21,6 +21,8 @@ const ( imageClusterRoleBindingName = "falcon-operator-image-controller-rolebinding" ) +// reconcileServiceAccount only returns true when a service account update requires a deployment restart. +// Only updates to image pull secrets should trigger a deployment restart. func (r *FalconImageAnalyzerReconciler) reconcileServiceAccount(ctx context.Context, req ctrl.Request, log logr.Logger, falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) (bool, error) { serviceAccountUpdated := false imagePullSecretsUpdated := false