From 58c39756ed55440a86211762e5aaab0dde1908dd Mon Sep 17 00:00:00 2001 From: Yehonathan Bruchim Date: Tue, 24 Dec 2024 22:27:40 +0200 Subject: [PATCH] cdp: added resources and verbs for the cluster role --- api/falcon/v1alpha1/falconnodesensor_types.go | 5 ++ api/falcon/v1alpha1/zz_generated.deepcopy.go | 5 ++ ...con.crowdstrike.com_falconnodesensors.yaml | 4 ++ deploy/falcon-operator.yaml | 7 +++ docs/resources/node/README.md | 1 + .../falconnodesensor_controller.go | 58 ++++++++++++++++++- .../falconnodesensor_controller_test.go | 6 ++ pkg/common/constants.go | 1 + 8 files changed, 85 insertions(+), 2 deletions(-) diff --git a/api/falcon/v1alpha1/falconnodesensor_types.go b/api/falcon/v1alpha1/falconnodesensor_types.go index d1ff5f6d..c1137197 100644 --- a/api/falcon/v1alpha1/falconnodesensor_types.go +++ b/api/falcon/v1alpha1/falconnodesensor_types.go @@ -110,6 +110,11 @@ type FalconNodeSensorConfig struct { // For more information, please see https://github.com/CrowdStrike/falcon-operator/blob/main/docs/ADVANCED.md. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="DaemonSet Advanced Settings" Advanced FalconAdvanced `json:"advanced,omitempty"` + + // Enabled the Cloud Data Protection module + // +kubebuilder:default=true + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=13 + CdpRolesEnabled *bool `json:"cdpRolesEnabled,omitempty"` } type PriorityClassConfig struct { diff --git a/api/falcon/v1alpha1/zz_generated.deepcopy.go b/api/falcon/v1alpha1/zz_generated.deepcopy.go index f1490388..e2a4b1c5 100644 --- a/api/falcon/v1alpha1/zz_generated.deepcopy.go +++ b/api/falcon/v1alpha1/zz_generated.deepcopy.go @@ -1088,6 +1088,11 @@ func (in *FalconNodeSensorConfig) DeepCopyInto(out *FalconNodeSensorConfig) { **out = **in } in.Advanced.DeepCopyInto(&out.Advanced) + if in.CdpRolesEnabled != nil { + in, out := &in.CdpRolesEnabled, &out.CdpRolesEnabled + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalconNodeSensorConfig. diff --git a/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml b/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml index 5f3371f7..653763c1 100644 --- a/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml +++ b/config/crd/bases/falcon.crowdstrike.com_falconnodesensors.yaml @@ -170,6 +170,10 @@ spec: - kernel - bpf type: string + cdpRolesEnabled: + default: true + description: Enabled the Cloud Data Protection module + type: boolean disableCleanup: default: false description: Disables the cleanup of the sensor through DaemonSet diff --git a/deploy/falcon-operator.yaml b/deploy/falcon-operator.yaml index da6b3a14..3d8abb3e 100644 --- a/deploy/falcon-operator.yaml +++ b/deploy/falcon-operator.yaml @@ -3259,6 +3259,10 @@ spec: - kernel - bpf type: string + cdpRolesEnabled: + default: true + description: Enabled the Cloud Data Protection module + type: boolean disableCleanup: default: false description: Disables the cleanup of the sensor through DaemonSet @@ -4317,6 +4321,9 @@ rules: - securitycontextconstraints verbs: - use +- apiGroups: [""] + resources: ["pods", "services", "nodes", "daemonsets", "replicasets", "deployments", "jobs", "ingresses", "cronjobs", "persistentvolumes"] + verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/docs/resources/node/README.md b/docs/resources/node/README.md index ffc824c1..72a4b992 100644 --- a/docs/resources/node/README.md +++ b/docs/resources/node/README.md @@ -68,6 +68,7 @@ spec: | node.backend | (optional) Configure the backend mode for Falcon Sensor (allowed values: kernel, bpf) | | node.disableCleanup | (optional) Cleans up `/opt/CrowdStrike` on the nodes by deleting the files and directory. | | node.version | (optional) Enforce particular Falcon Sensor version to be installed (example: "6.35", "6.35.0-13207") | +| node.cdpRolesEnabled | (Enable cluster roles for Cloud Data Protection module | #### Falcon Sensor Settings | Spec | Description | diff --git a/internal/controller/falcon_node/falconnodesensor_controller.go b/internal/controller/falcon_node/falconnodesensor_controller.go index 5cc63820..d0d2d883 100644 --- a/internal/controller/falcon_node/falconnodesensor_controller.go +++ b/internal/controller/falcon_node/falconnodesensor_controller.go @@ -2,6 +2,7 @@ package falcon import ( "context" + goerr "errors" "reflect" falconv1alpha1 "github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1" @@ -15,6 +16,7 @@ import ( "github.com/crowdstrike/gofalcon/falcon" "github.com/go-logr/logr" "github.com/operator-framework/operator-lib/proxy" + "golang.org/x/exp/slices" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -33,6 +35,16 @@ import ( clog "sigs.k8s.io/controller-runtime/pkg/log" ) +var ( + cdpRoleEnabledNil = goerr.New("CdpRolesEnabled must be defined") + + cdpRoles = rbacv1.PolicyRule{ + APIGroups: []string{""}, + Verbs: []string{"get", "watch", "list"}, + Resources: []string{"pods", "services", "nodes", "daemonsets", "replicasets", "deployments", "jobs", "ingresses", "cronjobs", "persistentvolumes"}, + } +) + // FalconNodeSensorReconciler reconciles a FalconNodeSensor object type FalconNodeSensorReconciler struct { client.Client @@ -790,6 +802,11 @@ func (r *FalconNodeSensorReconciler) handlePermissions(ctx context.Context, node return created, err } + created, err = r.handleClusterRole(ctx, nodesensor, logger) + if created || err != nil { + return created, err + } + return r.handleClusterRoleBinding(ctx, nodesensor, logger) } @@ -810,7 +827,7 @@ func (r *FalconNodeSensorReconciler) handleClusterRoleBinding(ctx context.Contex RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", - Name: "falcon-operator-node-sensor-role", + Name: common.NodeClusterRoleName, }, Subjects: []rbacv1.Subject{ { @@ -829,7 +846,7 @@ func (r *FalconNodeSensorReconciler) handleClusterRoleBinding(ctx context.Contex logger.Info("Creating FalconNodeSensor ClusterRoleBinding") err = r.Create(ctx, &binding) if err != nil && !errors.IsAlreadyExists(err) { - logger.Error(err, "Failed to create new ClusterRoleBinding", "ClusteRoleBinding.Name", common.NodeClusterRoleBindingName) + logger.Error(err, "Failed to create new ClusterRoleBinding", "ClusterRoleBinding.Name", common.NodeClusterRoleBindingName) return false, err } @@ -880,6 +897,43 @@ func (r *FalconNodeSensorReconciler) handleServiceAccount(ctx context.Context, n return false, nil } +// handleClusterRole updates the cluster role and grants necessary permissions to it +func (r *FalconNodeSensorReconciler) handleClusterRole(ctx context.Context, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) (bool, error) { + if nodesensor.Spec.Node.CdpRolesEnabled == nil { + return false, cdpRoleEnabledNil + } + + if *nodesensor.Spec.Node.CdpRolesEnabled == false { + return false, nil + } + clusterRole := rbacv1.ClusterRole{} + err := r.Get(ctx, types.NamespacedName{Name: common.NodeClusterRoleName}, &clusterRole) + if err != nil { + logger.Error(err, "Failed to get FalconNodeSensor ClusterRole") + return false, err + } + + // check if CDP cluster role was already set + for _, rule := range clusterRole.Rules { + if slices.Equal(rule.Resources, cdpRoles.Resources) && + slices.Equal(rule.Verbs, cdpRoles.Verbs) && + slices.Equal(rule.APIGroups, cdpRoles.APIGroups) { + return false, nil + } + } + + clusterRole.Rules = append(clusterRole.Rules, cdpRoles) + + err = r.Update(ctx, &clusterRole) + if err != nil { + logger.Error(err, "Failed to create new ClusterRole", "Namespace.Name", nodesensor.Spec.InstallNamespace, "ClusterRole.Name", common.NodeClusterRoleName) + return false, err + } + logger.Info("Updated FalconNodeSensor ClusterRole") + return true, nil + +} + // handleServiceAccount creates and updates the service account and grants necessary permissions to it func (r *FalconNodeSensorReconciler) handleSAAnnotations(ctx context.Context, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) error { sa := corev1.ServiceAccount{} diff --git a/internal/controller/falcon_node/falconnodesensor_controller_test.go b/internal/controller/falcon_node/falconnodesensor_controller_test.go index 5f5c062d..76f002b6 100644 --- a/internal/controller/falcon_node/falconnodesensor_controller_test.go +++ b/internal/controller/falcon_node/falconnodesensor_controller_test.go @@ -106,6 +106,12 @@ var _ = Describe("FalconNodeSensor controller", func() { }) Expect(err).To(Not(HaveOccurred())) + // ClusterRole reconcile + _, err = falconNodeReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespaceName, + }) + Expect(err).To(Not(HaveOccurred())) + // TODO: clusterRoleBinding reconciliation might be removed in the future _, err = falconNodeReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespaceName, diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 7926df89..bda56fe9 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -46,4 +46,5 @@ const ( AdmissionServiceAccountName = "falcon-operator-admission-controller" NodeClusterRoleBindingName = "falcon-operator-node-sensor-rolebinding" ImageServiceAccountName = "falcon-operator-image-analyzer" + NodeClusterRoleName = "falcon-operator-node-sensor-role" )