From 8887d4e21c9ff563aa7fe7de67447b0e5eab1198 Mon Sep 17 00:00:00 2001 From: Gabe Alford Date: Wed, 25 Oct 2023 15:17:29 -0600 Subject: [PATCH] feat: enable GKE autopilot support - Add GKE sensor types - Set default resource and priorityclass configuration for GKE - Reconcile more objects in DS configuration change updates --- api/falcon/v1alpha1/falconnodesensor_types.go | 55 +++- api/falcon/v1alpha1/zz_generated.deepcopy.go | 80 ++++++ ...falcon-operator.clusterserviceversion.yaml | 58 +++- ...con.crowdstrike.com_falconnodesensors.yaml | 55 ++++ cmd/main.go | 4 + ...con.crowdstrike.com_falconnodesensors.yaml | 55 ++++ ...falcon-operator.clusterserviceversion.yaml | 45 ++++ config/rbac/role.yaml | 11 + .../falconnodesensor_controller.go | 154 ++++++++++- deploy/falcon-operator.yaml | 66 +++++ internal/controller/assets/daemonset.go | 248 +++++++++++++----- internal/controller/assets/daemonset_test.go | 82 +++++- internal/controller/assets/priorityclass.go | 29 ++ .../controller/assets/priorityclass_test.go | 40 +++ pkg/common/common_test.go | 18 +- pkg/common/constants.go | 3 +- pkg/common/funcs.go | 18 +- 17 files changed, 934 insertions(+), 87 deletions(-) create mode 100644 internal/controller/assets/priorityclass.go create mode 100644 internal/controller/assets/priorityclass_test.go diff --git a/api/falcon/v1alpha1/falconnodesensor_types.go b/api/falcon/v1alpha1/falconnodesensor_types.go index 06494c6e..513039df 100644 --- a/api/falcon/v1alpha1/falconnodesensor_types.go +++ b/api/falcon/v1alpha1/falconnodesensor_types.go @@ -65,16 +65,69 @@ type FalconNodeSensorConfig struct { // +kubebuilder:default=false // +operator-sdk:csv:customresourcedefinitions:type=spec,order=8 NodeCleanup *bool `json:"disableCleanup,omitempty"` + + // Configure resource requests and limits for the DaemonSet Sensor. Only applies when using the eBPF backend. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Falcon eBPF Sensor Resources",order=9 + SensorResources Resources `json:"resources,omitempty"` + // Sets the backend to be used by the DaemonSet Sensor. // +kubebuilder:default=kernel // +kubebuilder:validation:Enum=kernel;bpf - // +operator-sdk-csv:customresourcedefinitions:type=spec,order=9 + // +operator-sdk-csv:customresourcedefinitions:type=spec,order=10 Backend string `json:"backend,omitempty"` + // Enables the use of GKE Autopilot. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="GKE Autopilot Settings",order=11 + GKE AutoPilot `json:"gke,omitempty"` + + // Enable priority class for the DaemonSet. This is useful for GKE Autopilot clusters, but can be set for any cluster. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Priority Class",order=12 + PriorityClass PriorityClassConfig `json:"priorityClass,omitempty"` + // Version of the sensor to be installed. The latest version will be selected when this version specifier is missing. Version *string `json:"version,omitempty"` } +type PriorityClassConfig struct { + // Enables the operator to deploy a PriorityClass instead of rolling your own. Default is false. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Deploy Priority Class to cluster",order=2 + Deploy *bool `json:"deploy,omitempty"` + + // Name of the priority class to use for the DaemonSet. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Name of the Priority Class to use",order=2 + Name string `json:"name,omitempty"` + + // Value of the priority class to use for the DaemonSet. Requires the Deploy field to be set to true. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Priority Class Value",order=3 + Value *int32 `json:"value,omitempty"` +} + +type Resources struct { + // Sets the resource limits for the DaemonSet Sensor. Only applies when using the eBPF backend. + // +operator-sdk:csv:customresourcedefinitions:type=spec + Limits ResourceList `json:"limits,omitempty"` + // Sets the resource requests for the DaemonSet Sensor. Only applies when using the eBPF backend. + // +operator-sdk:csv:customresourcedefinitions:type=spec + Requests ResourceList `json:"requests,omitempty"` +} + +type ResourceList struct { + // Minimum allowed is 250m. + // +operator-sdk:csv:customresourcedefinitions:type=spec + // +kubebuilder:validation:Pattern="^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$" + CPU string `json:"cpu,omitempty"` + // Minimum allowed is 500Mi. + // +operator-sdk:csv:customresourcedefinitions:type=spec + // +kubebuilder:validation:Pattern="^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$" + Memory string `json:"memory,omitempty"` +} + +type AutoPilot struct { + // Enables the use of GKE Autopilot. + // +operator-sdk:csv:customresourcedefinitions:type=spec + Enabled *bool `json:"autopilot,omitempty"` +} + type FalconNodeUpdateStrategy struct { // +kubebuilder:default=RollingUpdate // +kubebuilder:validation:Enum=RollingUpdate;OnDelete diff --git a/api/falcon/v1alpha1/zz_generated.deepcopy.go b/api/falcon/v1alpha1/zz_generated.deepcopy.go index 87b21835..53d869ed 100644 --- a/api/falcon/v1alpha1/zz_generated.deepcopy.go +++ b/api/falcon/v1alpha1/zz_generated.deepcopy.go @@ -216,6 +216,26 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AutoPilot) DeepCopyInto(out *AutoPilot) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoPilot. +func (in *AutoPilot) DeepCopy() *AutoPilot { + if in == nil { + return nil + } + out := new(AutoPilot) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FalconAPI) DeepCopyInto(out *FalconAPI) { *out = *in @@ -784,6 +804,9 @@ func (in *FalconNodeSensorConfig) DeepCopyInto(out *FalconNodeSensorConfig) { *out = new(bool) **out = **in } + out.SensorResources = in.SensorResources + in.GKE.DeepCopyInto(&out.GKE) + in.PriorityClass.DeepCopyInto(&out.PriorityClass) if in.Version != nil { in, out := &in.Version, &out.Version *out = new(string) @@ -955,6 +978,31 @@ func (in *FalconSensor) DeepCopy() *FalconSensor { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PriorityClassConfig) DeepCopyInto(out *PriorityClassConfig) { + *out = *in + if in.Deploy != nil { + in, out := &in.Deploy, &out.Deploy + *out = new(bool) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PriorityClassConfig. +func (in *PriorityClassConfig) DeepCopy() *PriorityClassConfig { + if in == nil { + return nil + } + out := new(PriorityClassConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RegistrySpec) DeepCopyInto(out *RegistrySpec) { *out = *in @@ -990,3 +1038,35 @@ func (in *RegistryTLSSpec) DeepCopy() *RegistryTLSSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceList) DeepCopyInto(out *ResourceList) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceList. +func (in *ResourceList) DeepCopy() *ResourceList { + if in == nil { + return nil + } + out := new(ResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Resources) DeepCopyInto(out *Resources) { + *out = *in + out.Limits = in.Limits + out.Requests = in.Requests +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources. +func (in *Resources) DeepCopy() *Resources { + if in == nil { + return nil + } + out := new(Resources) + in.DeepCopyInto(out) + return out +} diff --git a/bundle/manifests/falcon-operator.clusterserviceversion.yaml b/bundle/manifests/falcon-operator.clusterserviceversion.yaml index 7d7ed190..09d4430d 100644 --- a/bundle/manifests/falcon-operator.clusterserviceversion.yaml +++ b/bundle/manifests/falcon-operator.clusterserviceversion.yaml @@ -101,7 +101,7 @@ metadata: capabilities: Basic Install categories: Security,Monitoring containerImage: quay.io/crowdstrike/falcon-operator - createdAt: "2023-10-17T19:56:45Z" + createdAt: "2023-10-25T21:27:38Z" description: Falcon Operator installs CrowdStrike Falcon Sensors on the cluster operatorframework.io/suggested-namespace: falcon-operator operators.operatorframework.io/builder: operator-sdk-v1.29.0 @@ -492,6 +492,13 @@ spec: mirror the original image to your repository/name:tag displayName: Image path: node.image + - description: Enables the operator to deploy a PriorityClass instead of rolling + your own. Default is false. + displayName: Deploy Priority Class to cluster + path: node.priorityClass.deploy + - description: Name of the priority class to use for the DaemonSet. + displayName: Name of the Priority Class to use + path: node.priorityClass.name - description: Disable the Falcon Sensor's use of a proxy. displayName: Disable Falcon Proxy path: falcon.apd @@ -506,6 +513,10 @@ spec: path: node - displayName: Image Pull Policy path: node.imagePullPolicy + - description: Value of the priority class to use for the DaemonSet. Requires + the Deploy field to be set to true. + displayName: Priority Class Value + path: node.priorityClass.value - description: The application proxy host to use for Falcon sensor proxy configuration. displayName: Disable Falcon Proxy Host path: falcon.aph @@ -550,6 +561,40 @@ spec: as sensor downgrading. displayName: Node Cleanup path: node.disableCleanup + - description: Configure resource requests and limits for the DaemonSet Sensor. + Only applies when using the eBPF backend. + displayName: Falcon eBPF Sensor Resources + path: node.resources + - description: Enables the use of GKE Autopilot. + displayName: GKE Autopilot Settings + path: node.gke + - description: Enable priority class for the DaemonSet. This is useful for GKE + Autopilot clusters, but can be set for any cluster. + displayName: Priority Class + path: node.priorityClass + - description: Enables the use of GKE Autopilot. + displayName: Enabled + path: node.gke.autopilot + - description: Sets the resource limits for the DaemonSet Sensor. Only applies + when using the eBPF backend. + displayName: Limits + path: node.resources.limits + - description: Minimum allowed is 250m. + displayName: CPU + path: node.resources.limits.cpu + - description: Minimum allowed is 500Mi. + displayName: Memory + path: node.resources.limits.memory + - description: Sets the resource requests for the DaemonSet Sensor. Only applies + when using the eBPF backend. + displayName: Requests + path: node.resources.requests + - description: Minimum allowed is 250m. + displayName: CPU + path: node.resources.requests.cpu + - description: Minimum allowed is 500Mi. + displayName: Memory + path: node.resources.requests.memory - description: Add metadata to the DaemonSet Service Account for IAM roles. displayName: Service Account path: node.serviceAccount @@ -883,6 +928,17 @@ spec: - list - update - watch + - apiGroups: + - scheduling.k8s.io + resources: + - priorityclasses + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - security.openshift.io resourceNames: diff --git a/bundle/manifests/falcon.crowdstrike.com_falconnodesensors.yaml b/bundle/manifests/falcon.crowdstrike.com_falconnodesensors.yaml index 14196dbd..d8fdc291 100644 --- a/bundle/manifests/falcon.crowdstrike.com_falconnodesensors.yaml +++ b/bundle/manifests/falcon.crowdstrike.com_falconnodesensors.yaml @@ -143,6 +143,13 @@ spec: on the nodes. Disabling might have unintended consequences for certain operations such as sensor downgrading. type: boolean + gke: + description: Enables the use of GKE Autopilot. + properties: + autopilot: + description: Enables the use of GKE Autopilot. + type: boolean + type: object image: description: Location of the Falcon Sensor image. Use only in cases when you mirror the original image to your repository/name:tag @@ -379,6 +386,54 @@ spec: type: object x-kubernetes-map-type: atomic type: object + priorityClass: + description: Enable priority class for the DaemonSet. This is + useful for GKE Autopilot clusters, but can be set for any cluster. + properties: + deploy: + description: Enables the operator to deploy a PriorityClass + instead of rolling your own. Default is false. + type: boolean + name: + description: Name of the priority class to use for the DaemonSet. + type: string + value: + description: Value of the priority class to use for the DaemonSet. + Requires the Deploy field to be set to true. + format: int32 + type: integer + type: object + resources: + description: Configure resource requests and limits for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + limits: + description: Sets the resource limits for the DaemonSet Sensor. + Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + requests: + description: Sets the resource requests for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + type: object serviceAccount: description: Add metadata to the DaemonSet Service Account for IAM roles. diff --git a/cmd/main.go b/cmd/main.go index e3a7661e..292347d3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,6 +33,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + schedulingv1 "k8s.io/api/scheduling/v1" falconv1alpha1 "github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1" admissioncontroller "github.com/crowdstrike/falcon-operator/controllers/admission" @@ -133,6 +134,9 @@ func main() { &corev1.Secret{}: {}, &rbacv1.ClusterRoleBinding{}: {}, &corev1.ServiceAccount{}: {}, + &schedulingv1.PriorityClass{}: { + Label: labels.SelectorFromSet(labels.Set{common.FalconComponentKey: common.FalconKernelSensor}), + }, &imagev1.ImageStream{}: { Label: labels.SelectorFromSet(labels.Set{common.FalconProviderKey: common.FalconProviderValue}), }, diff --git a/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml b/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml index 9c011104..f4ab1147 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml @@ -143,6 +143,13 @@ spec: on the nodes. Disabling might have unintended consequences for certain operations such as sensor downgrading. type: boolean + gke: + description: Enables the use of GKE Autopilot. + properties: + autopilot: + description: Enables the use of GKE Autopilot. + type: boolean + type: object image: description: Location of the Falcon Sensor image. Use only in cases when you mirror the original image to your repository/name:tag @@ -379,6 +386,54 @@ spec: type: object x-kubernetes-map-type: atomic type: object + priorityClass: + description: Enable priority class for the DaemonSet. This is + useful for GKE Autopilot clusters, but can be set for any cluster. + properties: + deploy: + description: Enables the operator to deploy a PriorityClass + instead of rolling your own. Default is false. + type: boolean + name: + description: Name of the priority class to use for the DaemonSet. + type: string + value: + description: Value of the priority class to use for the DaemonSet. + Requires the Deploy field to be set to true. + format: int32 + type: integer + type: object + resources: + description: Configure resource requests and limits for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + limits: + description: Sets the resource limits for the DaemonSet Sensor. + Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + requests: + description: Sets the resource requests for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + type: object serviceAccount: description: Add metadata to the DaemonSet Service Account for IAM roles. diff --git a/config/manifests/bases/falcon-operator.clusterserviceversion.yaml b/config/manifests/bases/falcon-operator.clusterserviceversion.yaml index 91557f61..eb7a61f2 100644 --- a/config/manifests/bases/falcon-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/falcon-operator.clusterserviceversion.yaml @@ -395,6 +395,13 @@ spec: mirror the original image to your repository/name:tag displayName: Image path: node.image + - description: Enables the operator to deploy a PriorityClass instead of rolling + your own. Default is false. + displayName: Deploy Priority Class to cluster + path: node.priorityClass.deploy + - description: Name of the priority class to use for the DaemonSet. + displayName: Name of the Priority Class to use + path: node.priorityClass.name - description: Disable the Falcon Sensor's use of a proxy. displayName: Disable Falcon Proxy path: falcon.apd @@ -409,6 +416,10 @@ spec: path: node - displayName: Image Pull Policy path: node.imagePullPolicy + - description: Value of the priority class to use for the DaemonSet. Requires + the Deploy field to be set to true. + displayName: Priority Class Value + path: node.priorityClass.value - description: The application proxy host to use for Falcon sensor proxy configuration. displayName: Disable Falcon Proxy Host path: falcon.aph @@ -453,6 +464,40 @@ spec: as sensor downgrading. displayName: Node Cleanup path: node.disableCleanup + - description: Configure resource requests and limits for the DaemonSet Sensor. + Only applies when using the eBPF backend. + displayName: Falcon eBPF Sensor Resources + path: node.resources + - description: Enables the use of GKE Autopilot. + displayName: GKE Autopilot Settings + path: node.gke + - description: Enable priority class for the DaemonSet. This is useful for GKE + Autopilot clusters, but can be set for any cluster. + displayName: Priority Class + path: node.priorityClass + - description: Enables the use of GKE Autopilot. + displayName: Enabled + path: node.gke.autopilot + - description: Sets the resource limits for the DaemonSet Sensor. Only applies + when using the eBPF backend. + displayName: Limits + path: node.resources.limits + - description: Minimum allowed is 250m. + displayName: CPU + path: node.resources.limits.cpu + - description: Minimum allowed is 500Mi. + displayName: Memory + path: node.resources.limits.memory + - description: Sets the resource requests for the DaemonSet Sensor. Only applies + when using the eBPF backend. + displayName: Requests + path: node.resources.requests + - description: Minimum allowed is 250m. + displayName: CPU + path: node.resources.requests.cpu + - description: Minimum allowed is 500Mi. + displayName: Memory + path: node.resources.requests.memory - description: Add metadata to the DaemonSet Service Account for IAM roles. displayName: Service Account path: node.serviceAccount diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 18b5d097..8064fddb 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -282,6 +282,17 @@ rules: - list - update - watch +- apiGroups: + - scheduling.k8s.io + resources: + - priorityclasses + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - security.openshift.io resourceNames: diff --git a/controllers/falcon_node/falconnodesensor_controller.go b/controllers/falcon_node/falconnodesensor_controller.go index 5479188b..37df3deb 100644 --- a/controllers/falcon_node/falconnodesensor_controller.go +++ b/controllers/falcon_node/falconnodesensor_controller.go @@ -16,6 +16,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + schedulingv1 "k8s.io/api/scheduling/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -58,6 +59,7 @@ func (r *FalconNodeSensorReconciler) SetupWithManager(mgr ctrl.Manager) error { //+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch //+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=clusterrolebindings,verbs=get;list;watch;create //+kubebuilder:rbac:groups="security.openshift.io",resources=securitycontextconstraints,resourceNames=privileged,verbs=use +//+kubebuilder:rbac:groups="scheduling.k8s.io",resources=priorityclasses,verbs=get;list;watch;create;delete;update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -115,6 +117,11 @@ func (r *FalconNodeSensorReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{Requeue: true}, nil } + err = r.handlePriorityClass(ctx, nodesensor, logger) + if err != nil { + return ctrl.Result{}, err + } + serviceAccount := common.NodeServiceAccountName created, err = r.handlePermissions(ctx, nodesensor, logger) @@ -243,10 +250,14 @@ func (r *FalconNodeSensorReconciler) Reconcile(ctx context.Context, req ctrl.Req affUpdate := updateDaemonSetAffinity(dsUpdate, dsTarget, nodesensor, logger) containerVolUpdate := updateDaemonSetContainerVolumes(dsUpdate, dsTarget, logger) volumeUpdates := updateDaemonSetVolumes(dsUpdate, dsTarget, logger) + resources := updateDaemonSetResources(dsUpdate, dsTarget, logger) + pc := updateDaemonSetPriorityClass(dsUpdate, dsTarget, logger) + capabilities := updateDaemonSetCapabilities(dsUpdate, dsTarget, logger) + initArgs := updateDaemonSetInitArgs(dsUpdate, dsTarget, logger) updated = updateDaemonSetContainerProxy(dsUpdate, nodesensor, logger) // Update the daemonset and re-spin pods with changes - if imgUpdate || tolsUpdate || affUpdate || containerVolUpdate || volumeUpdates || updated { + if imgUpdate || tolsUpdate || affUpdate || containerVolUpdate || volumeUpdates || resources || pc || capabilities || initArgs || updated { err = r.Update(ctx, dsUpdate) if err != nil { err = r.conditionsUpdate(falconv1alpha1.ConditionDaemonSetReady, @@ -369,6 +380,77 @@ func (r *FalconNodeSensorReconciler) handleNamespace(ctx context.Context, nodese return true, nil } +// handlePriorityClass creates and updates the priority class +func (r *FalconNodeSensorReconciler) handlePriorityClass(ctx context.Context, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) error { + existingPC := &schedulingv1.PriorityClass{} + pcName := nodesensor.Spec.Node.PriorityClass.Name + update := false + + if pcName == "" && nodesensor.Spec.Node.GKE.Enabled == nil && nodesensor.Spec.Node.PriorityClass.Deploy == nil { + return nil + } else if pcName != "" && nodesensor.Spec.Node.PriorityClass.Deploy == nil && + (nodesensor.Spec.Node.GKE.Enabled != nil && *nodesensor.Spec.Node.GKE.Enabled) { + //logger.Info("Skipping PriorityClass creation on GKE AutoPilot because an existing priority class name was provided") + return nil + } else if pcName != "" && (nodesensor.Spec.Node.PriorityClass.Deploy == nil && !*nodesensor.Spec.Node.PriorityClass.Deploy) { + logger.Info("Skipping PriorityClass creation because an existing priority class name was provided") + return nil + } else if pcName == "" && (nodesensor.Spec.Node.GKE.Enabled != nil && *nodesensor.Spec.Node.GKE.Enabled) { + pcName = nodesensor.Name + "-priorityclass" + nodesensor.Spec.Node.PriorityClass.Name = pcName + } + + pc := assets.PriorityClass(pcName, nodesensor.Spec.Node.PriorityClass.Value) + + err := r.Get(ctx, types.NamespacedName{Name: pcName, Namespace: nodesensor.TargetNs()}, existingPC) + if err != nil && errors.IsNotFound(err) { + err = ctrl.SetControllerReference(nodesensor, pc, r.Scheme) + if err != nil { + logger.Error(err, "Unable to assign Controller Reference to the PriorityClass") + } + + err = r.Create(ctx, pc) + if err != nil { + logger.Error(err, "Failed to create PriorityClass", "PriorityClass.Name", pcName) + return err + } + logger.Info("Creating FalconNodeSensor PriorityClass") + + return nil + } else if err != nil { + logger.Error(err, "Failed to get FalconNodeSensor PriorityClass") + return err + } + + if nodesensor.Spec.Node.PriorityClass.Value != nil && existingPC.Value != *nodesensor.Spec.Node.PriorityClass.Value { + update = true + } + + if nodesensor.Spec.Node.PriorityClass.Name != "" && existingPC.Name != nodesensor.Spec.Node.PriorityClass.Name { + update = true + } + + if update { + err = r.Delete(ctx, existingPC) + if err != nil { + return err + } + + err = ctrl.SetControllerReference(nodesensor, pc, r.Scheme) + if err != nil { + logger.Error(err, "Unable to assign Controller Reference to the PriorityClass") + } + + err = r.Create(ctx, pc) + if err != nil { + return err + } + logger.Info("Updating FalconNodeSensor PriorityClass") + } + + return nil +} + // handleConfigMaps creates and updates the node sensor configmap func (r *FalconNodeSensorReconciler) handleConfigMaps(ctx context.Context, config *node.ConfigCache, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) (*corev1.ConfigMap, bool, error) { var updated bool @@ -530,21 +612,28 @@ func updateDaemonSetAffinity(ds, origDS *appsv1.DaemonSet, nodesensor *falconv1a // If an update is needed, this will update the containervolumes from the given DaemonSet func updateDaemonSetContainerVolumes(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { containerVolumeMounts := &ds.Spec.Template.Spec.Containers[0].VolumeMounts - containerVolumeMountsUpdates := !reflect.DeepEqual(*containerVolumeMounts, origDS.Spec.Template.Spec.Containers[0].VolumeMounts) + containerVolumeMountsUpdates := !equality.Semantic.DeepEqual(*containerVolumeMounts, origDS.Spec.Template.Spec.Containers[0].VolumeMounts) if containerVolumeMountsUpdates { - logger.Info("Updating FalconNodeSensor DaemonSet container volumeMounts") + logger.Info("Updating FalconNodeSensor DaemonSet Container volumeMounts") *containerVolumeMounts = origDS.Spec.Template.Spec.Containers[0].VolumeMounts } + containerVolumeMounts = &ds.Spec.Template.Spec.InitContainers[0].VolumeMounts + containerVolumeMountsUpdates = !equality.Semantic.DeepEqual(*containerVolumeMounts, origDS.Spec.Template.Spec.InitContainers[0].VolumeMounts) + if containerVolumeMountsUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet InitContainer volumeMounts") + *containerVolumeMounts = origDS.Spec.Template.Spec.InitContainers[0].VolumeMounts + } + return containerVolumeMountsUpdates } // If an update is needed, this will update the volumes from the given DaemonSet func updateDaemonSetVolumes(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { volumeMounts := &ds.Spec.Template.Spec.Volumes - volumeMountsUpdates := !reflect.DeepEqual(*volumeMounts, origDS.Spec.Template.Spec.Volumes) + volumeMountsUpdates := !equality.Semantic.DeepEqual(*volumeMounts, origDS.Spec.Template.Spec.Volumes) if volumeMountsUpdates { - logger.Info("Updating FalconNodeSensor DaemonSet volumeMounts") + logger.Info("Updating FalconNodeSensor DaemonSet volumes") *volumeMounts = origDS.Spec.Template.Spec.Volumes } @@ -570,6 +659,61 @@ func updateDaemonSetImages(ds *appsv1.DaemonSet, origImg string, nodesensor *fal return imgUpdate } +// If an update is needed, this will update the resources from the given DaemonSet +func updateDaemonSetResources(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { + resources := &ds.Spec.Template.Spec.Containers[0].Resources + resourcesUpdates := !equality.Semantic.DeepEqual(*resources, origDS.Spec.Template.Spec.Containers[0].Resources) + if resourcesUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet resources") + *resources = origDS.Spec.Template.Spec.Containers[0].Resources + } + + return resourcesUpdates +} + +// If an update is needed, this will update the priority class from the given DaemonSet +func updateDaemonSetPriorityClass(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { + priorityClass := &ds.Spec.Template.Spec.PriorityClassName + priorityClassUpdates := *priorityClass != origDS.Spec.Template.Spec.PriorityClassName + if priorityClassUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet priority class") + *priorityClass = origDS.Spec.Template.Spec.PriorityClassName + } + + return priorityClassUpdates +} + +// If an update is needed, this will update the capabilities from the given DaemonSet +func updateDaemonSetCapabilities(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { + capabilities := &ds.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities + capabilitiesUpdates := !equality.Semantic.DeepEqual(*capabilities, origDS.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities) + if capabilitiesUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet Container capabilities") + *capabilities = origDS.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities + } + + capabilities = &ds.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities + capabilitiesUpdates = !equality.Semantic.DeepEqual(*capabilities, origDS.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities) + if capabilitiesUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet InitContainer capabilities") + *capabilities = origDS.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities + } + + return capabilitiesUpdates +} + +// If an update is needed, this will update the init args from the given DaemonSet +func updateDaemonSetInitArgs(ds, origDS *appsv1.DaemonSet, logger logr.Logger) bool { + initArgs := &ds.Spec.Template.Spec.InitContainers[0].Args + initArgsUpdates := !equality.Semantic.DeepEqual(*initArgs, origDS.Spec.Template.Spec.InitContainers[0].Args) + if initArgsUpdates { + logger.Info("Updating FalconNodeSensor DaemonSet init args") + *initArgs = origDS.Spec.Template.Spec.InitContainers[0].Args + } + + return initArgsUpdates +} + // handlePermissions creates and updates the service account, role and role binding func (r *FalconNodeSensorReconciler) handlePermissions(ctx context.Context, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) (bool, error) { created, err := r.handleServiceAccount(ctx, nodesensor, logger) diff --git a/deploy/falcon-operator.yaml b/deploy/falcon-operator.yaml index 4dd29d49..fb9e038f 100644 --- a/deploy/falcon-operator.yaml +++ b/deploy/falcon-operator.yaml @@ -2700,6 +2700,13 @@ spec: on the nodes. Disabling might have unintended consequences for certain operations such as sensor downgrading. type: boolean + gke: + description: Enables the use of GKE Autopilot. + properties: + autopilot: + description: Enables the use of GKE Autopilot. + type: boolean + type: object image: description: Location of the Falcon Sensor image. Use only in cases when you mirror the original image to your repository/name:tag @@ -2936,6 +2943,54 @@ spec: type: object x-kubernetes-map-type: atomic type: object + priorityClass: + description: Enable priority class for the DaemonSet. This is + useful for GKE Autopilot clusters, but can be set for any cluster. + properties: + deploy: + description: Enables the operator to deploy a PriorityClass + instead of rolling your own. Default is false. + type: boolean + name: + description: Name of the priority class to use for the DaemonSet. + type: string + value: + description: Value of the priority class to use for the DaemonSet. + Requires the Deploy field to be set to true. + format: int32 + type: integer + type: object + resources: + description: Configure resource requests and limits for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + limits: + description: Sets the resource limits for the DaemonSet Sensor. + Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + requests: + description: Sets the resource requests for the DaemonSet + Sensor. Only applies when using the eBPF backend. + properties: + cpu: + description: Minimum allowed is 250m. + pattern: ^(([0-9]{4,}|[2-9][5-9][0-9])m$)|[0-9]+$ + type: string + memory: + description: Minimum allowed is 500Mi. + pattern: ^(([5-9][0-9]{2}[Mi]+)|([0-9.]+[iEGTP]+))|(([5-9][0-9]{8})|([0-9]{10,}))$ + type: string + type: object + type: object serviceAccount: description: Add metadata to the DaemonSet Service Account for IAM roles. @@ -3555,6 +3610,17 @@ rules: - list - update - watch +- apiGroups: + - scheduling.k8s.io + resources: + - priorityclasses + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - security.openshift.io resourceNames: diff --git a/internal/controller/assets/daemonset.go b/internal/controller/assets/daemonset.go index 9e40755f..40c1277e 100644 --- a/internal/controller/assets/daemonset.go +++ b/internal/controller/assets/daemonset.go @@ -7,6 +7,7 @@ import ( "github.com/crowdstrike/falcon-operator/pkg/common" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -60,16 +61,173 @@ func dsUpdateStrategy(node *falconv1alpha1.FalconNodeSensor) appsv1.DaemonSetUpd return appsv1.DaemonSetUpdateStrategy{Type: appsv1.OnDeleteDaemonSetStrategyType} } +func sensorCapabilities(node *falconv1alpha1.FalconNodeSensor, initContainer bool) *corev1.Capabilities { + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + if initContainer { + return &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_ADMIN", + "SYS_PTRACE", + "SYS_CHROOT", + "DAC_READ_SEARCH", + }, + } + } + return &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_ADMIN", + "SETGID", + "SETUID", + "SYS_PTRACE", + "SYS_CHROOT", + "DAC_OVERRIDE", + "SETPCAP", + "DAC_READ_SEARCH", + "BPF", + "PERFMON", + "SYS_RESOURCE", + "NET_RAW", + "CHOWN", + }, + } + } + return nil +} + +func dsResources(node *falconv1alpha1.FalconNodeSensor) corev1.ResourceRequirements { + if node.Spec.Node.Backend == "bpf" { + var limitResources, requestsResources corev1.ResourceList + + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + limitResources = corev1.ResourceList{ + "cpu": resource.MustParse("750m"), + "memory": resource.MustParse("1.5Gi"), + } + requestsResources = corev1.ResourceList{ + "cpu": resource.MustParse("750m"), + "memory": resource.MustParse("1.5Gi"), + } + } + + if node.Spec.Node.SensorResources.Limits.CPU != "" { + limitResources["cpu"] = resource.MustParse(node.Spec.Node.SensorResources.Limits.CPU) + } + if node.Spec.Node.SensorResources.Limits.Memory != "" { + limitResources["memory"] = resource.MustParse(node.Spec.Node.SensorResources.Limits.Memory) + } + if node.Spec.Node.SensorResources.Requests.CPU != "" { + requestsResources["cpu"] = resource.MustParse(node.Spec.Node.SensorResources.Requests.CPU) + } + if node.Spec.Node.SensorResources.Requests.Memory != "" { + requestsResources["memory"] = resource.MustParse(node.Spec.Node.SensorResources.Requests.Memory) + } + + return corev1.ResourceRequirements{ + Limits: limitResources, + Requests: requestsResources, + } + } + + node.Spec.Node.SensorResources = falconv1alpha1.Resources{} + return corev1.ResourceRequirements{} +} + +// initArgs - remove this function when 6.53 is no longer supported. 6.53+ will use InitContainerArgs() +func initArgs(node *falconv1alpha1.FalconNodeSensor) []string { + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + return common.InitContainerArgs() + } + return common.LegacyInitContainerArgs() +} + +// cleanupArgs - remove this function when 6.53 is no longer supported. 6.53+ will use InitCleanupArgs() +func cleanupArgs(node *falconv1alpha1.FalconNodeSensor) []string { + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + return common.InitCleanupArgs() + } + return common.LegacyInitCleanupArgs() +} + +// volumes - remove this function when 6.53 is no longer supported. 6.53+ will only use falconstore +func volumes(node *falconv1alpha1.FalconNodeSensor) []corev1.Volume { + pathTypeUnset := corev1.HostPathUnset + pathDirCreate := corev1.HostPathDirectoryOrCreate + + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + return []corev1.Volume{ + { + Name: "falconstore", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: common.FalconStoreFile, + Type: &pathTypeUnset, + }, + }, + }, + } + } + + return []corev1.Volume{ + { + Name: "falconstore", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: common.FalconStoreFile, + Type: &pathTypeUnset, + }, + }, + }, + { + Name: "falconstore-hostdir", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: common.FalconHostInstallDir, + Type: &pathDirCreate, + }, + }, + }, + } +} + +func volumeMounts(node *falconv1alpha1.FalconNodeSensor, name string) []corev1.VolumeMount { + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + return []corev1.VolumeMount{} + } + + return []corev1.VolumeMount{ + { + Name: name, + MountPath: common.FalconInitHostInstallDir, + }, + } +} + +func volumesCleanup(node *falconv1alpha1.FalconNodeSensor) []corev1.Volume { + if node.Spec.Node.GKE.Enabled != nil && *node.Spec.Node.GKE.Enabled { + return []corev1.Volume{} + } + return []corev1.Volume{ + { + Name: "opt-crowdstrike", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: common.FalconHostInstallDir, + }, + }, + }, + } + +} + func Daemonset(dsName, image, serviceAccount string, node *falconv1alpha1.FalconNodeSensor) *appsv1.DaemonSet { privileged := true escalation := true - readOnlyFs := false + readOnlyFSDisabled := false + readOnlyFSEnabled := true hostpid := true hostnetwork := true hostipc := true - runAs := int64(0) - pathTypeUnset := corev1.HostPathUnset - pathDirCreate := corev1.HostPathDirectoryOrCreate + runAsRoot := int64(0) return &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ @@ -101,21 +259,17 @@ func Daemonset(dsName, image, serviceAccount string, node *falconv1alpha1.Falcon ImagePullSecrets: pullSecrets(node), InitContainers: []corev1.Container{ { - Name: "init-falconstore", - Image: image, - Command: common.FalconShellCommand, - Args: common.InitContainerArgs(), - VolumeMounts: []corev1.VolumeMount{ - { - Name: "falconstore-hostdir", - MountPath: common.FalconInitHostInstallDir, - }, - }, + Name: "init-falconstore", + Image: image, + Command: common.FalconShellCommand, + Args: initArgs(node), + VolumeMounts: volumeMounts(node, "falconstore-hostdir"), SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, - ReadOnlyRootFilesystem: &readOnlyFs, + RunAsUser: &runAsRoot, + ReadOnlyRootFilesystem: &readOnlyFSEnabled, AllowPrivilegeEscalation: &escalation, + Capabilities: sensorCapabilities(node, true), }, }, }, @@ -124,9 +278,10 @@ func Daemonset(dsName, image, serviceAccount string, node *falconv1alpha1.Falcon { SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, - ReadOnlyRootFilesystem: &readOnlyFs, + RunAsUser: &runAsRoot, + ReadOnlyRootFilesystem: &readOnlyFSDisabled, AllowPrivilegeEscalation: &escalation, + Capabilities: sensorCapabilities(node, false), }, Name: "falcon-node-sensor", Image: image, @@ -146,28 +301,11 @@ func Daemonset(dsName, image, serviceAccount string, node *falconv1alpha1.Falcon MountPath: common.FalconStoreFile, }, }, + Resources: dsResources(node), }, }, - Volumes: []corev1.Volume{ - { - Name: "falconstore", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: common.FalconStoreFile, - Type: &pathTypeUnset, - }, - }, - }, - { - Name: "falconstore-hostdir", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: common.FalconHostInstallDir, - Type: &pathDirCreate, - }, - }, - }, - }, + Volumes: volumes(node), + PriorityClassName: node.Spec.Node.PriorityClass.Name, }, }, }, @@ -176,10 +314,12 @@ func Daemonset(dsName, image, serviceAccount string, node *falconv1alpha1.Falcon func RemoveNodeDirDaemonset(dsName, image, serviceAccount string, node *falconv1alpha1.FalconNodeSensor) *appsv1.DaemonSet { privileged := true + nonPrivileged := false escalation := true - readOnlyFs := false + allowEscalation := false + readOnlyFs := true hostpid := true - runAs := int64(0) + runAsRoot := int64(0) return &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ @@ -211,29 +351,24 @@ func RemoveNodeDirDaemonset(dsName, image, serviceAccount string, node *falconv1 Name: "cleanup-opt-crowdstrike", Image: image, Command: common.FalconShellCommand, - Args: common.InitCleanupArgs(), + Args: cleanupArgs(node), SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, + RunAsUser: &runAsRoot, ReadOnlyRootFilesystem: &readOnlyFs, AllowPrivilegeEscalation: &escalation, + Capabilities: sensorCapabilities(node, true), }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "opt-crowdstrike", - MountPath: common.FalconInitHostInstallDir, - }, - }, + VolumeMounts: volumeMounts(node, "opt-crowdstrike"), }, }, ServiceAccountName: serviceAccount, Containers: []corev1.Container{ { SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - RunAsUser: &runAs, + Privileged: &nonPrivileged, ReadOnlyRootFilesystem: &readOnlyFs, - AllowPrivilegeEscalation: &escalation, + AllowPrivilegeEscalation: &allowEscalation, }, Name: "cleanup-sleep", Image: image, @@ -241,16 +376,7 @@ func RemoveNodeDirDaemonset(dsName, image, serviceAccount string, node *falconv1 Args: common.CleanupSleep(), }, }, - Volumes: []corev1.Volume{ - { - Name: "opt-crowdstrike", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: common.FalconHostInstallDir, - }, - }, - }, - }, + Volumes: volumesCleanup(node), }, }, }, diff --git a/internal/controller/assets/daemonset_test.go b/internal/controller/assets/daemonset_test.go index 1a32cc68..da4260e6 100644 --- a/internal/controller/assets/daemonset_test.go +++ b/internal/controller/assets/daemonset_test.go @@ -129,6 +129,58 @@ func TestDsUpdateStrategy(t *testing.T) { } } +func TestSensorCapabilities(t *testing.T) { + falconNode := falconv1alpha1.FalconNodeSensor{} + enabled := true + + // Check default return value when GKE is not enabled + got := sensorCapabilities(&falconNode, false) + wantNil := (*corev1.Capabilities)(nil) + if diff := cmp.Diff(wantNil, got); diff != "" { + t.Errorf("sensorCapabilities() mismatch (-want +got): %s", diff) + } + + // Check return value when GKE is enabled + falconNode.Spec.Node.GKE.Enabled = &enabled + got = sensorCapabilities(&falconNode, false) + want := &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_ADMIN", + "SETGID", + "SETUID", + "SYS_PTRACE", + "SYS_CHROOT", + "DAC_OVERRIDE", + "SETPCAP", + "DAC_READ_SEARCH", + "BPF", + "PERFMON", + "SYS_RESOURCE", + "NET_RAW", + "CHOWN", + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("sensorCapabilities() mismatch (-want +got): %s", diff) + } + + // check if initContainer is enabled + got = sensorCapabilities(&falconNode, true) + want = &corev1.Capabilities{ + Add: []corev1.Capability{ + "SYS_ADMIN", + "SYS_PTRACE", + "SYS_CHROOT", + "DAC_READ_SEARCH", + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("sensorCapabilities() mismatch (-want +got): %s", diff) + } +} + func TestDaemonset(t *testing.T) { falconNode := falconv1alpha1.FalconNodeSensor{} falconNode.Namespace = "falcon-system" @@ -138,11 +190,12 @@ func TestDaemonset(t *testing.T) { privileged := true escalation := true - readOnlyFs := false + readOnlyFSDisabled := false + readOnlyFSEnabled := true hostpid := true hostnetwork := true hostipc := true - runAs := int64(0) + runAsRoot := int64(0) pathTypeUnset := corev1.HostPathUnset pathDirCreate := corev1.HostPathDirectoryOrCreate @@ -177,11 +230,11 @@ func TestDaemonset(t *testing.T) { Name: "init-falconstore", Image: image, Command: common.FalconShellCommand, - Args: common.InitContainerArgs(), + Args: initArgs(&falconNode), SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, - ReadOnlyRootFilesystem: &readOnlyFs, + RunAsUser: &runAsRoot, + ReadOnlyRootFilesystem: &readOnlyFSEnabled, AllowPrivilegeEscalation: &escalation, }, VolumeMounts: []corev1.VolumeMount{ @@ -197,8 +250,8 @@ func TestDaemonset(t *testing.T) { { SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, - ReadOnlyRootFilesystem: &readOnlyFs, + RunAsUser: &runAsRoot, + ReadOnlyRootFilesystem: &readOnlyFSDisabled, AllowPrivilegeEscalation: &escalation, }, Name: "falcon-node-sensor", @@ -260,10 +313,12 @@ func TestRemoveNodeDirDaemonset(t *testing.T) { dsName := "test-DaemonSet" privileged := true + nonPrivileged := false escalation := true - readOnlyFs := false + allowEscalation := false + readOnlyFs := true hostpid := true - runAs := int64(0) + runAsRoot := int64(0) want := &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ @@ -293,10 +348,10 @@ func TestRemoveNodeDirDaemonset(t *testing.T) { Name: "cleanup-opt-crowdstrike", Image: image, Command: common.FalconShellCommand, - Args: common.InitCleanupArgs(), + Args: cleanupArgs(&falconNode), SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, - RunAsUser: &runAs, + RunAsUser: &runAsRoot, ReadOnlyRootFilesystem: &readOnlyFs, AllowPrivilegeEscalation: &escalation, }, @@ -312,10 +367,9 @@ func TestRemoveNodeDirDaemonset(t *testing.T) { Containers: []corev1.Container{ { SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - RunAsUser: &runAs, + Privileged: &nonPrivileged, ReadOnlyRootFilesystem: &readOnlyFs, - AllowPrivilegeEscalation: &escalation, + AllowPrivilegeEscalation: &allowEscalation, }, Name: "cleanup-sleep", Image: image, diff --git a/internal/controller/assets/priorityclass.go b/internal/controller/assets/priorityclass.go new file mode 100644 index 00000000..290b40f2 --- /dev/null +++ b/internal/controller/assets/priorityclass.go @@ -0,0 +1,29 @@ +package assets + +import ( + "github.com/crowdstrike/falcon-operator/pkg/common" + schedulingv1 "k8s.io/api/scheduling/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func PriorityClass(name string, value *int32) *schedulingv1.PriorityClass { + defaultValue := int32(1000000000) + labels := common.CRLabels("priorityclass", name, common.FalconKernelSensor) + + if value == nil { + value = &defaultValue + } + + return &schedulingv1.PriorityClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: schedulingv1.SchemeGroupVersion.String(), + Kind: "PriorityClass", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + }, + Description: "This priority class would be used to deploy CrowdStrike Falcon node sensor", + Value: *value, + } +} diff --git a/internal/controller/assets/priorityclass_test.go b/internal/controller/assets/priorityclass_test.go new file mode 100644 index 00000000..e012fa43 --- /dev/null +++ b/internal/controller/assets/priorityclass_test.go @@ -0,0 +1,40 @@ +package assets + +import ( + "testing" + + "github.com/crowdstrike/falcon-operator/pkg/common" + "github.com/google/go-cmp/cmp" + schedulingv1 "k8s.io/api/scheduling/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TestPriorityClass tests the PriorityClass function +func TestPriorityClass(t *testing.T) { + name := "test" + value := int32(1000000000) + want := &schedulingv1.PriorityClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: schedulingv1.SchemeGroupVersion.String(), + Kind: "PriorityClass", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: common.CRLabels("priorityclass", name, common.FalconKernelSensor), + }, + Description: "This priority class would be used to deploy CrowdStrike Falcon node sensor", + Value: value, + } + + // Test with nil value + got := PriorityClass(name, nil) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("PriorityClass() mismatch (-want +got): %s", diff) + } + + // Test with defined value + got = PriorityClass(name, &value) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("PriorityClass() mismatch (-want +got): %s", diff) + } +} diff --git a/pkg/common/common_test.go b/pkg/common/common_test.go index f4849745..7bee957a 100644 --- a/pkg/common/common_test.go +++ b/pkg/common/common_test.go @@ -18,19 +18,33 @@ type FakeDiscovery struct { } func TestInitContainerArgs(t *testing.T) { - want := []string{"-c", `if [ -x "/opt/CrowdStrike/falcon-daemonset-init" ]; then echo "Executing falcon-daemonset-init -i"; falcon-daemonset-init -i ; else if [ -d "/host_opt/CrowdStrike/falconstore" ]; then echo "Re-creating /opt/CrowdStrike/falconstore as it is a directory instead of a file"; rm -rf /host_opt/CrowdStrike/falconstore; fi; mkdir -p /host_opt/CrowdStrike/ && touch /host_opt/CrowdStrike/falconstore; fi`} + want := []string{"-c", `echo "Running /opt/CrowdStrike/falcon-daemonset-init -i"; /opt/CrowdStrike/falcon-daemonset-init -i`} if got := InitContainerArgs(); !reflect.DeepEqual(got, want) { t.Errorf("InitContainerArgs() = %v, want %v", got, want) } } func TestInitCleanupArgs(t *testing.T) { - want := []string{"-c", `if [ -x "/opt/CrowdStrike/falcon-daemonset-init" ]; then echo "Running falcon-daemonset-init -u"; falcon-daemonset-init -u; else echo "Manually removing /host_opt/CrowdStrike/"; rm -rf /host_opt/CrowdStrike/; fi`} + want := []string{"-c", `echo "Running /opt/CrowdStrike/falcon-daemonset-init -u"; /opt/CrowdStrike/falcon-daemonset-init -u`} if got := InitCleanupArgs(); !reflect.DeepEqual(got, want) { t.Errorf("InitCleanupArgs() = %v, want %v", got, want) } } +func TestLegacyInitContainerArgs(t *testing.T) { + want := []string{"-c", `if [ -x "/opt/CrowdStrike/falcon-daemonset-init -i" ]; then echo "Executing falcon-daemonset-init -i"; falcon-daemonset-init -i ; else if [ -d "/host_opt/CrowdStrike/falconstore" ]; then echo "Re-creating /opt/CrowdStrike/falconstore as it is a directory instead of a file"; rm -rf /host_opt/CrowdStrike/falconstore; fi; mkdir -p /host_opt/CrowdStrike/ && touch /host_opt/CrowdStrike/falconstore; fi`} + if got := LegacyInitContainerArgs(); !reflect.DeepEqual(got, want) { + t.Errorf("LegacyInitContainerArgs() = %v, want %v", got, want) + } +} + +func TestLegacyInitCleanupArgs(t *testing.T) { + want := []string{"-c", `if [ -x "/opt/CrowdStrike/falcon-daemonset-init -u" ]; then echo "Running falcon-daemonset-init -u"; falcon-daemonset-init -u; else echo "Manually removing /host_opt/CrowdStrike/"; rm -rf /host_opt/CrowdStrike/; fi`} + if got := LegacyInitCleanupArgs(); !reflect.DeepEqual(got, want) { + t.Errorf("LegacyInitCleanupArgs() = %v, want %v", got, want) + } +} + func TestCleanupSleep(t *testing.T) { want := []string{"-c", "sleep 10"} if got := CleanupSleep(); !reflect.DeepEqual(got, want) { diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 31ff6b5f..7e6820e8 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -17,9 +17,10 @@ const ( FalconInitDataDir = "/host_opt/CrowdStrike/" FalconStoreFile = "/opt/CrowdStrike/falconstore" FalconInitStoreFile = "/host_opt/CrowdStrike/falconstore" - FalconDaemonsetInitBinary = "/opt/CrowdStrike/falcon-daemonset-init" + FalconDaemonsetInitBinary = "/opt/CrowdStrike/falcon-daemonset-init -i" FalconDaemonsetInitBinaryInvocation = "falcon-daemonset-init -i" FalconDaemonsetCleanupBinaryInvocation = "falcon-daemonset-init -u" + FalconDaemonsetCleanupBinary = "/opt/CrowdStrike/falcon-daemonset-init -u" FalconContainerProbePath = "/live" FalconAdmissionClientStartupProbePath = "/startz" FalconAdmissionClientLivenessProbePath = "/livez" diff --git a/pkg/common/funcs.go b/pkg/common/funcs.go index 0f8245dc..639db104 100644 --- a/pkg/common/funcs.go +++ b/pkg/common/funcs.go @@ -16,6 +16,20 @@ import ( ) func InitContainerArgs() []string { + return []string{ + "-c", + fmt.Sprintf("echo \"Running %[1]s\"; %[1]s", FalconDaemonsetInitBinary), + } +} + +func InitCleanupArgs() []string { + return []string{ + "-c", + fmt.Sprintf("echo \"Running %[1]s\"; %[1]s", FalconDaemonsetCleanupBinary), + } +} + +func LegacyInitContainerArgs() []string { return []string{ "-c", // Versions of falcon-sensor 6.53+ will contain an init binary that we invoke @@ -29,11 +43,11 @@ func InitContainerArgs() []string { } } -func InitCleanupArgs() []string { +func LegacyInitCleanupArgs() []string { return []string{ "-c", // Versions of falcon-sensor 6.53+ will contain an init binary that we invoke with a cleanup argument - `if [ -x "` + FalconDaemonsetInitBinary + `" ]; then ` + + `if [ -x "` + FalconDaemonsetCleanupBinary + `" ]; then ` + `echo "Running ` + FalconDaemonsetCleanupBinaryInvocation + `"; ` + FalconDaemonsetCleanupBinaryInvocation + `; else ` + `echo "Manually removing ` + FalconInitDataDir + `"; ` + `rm -rf ` + FalconInitDataDir +