From a6bde2dd1628f3a1597d6151a6fb872caa836592 Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 17:52:30 +0200 Subject: [PATCH 1/7] chore: FalconAdmission support tolerations --- api/falcon/v1alpha1/falconadmission_types.go | 12 ++++++ ...lcon.crowdstrike.com_falconadmissions.yaml | 40 +++++++++++++++++++ ...con.crowdstrike.com_falcondeployments.yaml | 40 +++++++++++++++++++ ...pha1_falconadmission_with_tolerations.yaml | 39 ++++++++++++++++++ internal/controller/assets/deployment.go | 8 ++++ 5 files changed, 139 insertions(+) create mode 100644 config/samples/falcon_v1alpha1_falconadmission_with_tolerations.yaml diff --git a/api/falcon/v1alpha1/falconadmission_types.go b/api/falcon/v1alpha1/falconadmission_types.go index f6db868f9..f6c0677ba 100644 --- a/api/falcon/v1alpha1/falconadmission_types.go +++ b/api/falcon/v1alpha1/falconadmission_types.go @@ -190,6 +190,11 @@ type FalconAdmissionConfigSpec struct { // Specifies node affinity for scheduling the Admission Controller. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=18 NodeAffinity *corev1.NodeAffinity `json:"nodeAffinity,omitempty"` + + // Specifies tolerations for custom taints on the admission controller pods. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=19 + Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"` } type FalconAdmissionServiceAccount struct { @@ -323,3 +328,10 @@ func (ac *FalconAdmission) GetFalconSpec() FalconSensor { func (ac *FalconAdmission) SetFalconSpec(falconSpec FalconSensor) { ac.Spec.Falcon = falconSpec } + +func (ac *FalconAdmission) GetTolerations() *[]corev1.Toleration { + if ac.Spec.AdmissionConfig.Tolerations == nil { + return &[]corev1.Toleration{} + } + return ac.Spec.AdmissionConfig.Tolerations +} diff --git a/config/crd/bases/falcon.crowdstrike.com_falconadmissions.yaml b/config/crd/bases/falcon.crowdstrike.com_falconadmissions.yaml index e7fe42d38..790238766 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falconadmissions.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falconadmissions.yaml @@ -633,6 +633,46 @@ spec: type: integer x-kubernetes-int-or-string: true type: object + tolerations: + description: Specifies tolerations for custom taints on the admission + controller pods. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array updateStrategy: default: rollingUpdate: diff --git a/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml b/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml index 3dc45fa1e..f352b0aee 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml @@ -688,6 +688,46 @@ spec: type: integer x-kubernetes-int-or-string: true type: object + tolerations: + description: Specifies tolerations for custom taints on the + admission controller pods. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array updateStrategy: default: rollingUpdate: diff --git a/config/samples/falcon_v1alpha1_falconadmission_with_tolerations.yaml b/config/samples/falcon_v1alpha1_falconadmission_with_tolerations.yaml new file mode 100644 index 000000000..92e15a83f --- /dev/null +++ b/config/samples/falcon_v1alpha1_falconadmission_with_tolerations.yaml @@ -0,0 +1,39 @@ +apiVersion: falcon.crowdstrike.com/v1alpha1 +kind: FalconAdmission +metadata: + labels: + crowdstrike.com/component: sample + crowdstrike.com/created-by: falcon-operator + crowdstrike.com/instance: falcon-admission + crowdstrike.com/managed-by: kustomize + crowdstrike.com/name: falconadmission + crowdstrike.com/part-of: Falcon + crowdstrike.com/provider: crowdstrike + name: falcon-kac +spec: + falcon_api: + client_id: PLEASE_FILL_IN + client_secret: PLEASE_FILL_IN + cloud_region: autodiscover + registry: + type: crowdstrike + falcon: + trace: none + tags: + - admission_controller + admissionConfig: + # Example tolerations configuration + tolerations: + # Tolerate control-plane nodes + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" + effect: "NoSchedule" + # Tolerate master nodes + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + # Custom toleration example + - key: "dedicated" + operator: "Equal" + value: "admission" + effect: "NoSchedule" \ No newline at end of file diff --git a/internal/controller/assets/deployment.go b/internal/controller/assets/deployment.go index 6978952ba..6fc0c3c54 100644 --- a/internal/controller/assets/deployment.go +++ b/internal/controller/assets/deployment.go @@ -730,6 +730,7 @@ func AdmissionDeployment(name string, namespace string, component string, imageU PriorityClassName: common.FalconPriorityClassName, Containers: *kacContainers, Volumes: volumes, + Tolerations: getTolerations(falconAdmission), }, }, }, @@ -858,3 +859,10 @@ func getNodeAffinity(nodeAffinity *corev1.NodeAffinity) *corev1.Affinity { }, } } + +func getTolerations(falconAdmission *falconv1alpha1.FalconAdmission) []corev1.Toleration { + if falconAdmission.Spec.AdmissionConfig.Tolerations != nil { + return *falconAdmission.Spec.AdmissionConfig.Tolerations + } + return []corev1.Toleration{} +} From 1fb5993975ef4e99c319d9658c7734adaf3f1d9a Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 17:56:55 +0200 Subject: [PATCH 2/7] chore: FalconImageAnalyzer support tolerations --- .../v1alpha1/falconimageanalyzer_types.go | 12 ++++++ ...con.crowdstrike.com_falcondeployments.yaml | 40 +++++++++++++++++++ ....crowdstrike.com_falconimageanalyzers.yaml | 40 +++++++++++++++++++ ..._falconimageanalyzer_with_tolerations.yaml | 35 ++++++++++++++++ internal/controller/assets/deployment.go | 8 ++++ 5 files changed, 135 insertions(+) create mode 100644 config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml diff --git a/api/falcon/v1alpha1/falconimageanalyzer_types.go b/api/falcon/v1alpha1/falconimageanalyzer_types.go index 22c986db2..f2caf5f34 100644 --- a/api/falcon/v1alpha1/falconimageanalyzer_types.go +++ b/api/falcon/v1alpha1/falconimageanalyzer_types.go @@ -115,6 +115,11 @@ type FalconImageAnalyzerConfigSpec struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Falcon Image Analyzer Enable Debugging",order=13 // +kubebuilder:default:=false EnableDebug bool `json:"debug,omitempty"` + + // Specifies tolerations for custom taints on the image analyzer pods. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=14 + Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"` } type FalconImageAnalyzerPriorityClass struct { @@ -223,3 +228,10 @@ func (fia *FalconImageAnalyzer) GetFalconSpec() FalconSensor { func (fia *FalconImageAnalyzer) SetFalconSpec(FalconSensor) { // noop } + +func (fia *FalconImageAnalyzer) GetTolerations() *[]corev1.Toleration { + if fia.Spec.ImageAnalyzerConfig.Tolerations == nil { + return &[]corev1.Toleration{} + } + return fia.Spec.ImageAnalyzerConfig.Tolerations +} diff --git a/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml b/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml index f352b0aee..cafbdb02e 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falcondeployments.yaml @@ -3537,6 +3537,46 @@ spec: default: 20Gi description: Set the falcon image analyzer volume size limit. type: string + tolerations: + description: Specifies tolerations for custom taints on the + image analyzer pods. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array updateStrategy: default: rollingUpdate: diff --git a/config/crd/bases/falcon.crowdstrike.com_falconimageanalyzers.yaml b/config/crd/bases/falcon.crowdstrike.com_falconimageanalyzers.yaml index 14825a6e3..81004eb2b 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falconimageanalyzers.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falconimageanalyzers.yaml @@ -274,6 +274,46 @@ spec: default: 20Gi description: Set the falcon image analyzer volume size limit. type: string + tolerations: + description: Specifies tolerations for custom taints on the image + analyzer pods. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array updateStrategy: default: rollingUpdate: diff --git a/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml b/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml new file mode 100644 index 000000000..f3ba699c2 --- /dev/null +++ b/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml @@ -0,0 +1,35 @@ +apiVersion: falcon.crowdstrike.com/v1alpha1 +kind: FalconImageAnalyzer +metadata: + labels: + crowdstrike.com/component: sample + crowdstrike.com/created-by: falcon-operator + crowdstrike.com/instance: falcon-image-analyzer + crowdstrike.com/managed-by: kustomize + crowdstrike.com/name: falconimageanalyzer + crowdstrike.com/part-of: Falcon + crowdstrike.com/provider: crowdstrike + name: falcon-iar +spec: + falcon_api: + client_id: PLEASE_FILL_IN + client_secret: PLEASE_FILL_IN + cloud_region: autodiscover + registry: + type: crowdstrike + imageAnalyzerConfig: + # Example tolerations configuration + tolerations: + # Tolerate control-plane nodes + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" + effect: "NoSchedule" + # Tolerate master nodes + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + # Custom toleration example for dedicated image analyzer nodes + - key: "dedicated" + operator: "Equal" + value: "image-analyzer" + effect: "NoSchedule" \ No newline at end of file diff --git a/internal/controller/assets/deployment.go b/internal/controller/assets/deployment.go index 6fc0c3c54..1e220163f 100644 --- a/internal/controller/assets/deployment.go +++ b/internal/controller/assets/deployment.go @@ -360,6 +360,7 @@ func ImageAnalyzerDeployment(name string, namespace string, component string, im NodeSelector: common.NodeSelector, Volumes: volumes, PriorityClassName: falconImageAnalyzer.Spec.ImageAnalyzerConfig.PriorityClass.Name, + Tolerations: getImageAnalyzerTolerations(falconImageAnalyzer), }, }, }, @@ -866,3 +867,10 @@ func getTolerations(falconAdmission *falconv1alpha1.FalconAdmission) []corev1.To } return []corev1.Toleration{} } + +func getImageAnalyzerTolerations(falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) []corev1.Toleration { + if falconImageAnalyzer.Spec.ImageAnalyzerConfig.Tolerations != nil { + return *falconImageAnalyzer.Spec.ImageAnalyzerConfig.Tolerations + } + return []corev1.Toleration{} +} From 002d13231016480701efb0f7ed576840c3fd1afa Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 18:08:07 +0200 Subject: [PATCH 3/7] fix tests --- api/falcon/v1alpha1/falconadmission_types.go | 2 +- .../v1alpha1/falconimageanalyzer_types.go | 2 +- api/falcon/v1alpha1/zz_generated.deepcopy.go | 22 +++++++++++++++++++ internal/controller/assets/deployment.go | 4 ++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/api/falcon/v1alpha1/falconadmission_types.go b/api/falcon/v1alpha1/falconadmission_types.go index f6c0677ba..87c739870 100644 --- a/api/falcon/v1alpha1/falconadmission_types.go +++ b/api/falcon/v1alpha1/falconadmission_types.go @@ -331,7 +331,7 @@ func (ac *FalconAdmission) SetFalconSpec(falconSpec FalconSensor) { func (ac *FalconAdmission) GetTolerations() *[]corev1.Toleration { if ac.Spec.AdmissionConfig.Tolerations == nil { - return &[]corev1.Toleration{} + return nil } return ac.Spec.AdmissionConfig.Tolerations } diff --git a/api/falcon/v1alpha1/falconimageanalyzer_types.go b/api/falcon/v1alpha1/falconimageanalyzer_types.go index f2caf5f34..c29cc78ff 100644 --- a/api/falcon/v1alpha1/falconimageanalyzer_types.go +++ b/api/falcon/v1alpha1/falconimageanalyzer_types.go @@ -231,7 +231,7 @@ func (fia *FalconImageAnalyzer) SetFalconSpec(FalconSensor) { func (fia *FalconImageAnalyzer) GetTolerations() *[]corev1.Toleration { if fia.Spec.ImageAnalyzerConfig.Tolerations == nil { - return &[]corev1.Toleration{} + return nil } return fia.Spec.ImageAnalyzerConfig.Tolerations } diff --git a/api/falcon/v1alpha1/zz_generated.deepcopy.go b/api/falcon/v1alpha1/zz_generated.deepcopy.go index 493f57362..3f4f3ca2d 100644 --- a/api/falcon/v1alpha1/zz_generated.deepcopy.go +++ b/api/falcon/v1alpha1/zz_generated.deepcopy.go @@ -394,6 +394,17 @@ func (in *FalconAdmissionConfigSpec) DeepCopyInto(out *FalconAdmissionConfigSpec *out = new(corev1.NodeAffinity) (*in).DeepCopyInto(*out) } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = new([]corev1.Toleration) + if **in != nil { + in, out := *in, *out + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalconAdmissionConfigSpec. @@ -1038,6 +1049,17 @@ func (in *FalconImageAnalyzerConfigSpec) DeepCopyInto(out *FalconImageAnalyzerCo in.DepUpdateStrategy.DeepCopyInto(&out.DepUpdateStrategy) in.Exclusions.DeepCopyInto(&out.Exclusions) in.RegistryConfig.DeepCopyInto(&out.RegistryConfig) + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = new([]corev1.Toleration) + if **in != nil { + in, out := *in, *out + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalconImageAnalyzerConfigSpec. diff --git a/internal/controller/assets/deployment.go b/internal/controller/assets/deployment.go index 1e220163f..86151195b 100644 --- a/internal/controller/assets/deployment.go +++ b/internal/controller/assets/deployment.go @@ -865,12 +865,12 @@ func getTolerations(falconAdmission *falconv1alpha1.FalconAdmission) []corev1.To if falconAdmission.Spec.AdmissionConfig.Tolerations != nil { return *falconAdmission.Spec.AdmissionConfig.Tolerations } - return []corev1.Toleration{} + return nil } func getImageAnalyzerTolerations(falconImageAnalyzer *falconv1alpha1.FalconImageAnalyzer) []corev1.Toleration { if falconImageAnalyzer.Spec.ImageAnalyzerConfig.Tolerations != nil { return *falconImageAnalyzer.Spec.ImageAnalyzerConfig.Tolerations } - return []corev1.Toleration{} + return nil } From 31bbbac46587d50c2a86d05b6142300e2e232406 Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 18:38:47 +0200 Subject: [PATCH 4/7] e2e tests --- ...ha1_falconnodesensor_with_tolerations.yaml | 37 ++++ test/e2e/e2e_test.go | 91 ++++++++++ test/e2e/tolerations_test.go | 163 ++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 config/samples/falcon_v1alpha1_falconnodesensor_with_tolerations.yaml create mode 100644 test/e2e/tolerations_test.go diff --git a/config/samples/falcon_v1alpha1_falconnodesensor_with_tolerations.yaml b/config/samples/falcon_v1alpha1_falconnodesensor_with_tolerations.yaml new file mode 100644 index 000000000..083c29d99 --- /dev/null +++ b/config/samples/falcon_v1alpha1_falconnodesensor_with_tolerations.yaml @@ -0,0 +1,37 @@ +apiVersion: falcon.crowdstrike.com/v1alpha1 +kind: FalconNodeSensor +metadata: + labels: + crowdstrike.com/component: sample + crowdstrike.com/created-by: falcon-operator + crowdstrike.com/instance: falcon-node-sensor + crowdstrike.com/managed-by: kustomize + crowdstrike.com/name: falconnodesensor + crowdstrike.com/part-of: Falcon + crowdstrike.com/provider: crowdstrike + name: falcon-node-sensor +spec: + falcon_api: + client_id: PLEASE_FILL_IN + client_secret: PLEASE_FILL_IN + cloud_region: autodiscover + node: + # Custom tolerations for the node sensor DaemonSet + tolerations: + # Tolerate control-plane nodes + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" + effect: "NoSchedule" + # Tolerate master nodes + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + # Tolerate infra nodes + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + # Custom toleration for dedicated security nodes + - key: "dedicated" + operator: "Equal" + value: "security" + effect: "NoSchedule" \ No newline at end of file diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 18f0e6a67..a241eb11c 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -238,6 +238,30 @@ var _ = Describe("falcon", Ordered, func() { }) }) + Context("Falcon Node Sensor with Tolerations", func() { + manifest := "./config/samples/falcon_v1alpha1_falconnodesensor_with_tolerations.yaml" + It("should deploy successfully with tolerations", func() { + updateManifestApiCreds(manifest) + nodeConfig.manageCrdInstance(crApply, manifest) + nodeConfig.validateRunningStatus(shouldBeRunning) + nodeConfig.validateCrStatus() + + // Validate tolerations on DaemonSet + expectedTolerations := []ExpectedToleration{ + {Key: "node-role.kubernetes.io/control-plane", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "node-role.kubernetes.io/master", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "node-role.kubernetes.io/infra", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "dedicated", Operator: "Equal", Value: "security", Effect: "NoSchedule"}, + } + nodeConfig.validateDaemonSetTolerations("falcon-node-sensor", expectedTolerations) + }) + It("should cleanup successfully", func() { + nodeConfig.manageCrdInstance(crDelete, manifest) + nodeConfig.validateRunningStatus(shouldBeTerminated) + nodeConfig.waitForNamespaceDeletion() + }) + }) + Context("Falcon Admission Controller", func() { manifest := "./config/samples/falcon_v1alpha1_falconadmission.yaml" It("should deploy successfully", func() { @@ -253,6 +277,73 @@ var _ = Describe("falcon", Ordered, func() { }) }) + Context("Falcon Admission Controller with Tolerations", func() { + manifest := "./config/samples/falcon_v1alpha1_falconadmission_with_tolerations.yaml" + It("should deploy successfully with tolerations", func() { + updateManifestApiCreds(manifest) + kacConfig.manageCrdInstance(crApply, manifest) + kacConfig.validateRunningStatus(shouldBeRunning) + kacConfig.validateCrStatus() + + // Get deployment name and validate tolerations + deploymentName, err := kacConfig.getDeploymentName() + Expect(err).NotTo(HaveOccurred()) + + expectedTolerations := []ExpectedToleration{ + {Key: "node-role.kubernetes.io/control-plane", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "node-role.kubernetes.io/master", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "dedicated", Operator: "Equal", Value: "admission", Effect: "NoSchedule"}, + } + kacConfig.validateDeploymentTolerations(deploymentName, expectedTolerations) + }) + It("should cleanup successfully", func() { + kacConfig.manageCrdInstance(crDelete, manifest) + kacConfig.validateRunningStatus(shouldBeTerminated) + kacConfig.waitForNamespaceDeletion() + }) + }) + + Context("Falcon Image Analyzer", func() { + manifest := "./config/samples/falcon_v1alpha1_falconimageanalyzer.yaml" + It("should deploy successfully", func() { + updateManifestApiCreds(manifest) + iarConfig.manageCrdInstance(crApply, manifest) + iarConfig.validateRunningStatus(shouldBeRunning) + iarConfig.validateCrStatus() + }) + It("should cleanup successfully", func() { + iarConfig.manageCrdInstance(crDelete, manifest) + iarConfig.validateRunningStatus(shouldBeTerminated) + iarConfig.waitForNamespaceDeletion() + }) + }) + + Context("Falcon Image Analyzer with Tolerations", func() { + manifest := "./config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml" + It("should deploy successfully with tolerations", func() { + updateManifestApiCreds(manifest) + iarConfig.manageCrdInstance(crApply, manifest) + iarConfig.validateRunningStatus(shouldBeRunning) + iarConfig.validateCrStatus() + + // Get deployment name and validate tolerations + deploymentName, err := iarConfig.getDeploymentName() + Expect(err).NotTo(HaveOccurred()) + + expectedTolerations := []ExpectedToleration{ + {Key: "node-role.kubernetes.io/control-plane", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "node-role.kubernetes.io/master", Operator: "Exists", Effect: "NoSchedule"}, + {Key: "dedicated", Operator: "Equal", Value: "image-analyzer", Effect: "NoSchedule"}, + } + iarConfig.validateDeploymentTolerations(deploymentName, expectedTolerations) + }) + It("should cleanup successfully", func() { + iarConfig.manageCrdInstance(crDelete, manifest) + iarConfig.validateRunningStatus(shouldBeTerminated) + iarConfig.waitForNamespaceDeletion() + }) + }) + Context("Falcon Sidecar Sensor", func() { manifest := "./config/samples/falcon_v1alpha1_falconcontainer.yaml" It("should deploy successfully", func() { diff --git a/test/e2e/tolerations_test.go b/test/e2e/tolerations_test.go new file mode 100644 index 000000000..dd7f2fc41 --- /dev/null +++ b/test/e2e/tolerations_test.go @@ -0,0 +1,163 @@ +package e2e + +import ( + "encoding/json" + "fmt" + "os/exec" + "strings" + + "github.com/crowdstrike/falcon-operator/test/utils" + //nolint:golint + //nolint:revive + . "github.com/onsi/ginkgo/v2" + + //nolint:golint + //nolint:revive + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + appsv1 "k8s.io/api/apps/v1" +) + +// Toleration represents the expected toleration to validate +type ExpectedToleration struct { + Key string + Operator string + Value string + Effect string +} + +// validateDeploymentTolerations checks if a deployment has the expected tolerations +func (cr crConfig) validateDeploymentTolerations(deploymentName string, expectedTolerations []ExpectedToleration) { + By(fmt.Sprintf("validating tolerations for deployment %s in namespace %s", deploymentName, cr.namespace)) + + checkTolerations := func() error { + // Get the deployment as JSON + cmd := exec.Command("kubectl", "get", "deployment", deploymentName, + "-n", cr.namespace, "-o", "json") + output, err := utils.Run(cmd) + if err != nil { + return fmt.Errorf("failed to get deployment: %v", err) + } + + // Parse the deployment + var deployment appsv1.Deployment + err = json.Unmarshal(output, &deployment) + if err != nil { + return fmt.Errorf("failed to parse deployment JSON: %v", err) + } + + // Get the tolerations from the pod spec + podTolerations := deployment.Spec.Template.Spec.Tolerations + + // Verify each expected toleration exists + for _, expected := range expectedTolerations { + found := false + for _, toleration := range podTolerations { + if matchesToleration(toleration, expected) { + found = true + break + } + } + if !found { + return fmt.Errorf("expected toleration not found: Key=%s, Operator=%s, Value=%s, Effect=%s", + expected.Key, expected.Operator, expected.Value, expected.Effect) + } + } + + fmt.Printf("Found %d tolerations in deployment %s\n", len(podTolerations), deploymentName) + for _, t := range podTolerations { + fmt.Printf(" - Key: %s, Operator: %s, Value: %s, Effect: %s\n", + t.Key, t.Operator, t.Value, t.Effect) + } + + return nil + } + + EventuallyWithOffset(1, checkTolerations, defaultTimeout, defaultPollPeriod).Should(Succeed()) +} + +// matchesToleration checks if a toleration matches the expected values +func matchesToleration(actual corev1.Toleration, expected ExpectedToleration) bool { + // Handle empty operator (defaults to Equal) + actualOperator := string(actual.Operator) + if actualOperator == "" { + actualOperator = "Equal" + } + expectedOperator := expected.Operator + if expectedOperator == "" { + expectedOperator = "Equal" + } + + return actual.Key == expected.Key && + actualOperator == expectedOperator && + actual.Value == expected.Value && + string(actual.Effect) == expected.Effect +} + +// validateDaemonSetTolerations checks if a daemonset has the expected tolerations +func (cr crConfig) validateDaemonSetTolerations(daemonsetName string, expectedTolerations []ExpectedToleration) { + By(fmt.Sprintf("validating tolerations for daemonset %s in namespace %s", daemonsetName, cr.namespace)) + + checkTolerations := func() error { + // Get the daemonset as JSON + cmd := exec.Command("kubectl", "get", "daemonset", daemonsetName, + "-n", cr.namespace, "-o", "json") + output, err := utils.Run(cmd) + if err != nil { + return fmt.Errorf("failed to get daemonset: %v", err) + } + + // Parse the daemonset + var daemonset appsv1.DaemonSet + err = json.Unmarshal(output, &daemonset) + if err != nil { + return fmt.Errorf("failed to parse daemonset JSON: %v", err) + } + + // Get the tolerations from the pod spec + podTolerations := daemonset.Spec.Template.Spec.Tolerations + + // Verify each expected toleration exists + for _, expected := range expectedTolerations { + found := false + for _, toleration := range podTolerations { + if matchesToleration(toleration, expected) { + found = true + break + } + } + if !found { + return fmt.Errorf("expected toleration not found: Key=%s, Operator=%s, Value=%s, Effect=%s", + expected.Key, expected.Operator, expected.Value, expected.Effect) + } + } + + fmt.Printf("Found %d tolerations in daemonset %s\n", len(podTolerations), daemonsetName) + for _, t := range podTolerations { + fmt.Printf(" - Key: %s, Operator: %s, Value: %s, Effect: %s\n", + t.Key, t.Operator, t.Value, t.Effect) + } + + return nil + } + + EventuallyWithOffset(1, checkTolerations, defaultTimeout, defaultPollPeriod).Should(Succeed()) +} + +// getDeploymentName retrieves the deployment name for a given CR +func (cr crConfig) getDeploymentName() (string, error) { + cmd := exec.Command("kubectl", "get", "deployments", + "-n", cr.namespace, "-o", "jsonpath={.items[0].metadata.name}") + output, err := utils.Run(cmd) + if err != nil { + return "", fmt.Errorf("failed to get deployment name: %v", err) + } + + deploymentName := strings.TrimSpace(string(output)) + if deploymentName == "" { + return "", fmt.Errorf("no deployment found in namespace %s", cr.namespace) + } + + return deploymentName, nil +} \ No newline at end of file From bafe9637a44d6d93c1a80ef0115b52af05101328 Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 19:27:58 +0200 Subject: [PATCH 5/7] fix falcon-image-analyzer --- .../falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml b/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml index f3ba699c2..a91276849 100644 --- a/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml +++ b/config/samples/falcon_v1alpha1_falconimageanalyzer_with_tolerations.yaml @@ -9,7 +9,7 @@ metadata: crowdstrike.com/name: falconimageanalyzer crowdstrike.com/part-of: Falcon crowdstrike.com/provider: crowdstrike - name: falcon-iar + name: falcon-image-analyzer spec: falcon_api: client_id: PLEASE_FILL_IN From e1eebb97119d5007560226e1eda8bbefe4ac8a9d Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Mon, 29 Sep 2025 19:49:38 +0200 Subject: [PATCH 6/7] fix tests --- test/e2e/cr_config_test.go | 6 ++++++ test/e2e/e2e_test.go | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/test/e2e/cr_config_test.go b/test/e2e/cr_config_test.go index 8f3cd7964..e92a66873 100644 --- a/test/e2e/cr_config_test.go +++ b/test/e2e/cr_config_test.go @@ -38,6 +38,12 @@ var ( componentName: "admission_controller", } iarConfig = crConfig{ + kind: "FalconImageAnalyzer", + namespace: "falcon-iar", + metadataName: "falcon-image-analyzer", + componentName: "falcon-imageanalyzer", + } + iarDeploymentConfig = crConfig{ kind: "FalconImageAnalyzer", namespace: "falcon-iar", metadataName: "falcon-image-analyzer", diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index a241eb11c..7fa08a7c2 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -385,17 +385,17 @@ var _ = Describe("falcon", Ordered, func() { kacConfig.validateCrStatus() sidecarConfig.validateRunningStatus(shouldBeRunning) sidecarConfig.validateCrStatus() - iarConfig.validateRunningStatus(shouldBeRunning) - iarConfig.validateCrStatus() + iarDeploymentConfig.validateRunningStatus(shouldBeRunning) + iarDeploymentConfig.validateCrStatus() }) It("should cleanup successfully", func() { falconDeploymentConfig.manageCrdInstance(crDelete, manifest) kacConfig.validateRunningStatus(shouldBeTerminated) sidecarConfig.validateRunningStatus(shouldBeTerminated) - iarConfig.validateRunningStatus(shouldBeTerminated) + iarDeploymentConfig.validateRunningStatus(shouldBeTerminated) sidecarConfig.waitForNamespaceDeletion() kacConfig.waitForNamespaceDeletion() - iarConfig.waitForNamespaceDeletion() + iarDeploymentConfig.waitForNamespaceDeletion() }) }) @@ -408,17 +408,17 @@ var _ = Describe("falcon", Ordered, func() { kacConfig.validateCrStatus() nodeConfig.validateRunningStatus(shouldBeRunning) nodeConfig.validateCrStatus() - iarConfig.validateRunningStatus(shouldBeRunning) - iarConfig.validateCrStatus() + iarDeploymentConfig.validateRunningStatus(shouldBeRunning) + iarDeploymentConfig.validateCrStatus() }) It("should cleanup successfully", func() { falconDeploymentConfig.manageCrdInstance(crDelete, manifest) kacConfig.validateRunningStatus(shouldBeTerminated) nodeConfig.validateRunningStatus(shouldBeTerminated) - iarConfig.validateRunningStatus(shouldBeTerminated) + iarDeploymentConfig.validateRunningStatus(shouldBeTerminated) nodeConfig.waitForNamespaceDeletion() kacConfig.waitForNamespaceDeletion() - iarConfig.waitForNamespaceDeletion() + iarDeploymentConfig.waitForNamespaceDeletion() }) }) @@ -431,18 +431,18 @@ var _ = Describe("falcon", Ordered, func() { kacConfig.validateCrStatus() nodeConfig.validateRunningStatus(shouldBeRunning) nodeConfig.validateCrStatus() - iarConfig.validateRunningStatus(shouldBeRunning) - iarConfig.validateCrStatus() + iarDeploymentConfig.validateRunningStatus(shouldBeRunning) + iarDeploymentConfig.validateCrStatus() }) It("should cleanup successfully", func() { falconDeploymentConfig.manageCrdInstance(crDelete, manifest) kacConfig.validateRunningStatus(shouldBeTerminated) nodeConfig.validateRunningStatus(shouldBeTerminated) - iarConfig.validateRunningStatus(shouldBeTerminated) + iarDeploymentConfig.validateRunningStatus(shouldBeTerminated) secretConfig.deleteNamespace() nodeConfig.waitForNamespaceDeletion() kacConfig.waitForNamespaceDeletion() - iarConfig.waitForNamespaceDeletion() + iarDeploymentConfig.waitForNamespaceDeletion() secretConfig.waitForNamespaceDeletion() }) }) From 516f0cf1226c80dc424cd89936b44991a4c898a1 Mon Sep 17 00:00:00 2001 From: Ivan Aguilar Date: Fri, 17 Oct 2025 14:34:43 +0200 Subject: [PATCH 7/7] reconciler checks --- internal/controller/admission/falconadmission_controller.go | 5 +++++ .../falcon_image_analyzer/falconimage_controller.go | 5 +++++ test/e2e/tolerations_test.go | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/controller/admission/falconadmission_controller.go b/internal/controller/admission/falconadmission_controller.go index 4b9265aa4..27fd5c600 100644 --- a/internal/controller/admission/falconadmission_controller.go +++ b/internal/controller/admission/falconadmission_controller.go @@ -593,6 +593,11 @@ func (r *FalconAdmissionReconciler) reconcileAdmissionDeployment(ctx context.Con updated = true } + if !equality.Semantic.DeepEqual(existingDeployment.Spec.Template.Spec.Tolerations, dep.Spec.Template.Spec.Tolerations) { + existingDeployment.Spec.Template.Spec.Tolerations = dep.Spec.Template.Spec.Tolerations + updated = true + } + if len(dep.Spec.Template.Spec.Containers) != len(existingDeployment.Spec.Template.Spec.Containers) { existingDeployment.Spec.Template.Spec.Containers = dep.Spec.Template.Spec.Containers updated = true diff --git a/internal/controller/falcon_image_analyzer/falconimage_controller.go b/internal/controller/falcon_image_analyzer/falconimage_controller.go index 0364af0c6..334a192e8 100644 --- a/internal/controller/falcon_image_analyzer/falconimage_controller.go +++ b/internal/controller/falcon_image_analyzer/falconimage_controller.go @@ -333,6 +333,11 @@ func (r *FalconImageAnalyzerReconciler) reconcileImageAnalyzerDeployment(ctx con updated = true } + if !equality.Semantic.DeepEqual(existingDeployment.Spec.Template.Spec.Tolerations, dep.Spec.Template.Spec.Tolerations) { + existingDeployment.Spec.Template.Spec.Tolerations = dep.Spec.Template.Spec.Tolerations + updated = true + } + if updated { if err := k8sutils.Update(r.Client, ctx, req, log, falconImageAnalyzer, &falconImageAnalyzer.Status, existingDeployment); err != nil { return err diff --git a/test/e2e/tolerations_test.go b/test/e2e/tolerations_test.go index dd7f2fc41..5cd1fc804 100644 --- a/test/e2e/tolerations_test.go +++ b/test/e2e/tolerations_test.go @@ -15,8 +15,8 @@ import ( //nolint:revive . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" ) // Toleration represents the expected toleration to validate @@ -160,4 +160,4 @@ func (cr crConfig) getDeploymentName() (string, error) { } return deploymentName, nil -} \ No newline at end of file +}