From c063bd317c6016a7d6c63df71fa732ef4adfe7ba Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 13:21:41 +0200 Subject: [PATCH 01/26] feat: ignore dlv binary --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 476ac08a0..f0bc782c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ testbin/* # Test binary, build with `go test -c` *.test +# Go dlv debugger binary +__debug_bin* + # Output of the go coverage tool, specifically when used with LiteIDE *.out From aafe1da7cfdb5a74ab9e2da53c79385494160587 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 13:44:40 +0200 Subject: [PATCH 02/26] feat: use templated inventory and universal configmap; add daemonset node scanning style --- api/v1alpha2/mondooauditconfig_types.go | 1 + controllers/nodes/deployment_handler.go | 34 ++++--------- controllers/nodes/deployment_handler_test.go | 50 +++++++++----------- controllers/nodes/resources.go | 37 ++++++++++----- controllers/nodes/resources_test.go | 41 +--------------- 5 files changed, 61 insertions(+), 102 deletions(-) diff --git a/api/v1alpha2/mondooauditconfig_types.go b/api/v1alpha2/mondooauditconfig_types.go index 8b6c25840..b0b80ce53 100644 --- a/api/v1alpha2/mondooauditconfig_types.go +++ b/api/v1alpha2/mondooauditconfig_types.go @@ -96,6 +96,7 @@ type NodeScanStyle string const ( NodeScanStyle_CronJob NodeScanStyle = "cronjob" NodeScanStyle_Deployment NodeScanStyle = "deployment" + NodeScanStyle_DaemonSet NodeScanStyle = "daemonset" ) type Nodes struct { diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index a8d7460c9..3ac0e6776 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -38,8 +38,8 @@ func (n *DeploymentHandler) Reconcile(ctx context.Context) (ctrl.Result, error) if err := n.syncCronJob(ctx); err != nil { return ctrl.Result{}, err } - } else if n.Mondoo.Spec.Nodes.Style == v1alpha2.NodeScanStyle_Deployment { - if err := n.syncDeployment(ctx); err != nil { + } else if n.Mondoo.Spec.Nodes.Style == v1alpha2.NodeScanStyle_Deployment || n.Mondoo.Spec.Nodes.Style == v1alpha2.NodeScanStyle_DaemonSet { + if err := n.syncDaemonSet(ctx); err != nil { return ctrl.Result{}, err } } @@ -83,7 +83,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { return err } - updated, err := n.syncConfigMap(ctx, node, clusterUid) + updated, err := n.syncConfigMap(ctx, clusterUid) if err != nil { return err } @@ -157,7 +157,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { return nil } -func (n *DeploymentHandler) syncDeployment(ctx context.Context) error { +func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { mondooClientImage, err := n.ContainerImageResolver.CnspecImage( n.Mondoo.Spec.Scanner.Image.Name, n.Mondoo.Spec.Scanner.Image.Tag, n.MondooOperatorConfig.Spec.SkipContainerResolution) if err != nil { @@ -194,7 +194,7 @@ func (n *DeploymentHandler) syncDeployment(ctx context.Context) error { return err } - updated, err := n.syncConfigMap(ctx, node, clusterUid) + updated, err := n.syncConfigMap(ctx, clusterUid) if err != nil { return err } @@ -269,16 +269,16 @@ func (n *DeploymentHandler) syncDeployment(ctx context.Context) error { // syncConfigMap syncs the inventory ConfigMap. Returns a boolean indicating whether the ConfigMap has been updated. It // can only be "true", if the ConfigMap existed before this reconcile cycle and the inventory was different from the // desired state. -func (n *DeploymentHandler) syncConfigMap(ctx context.Context, node corev1.Node, clusterUid string) (bool, error) { +func (n *DeploymentHandler) syncConfigMap(ctx context.Context, clusterUid string) (bool, error) { integrationMrn, err := k8s.TryGetIntegrationMrnForAuditConfig(ctx, n.KubeClient, *n.Mondoo) if err != nil { logger.Error(err, "failed to retrieve IntegrationMRN") return false, err } - cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}} + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}} op, err := k8s.CreateOrUpdate(ctx, n.KubeClient, cm, n.Mondoo, logger, func() error { - return UpdateConfigMap(cm, node, integrationMrn, clusterUid, *n.Mondoo) + return UpdateConfigMap(cm, integrationMrn, clusterUid, *n.Mondoo) }) if err != nil { return false, err @@ -315,14 +315,6 @@ func (n *DeploymentHandler) cleanupCronJobsForDeletedNodes(ctx context.Context, return err } logger.Info("Deleted CronJob", "namespace", c.Namespace, "name", c.Name) - - configMap := &corev1.ConfigMap{} - configMap.Name = ConfigMapName(n.Mondoo.Name, c.Spec.JobTemplate.Spec.Template.Spec.NodeName) - configMap.Namespace = n.Mondoo.Namespace - if err := k8s.DeleteIfExists(ctx, n.KubeClient, configMap); err != nil { - logger.Error(err, "Failed to delete ConfigMap", "namespace", configMap.Namespace, "name", configMap.Name) - return err - } } return nil } @@ -355,14 +347,6 @@ func (n *DeploymentHandler) cleanupDeploymentsForDeletedNodes(ctx context.Contex return err } logger.Info("Deleted Deployment", "namespace", d.Namespace, "name", d.Name) - - configMap := &corev1.ConfigMap{} - configMap.Name = ConfigMapName(n.Mondoo.Name, d.Spec.Template.Spec.NodeSelector["kubernetes.io/hostname"]) - configMap.Namespace = n.Mondoo.Namespace - if err := k8s.DeleteIfExists(ctx, n.KubeClient, configMap); err != nil { - logger.Error(err, "Failed to delete ConfigMap", "namespace", configMap.Namespace, "name", configMap.Name) - return err - } } return nil } @@ -427,7 +411,7 @@ func (n *DeploymentHandler) down(ctx context.Context) error { } configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}, + ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}, } if err := k8s.DeleteIfExists(ctx, n.KubeClient, configMap); err != nil { logger.Error(err, "Failed to clean up inventory ConfigMap", "namespace", configMap.Namespace, "name", configMap.Name) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index f4e99d750..6b18e32e4 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -68,14 +68,14 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateConfigMap() { nodes := &corev1.NodeList{} s.NoError(d.KubeClient.List(s.ctx, nodes)) - for _, node := range nodes.Items { + for range nodes.Items { cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, node.Name), Namespace: s.auditConfig.Namespace, + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, }} s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, node, "", testClusterUID, s.auditConfig)) + s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } } @@ -112,14 +112,14 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateConfigMapWithIntegrationMRN nodes := &corev1.NodeList{} s.NoError(d.KubeClient.List(s.ctx, nodes)) - for _, node := range nodes.Items { + for range nodes.Items { cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, node.Name), Namespace: s.auditConfig.Namespace, + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, }} s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, node, testIntegrationMRN, testClusterUID, s.auditConfig)) + s.Require().NoError(UpdateConfigMap(cfgMapExpected, testIntegrationMRN, testClusterUID, s.auditConfig)) s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } } @@ -133,29 +133,25 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateConfigMap() { nodes := &corev1.NodeList{} s.NoError(d.KubeClient.List(s.ctx, nodes)) - for _, node := range nodes.Items { - cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, node.Name), Namespace: s.auditConfig.Namespace, - }} - s.Require().NoError(UpdateConfigMap(cfgMap, node, "", testClusterUID, s.auditConfig)) - cfgMap.Data["inventory"] = "" - s.NoError(d.KubeClient.Create(s.ctx, cfgMap)) - } + cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, + }} + s.Require().NoError(UpdateConfigMap(cfgMap, "", testClusterUID, s.auditConfig)) + cfgMap.Data["inventory"] = "" + s.NoError(d.KubeClient.Create(s.ctx, cfgMap)) result, err := d.Reconcile(s.ctx) s.NoError(err) s.True(result.IsZero()) - for _, node := range nodes.Items { - cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, node.Name), Namespace: s.auditConfig.Namespace, - }} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) + cfgMap = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, + }} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) - cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, node, "", testClusterUID, s.auditConfig)) - s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) - } + cfgMapExpected := cfgMap.DeepCopy() + s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) + s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } func (s *DeploymentHandlerSuite) TestReconcile_CronJob_CleanConfigMapsForDeletedNodes() { @@ -186,12 +182,12 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_CleanConfigMapsForDeleted s.Equal(1, len(configMaps.Items)) cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace, + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, }} s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, nodes.Items[0], "", testClusterUID, s.auditConfig)) + s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } @@ -224,12 +220,12 @@ func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CleanConfigMapsForDele s.Equal(1, len(configMaps.Items)) cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace, + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, }} s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, nodes.Items[0], "", testClusterUID, s.auditConfig)) + s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index 4eb2da7b5..d6a98de87 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -31,7 +31,7 @@ const ( CronJobNameBase = "-node-" DeploymentNameBase = "-node-" GarbageCollectCronJobNameBase = "-node-gc" - InventoryConfigMapBase = "-node-inventory-" + InventoryConfigMapBase = "-node-inventory" ignoreQueryAnnotationPrefix = "policies.k8s.mondoo.com/" @@ -122,6 +122,14 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp Name: "MONDOO_AUTO_UPDATE", Value: "false", }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, }, m.Spec.Nodes.Env), TerminationMessagePath: "/dev/termination-log", TerminationMessagePolicy: corev1.TerminationMessageReadFile, @@ -143,7 +151,7 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp Sources: []corev1.VolumeProjection{ { ConfigMap: &corev1.ConfigMapProjection{ - LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name, node.Name)}, + LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name)}, Items: []corev1.KeyToPath{{ Key: "inventory", Path: "mondoo/inventory.yml", @@ -265,6 +273,14 @@ func UpdateDeployment( Name: "MONDOO_AUTO_UPDATE", Value: "false", }, + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, }, m.Spec.Nodes.Env), }, } @@ -283,7 +299,7 @@ func UpdateDeployment( Sources: []corev1.VolumeProjection{ { ConfigMap: &corev1.ConfigMapProjection{ - LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name, node.Name)}, + LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name)}, Items: []corev1.KeyToPath{{ Key: "inventory", Path: "mondoo/inventory.yml", @@ -398,8 +414,8 @@ func UpdateGarbageCollectCronJob(cj *batchv1.CronJob, image, clusterUid string, } } -func UpdateConfigMap(cm *corev1.ConfigMap, node corev1.Node, integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) error { - inv, err := Inventory(node, integrationMRN, clusterUID, m) +func UpdateConfigMap(cm *corev1.ConfigMap, integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) error { + inv, err := Inventory(integrationMRN, clusterUID, m) if err != nil { return err } @@ -427,12 +443,11 @@ func GarbageCollectCronJobName(prefix string) string { return fmt.Sprintf("%s%s", prefix, GarbageCollectCronJobNameBase) } -func ConfigMapName(prefix, nodeName string) string { - base := fmt.Sprintf("%s%s", prefix, InventoryConfigMapBase) - return fmt.Sprintf("%s%s", base, NodeNameOrHash(k8s.ResourceNameMaxLength-len(base), nodeName)) +func ConfigMapName(prefix string) string { + return fmt.Sprintf("%s%s", prefix, InventoryConfigMapBase) } -func Inventory(node corev1.Node, integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) (string, error) { +func Inventory(integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) (string, error) { inv := &inventory.Inventory{ Metadata: &inventory.ObjectMeta{ Name: "mondoo-node-inventory", @@ -444,12 +459,12 @@ func Inventory(node corev1.Node, integrationMRN, clusterUID string, m v1alpha2.M Assets: []*inventory.Asset{ { Id: "host", - Name: node.Name, + Name: `{{ getenv "NODE_NAME" }}`, Connections: []*inventory.Config{ { Type: "filesystem", Host: "/mnt/host", - PlatformId: fmt.Sprintf("//platformid.api.mondoo.app/runtime/k8s/uid/%s/node/%s", clusterUID, node.UID), + PlatformId: fmt.Sprintf("//platformid.api.mondoo.app/runtime/k8s/uid/%s/node/{{- getenv NODE_NAME -}}", clusterUID), }, }, Labels: map[string]string{ diff --git a/controllers/nodes/resources_test.go b/controllers/nodes/resources_test.go index 4bbcd9397..e9ec4e8bd 100644 --- a/controllers/nodes/resources_test.go +++ b/controllers/nodes/resources_test.go @@ -64,40 +64,6 @@ func TestGarbageCollectCronJobName(t *testing.T) { assert.Equal(t, fmt.Sprintf("%s%s", prefix, GarbageCollectCronJobNameBase), GarbageCollectCronJobName(prefix)) } -func TestConfigMapName(t *testing.T) { - prefix := "mondoo-client" - tests := []struct { - name string - data func() (suffix, expected string) - }{ - { - name: "should be prefix+base+suffix when shorter than 52 chars", - data: func() (suffix, expected string) { - base := fmt.Sprintf("%s%s", prefix, InventoryConfigMapBase) - suffix = utils.RandString(k8s.ResourceNameMaxLength - len(base)) - return suffix, fmt.Sprintf("%s%s", base, suffix) - }, - }, - { - name: "should be prefix+base+hash when longer than 52 chars", - data: func() (suffix, expected string) { - base := fmt.Sprintf("%s%s", prefix, InventoryConfigMapBase) - suffix = utils.RandString(53 - len(base)) - - hash := fmt.Sprintf("%x", sha256.Sum256([]byte(suffix))) - return suffix, fmt.Sprintf("%s%s", base, hash[:k8s.ResourceNameMaxLength-len(base)]) - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - suffix, expected := test.data() - assert.Equal(t, expected, ConfigMapName(prefix, suffix)) - }) - } -} - func TestResources(t *testing.T) { tests := []struct { name string @@ -183,18 +149,15 @@ func TestCronJob_Privileged(t *testing.T) { } func TestInventory(t *testing.T) { - randName := utils.RandString(10) auditConfig := v1alpha2.MondooAuditConfig{ObjectMeta: metav1.ObjectMeta{Name: "mondoo-client"}} - inventory, err := Inventory(corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: randName}}, "", testClusterUID, auditConfig) + inventory, err := Inventory("", testClusterUID, auditConfig) assert.NoError(t, err, "unexpected error generating inventory") - assert.Contains(t, inventory, randName) assert.NotContains(t, inventory, constants.MondooAssetsIntegrationLabel) const integrationMRN = "//test-MRN" - inventory, err = Inventory(corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: randName}}, integrationMRN, testClusterUID, auditConfig) + inventory, err = Inventory(integrationMRN, testClusterUID, auditConfig) assert.NoError(t, err, "unexpected error generating inventory") - assert.Contains(t, inventory, randName) assert.Contains(t, inventory, constants.MondooAssetsIntegrationLabel) assert.Contains(t, inventory, integrationMRN) } From 1096c00cc30b107179767e51c2141168a8a02916 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 14:00:02 +0200 Subject: [PATCH 03/26] feat: create daemonset instead of deployments, cleanup old deployments --- controllers/nodes/deployment_handler.go | 34 +++++++++++-------- controllers/nodes/resources.go | 44 +++++++++++++------------ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 3ac0e6776..c1bb4297e 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -206,22 +206,28 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { "name", DeploymentName(n.Mondoo.Name, node.Name)) } - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}} - op, err := k8s.CreateOrUpdate(ctx, n.KubeClient, dep, n.Mondoo, logger, func() error { - UpdateDeployment(dep, node, *n.Mondoo, n.IsOpenshift, mondooClientImage, *n.MondooOperatorConfig) - return nil - }) - if err != nil { - return err + if n.Mondoo.Spec.Nodes.Style == v1alpha2.NodeScanStyle_Deployment { + dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}} + if err := k8s.DeleteIfExists(ctx, n.KubeClient, dep); err != nil { + logger.Error(err, "Failed to clean up node scanning Deployment", "namespace", dep.Namespace, "name", dep.Name) + } } + } - if op == controllerutil.OperationResultCreated { - err = mondoo.UpdateMondooAuditConfig(ctx, n.KubeClient, n.Mondoo, logger) - if err != nil { - logger.Error(err, "Failed to update MondooAuditConfig", "namespace", n.Mondoo.Namespace, "name", n.Mondoo.Name) - return err - } - continue + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}} + op, err := k8s.CreateOrUpdate(ctx, n.KubeClient, ds, n.Mondoo, logger, func() error { + UpdateDaemonSet(ds, *n.Mondoo, n.IsOpenshift, mondooClientImage, *n.MondooOperatorConfig) + return nil + }) + if err != nil { + return err + } + + if op == controllerutil.OperationResultCreated { + err = mondoo.UpdateMondooAuditConfig(ctx, n.KubeClient, n.Mondoo, logger) + if err != nil { + logger.Error(err, "Failed to update MondooAuditConfig", "namespace", n.Mondoo.Namespace, "name", n.Mondoo.Name) + return err } } diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index d6a98de87..2a0da71d4 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -30,6 +30,7 @@ import ( const ( CronJobNameBase = "-node-" DeploymentNameBase = "-node-" + DaemonSetNameBase = "-node" GarbageCollectCronJobNameBase = "-node-gc" InventoryConfigMapBase = "-node-inventory" @@ -180,9 +181,8 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp } } -func UpdateDeployment( - dep *appsv1.Deployment, - node corev1.Node, +func UpdateDaemonSet( + ds *appsv1.DaemonSet, m v1alpha2.MondooAuditConfig, isOpenshift bool, image string, @@ -199,29 +199,24 @@ func UpdateDeployment( cmd = append(cmd, []string{"--api-proxy", *cfg.Spec.HttpProxy}...) } - dep.Labels = labels - if dep.Annotations == nil { - dep.Annotations = map[string]string{} + ds.Labels = labels + if ds.Annotations == nil { + ds.Annotations = map[string]string{} } - dep.Annotations[ignoreQueryAnnotationPrefix+"mondoo-kubernetes-security-deployment-runasnonroot"] = ignoreAnnotationValue - dep.Spec.Replicas = ptr.To(int32(1)) - dep.Spec.Selector = &metav1.LabelSelector{ + ds.Annotations[ignoreQueryAnnotationPrefix+"mondoo-kubernetes-security-deployment-runasnonroot"] = ignoreAnnotationValue + ds.Spec.Selector = &metav1.LabelSelector{ MatchLabels: labels, } - dep.Spec.Template.Labels = labels - if dep.Spec.Template.Annotations == nil { - dep.Spec.Template.Annotations = map[string]string{} + ds.Spec.Template.Labels = labels + if ds.Spec.Template.Annotations == nil { + ds.Spec.Template.Annotations = map[string]string{} } - dep.Spec.Template.Annotations[ignoreQueryAnnotationPrefix+"mondoo-kubernetes-security-pod-runasnonroot"] = ignoreAnnotationValue - dep.Spec.Template.Spec.PriorityClassName = m.Spec.Nodes.PriorityClassName - dep.Spec.Template.Spec.NodeSelector = map[string]string{ - "kubernetes.io/hostname": node.Name, - } - dep.Spec.Template.Spec.Tolerations = k8s.TaintsToTolerations(node.Spec.Taints) + ds.Spec.Template.Annotations[ignoreQueryAnnotationPrefix+"mondoo-kubernetes-security-pod-runasnonroot"] = ignoreAnnotationValue + ds.Spec.Template.Spec.PriorityClassName = m.Spec.Nodes.PriorityClassName // The node scanning does not use the Kubernetes API at all, therefore the service account token // should not be mounted at all. - dep.Spec.Template.Spec.AutomountServiceAccountToken = ptr.To(false) - dep.Spec.Template.Spec.Containers = []corev1.Container{ + ds.Spec.Template.Spec.AutomountServiceAccountToken = ptr.To(false) + ds.Spec.Template.Spec.Containers = []corev1.Container{ { Image: image, Name: "cnspec", @@ -284,7 +279,7 @@ func UpdateDeployment( }, m.Spec.Nodes.Env), }, } - dep.Spec.Template.Spec.Volumes = []corev1.Volume{ + ds.Spec.Template.Spec.Volumes = []corev1.Volume{ { Name: "root", VolumeSource: corev1.VolumeSource{ @@ -439,6 +434,13 @@ func DeploymentName(prefix, suffix string) string { return fmt.Sprintf("%s%s", base, NodeNameOrHash(k8s.ResourceNameMaxLength-len(base), suffix)) } +func DaemonSetName(prefix string) string { + // If the name becomes longer than 52 chars, then we hash the suffix and trim + // it such that the full name fits within 52 chars. This is needed because in + // manager Kubernetes services such as EKS or GKE the node names can be very long. + return fmt.Sprintf("%s%s", prefix, DaemonSetNameBase) +} + func GarbageCollectCronJobName(prefix string) string { return fmt.Sprintf("%s%s", prefix, GarbageCollectCronJobNameBase) } From 3faf494d4b99cf0beb2ff085a226acc7cd4b89af Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 16:12:35 +0200 Subject: [PATCH 04/26] feat: use `inventory_template` file instead of `inventory` to avoid cnspec treating template as config file --- controllers/nodes/resources.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index 2a0da71d4..fbde6403a 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -192,7 +192,7 @@ func UpdateDaemonSet( cmd := []string{ "cnspec", "serve", "--config", "/etc/opt/mondoo/mondoo.yml", - "--inventory-file", "/etc/opt/mondoo/inventory.yml", + "--inventory-template", "/etc/opt/mondoo/inventory_template.yml", "--timer", fmt.Sprintf("%d", m.Spec.Nodes.IntervalTimer), } if cfg.Spec.HttpProxy != nil { @@ -297,7 +297,7 @@ func UpdateDaemonSet( LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name)}, Items: []corev1.KeyToPath{{ Key: "inventory", - Path: "mondoo/inventory.yml", + Path: "mondoo/inventory_template.yml", }}, }, }, @@ -466,7 +466,7 @@ func Inventory(integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) { Type: "filesystem", Host: "/mnt/host", - PlatformId: fmt.Sprintf("//platformid.api.mondoo.app/runtime/k8s/uid/%s/node/{{- getenv NODE_NAME -}}", clusterUID), + PlatformId: fmt.Sprintf(`{{ printf "//platformid.api.mondoo.app/runtime/k8s/uid/%%s/node/%%s" "%s" (getenv "NODE_NAME")}}`, clusterUID), }, }, Labels: map[string]string{ From 4eb0facb7c947a218fdc90c8712663934ab1c783 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 16:13:26 +0200 Subject: [PATCH 05/26] scratching the surface of tests updates --- controllers/nodes/deployment_handler_test.go | 270 ++++++++----------- 1 file changed, 109 insertions(+), 161 deletions(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index 6b18e32e4..d0cf4175b 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -6,7 +6,6 @@ package nodes import ( "context" "encoding/json" - "fmt" "testing" "time" @@ -467,17 +466,15 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - for _, n := range nodes.Items { - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, n.Name), Namespace: s.auditConfig.Namespace}} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(ds), ds)) - depExpected := dep.DeepCopy() - UpdateDeployment(depExpected, n, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) - // Make sure the env vars for both are sorted - utils.SortEnvVars(depExpected.Spec.Template.Spec.Containers[0].Env) - utils.SortEnvVars(dep.Spec.Template.Spec.Containers[0].Env) - s.True(equality.Semantic.DeepEqual(depExpected, dep)) - } + dsExpected := ds.DeepCopy() + UpdateDaemonSet(dsExpected, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) + // Make sure the env vars for both are sorted + utils.SortEnvVars(dsExpected.Spec.Template.Spec.Containers[0].Env) + utils.SortEnvVars(ds.Spec.Template.Spec.Containers[0].Env) + s.True(equality.Semantic.DeepEqual(dsExpected, ds)) operatorImage, err := s.containerImageResolver.MondooOperatorImage(s.ctx, "", "", false) s.NoError(err) @@ -509,14 +506,12 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments_Switch() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - for _, n := range nodes.Items { - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, n.Name), Namespace: s.auditConfig.Namespace}} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(ds), ds)) - depExpected := dep.DeepCopy() - UpdateDeployment(depExpected, n, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) - s.True(equality.Semantic.DeepEqual(depExpected, dep)) - } + dsExpected := ds.DeepCopy() + UpdateDaemonSet(dsExpected, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) + s.True(equality.Semantic.DeepEqual(dsExpected, ds)) mondooAuditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_CronJob result, err = d.Reconcile(s.ctx) @@ -558,68 +553,21 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateDeployments() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - // Make sure a deployment exists for one of the nodes - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, nodes.Items[1].Name), Namespace: s.auditConfig.Namespace}} - UpdateDeployment(dep, nodes.Items[1], s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) - dep.Spec.Template.Spec.Containers[0].Command = []string{"test-command"} - s.NoError(d.KubeClient.Create(s.ctx, dep)) - - result, err := d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - for _, n := range nodes.Items { - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, n.Name), Namespace: s.auditConfig.Namespace}} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) - - depExpected := dep.DeepCopy() - UpdateDeployment(depExpected, n, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) - s.True(equality.Semantic.DeepEqual(depExpected, dep)) - } -} - -func (s *DeploymentHandlerSuite) TestReconcile_CleanDeploymentsForDeletedNodes() { - s.seedNodes() - d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment - mondooAuditConfig := &s.auditConfig - s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) + // Make sure a daemonset exists for one of the nodes + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} + UpdateDaemonSet(ds, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) + ds.Spec.Template.Spec.Containers[0].Command = []string{"test-command"} + s.NoError(d.KubeClient.Create(s.ctx, ds)) - // Reconcile to create the initial cron jobs result, err := d.Reconcile(s.ctx) s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - - // Delete one node - s.NoError(d.KubeClient.Delete(s.ctx, &nodes.Items[1])) - - // Reconcile again to delete the cron job for the deleted node - result, err = d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - image, err := s.containerImageResolver.CnspecImage( - s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) - s.NoError(err) - - listOpts := &client.ListOptions{ - Namespace: s.auditConfig.Namespace, - LabelSelector: labels.SelectorFromSet(NodeScanningLabels(s.auditConfig)), - } - deployments := &appsv1.DeploymentList{} - s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) - - s.Equal(1, len(deployments.Items)) - - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace}} - UpdateDeployment(dep, nodes.Items[0], s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) + dep := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) depExpected := dep.DeepCopy() - UpdateDeployment(depExpected, nodes.Items[0], s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) + UpdateDaemonSet(depExpected, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) s.True(equality.Semantic.DeepEqual(depExpected, dep)) } @@ -697,78 +645,78 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_NodeScanningStatus() { s.Equal(corev1.ConditionFalse, condition.Status) } -func (s *DeploymentHandlerSuite) TestReconcile_Deployment_NodeScanningStatus() { - s.seedNodes() - d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment - mondooAuditConfig := &s.auditConfig - s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) - - // Reconcile to create all resources - result, err := d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - // Verify the node scanning status is set to available - s.Equal(1, len(d.Mondoo.Status.Conditions)) - condition := d.Mondoo.Status.Conditions[0] - s.Equal("Node Scanning is unavailable", condition.Message) - s.Equal("NodeScanningUnavailable", condition.Reason) - s.Equal(corev1.ConditionTrue, condition.Status) - - listOpts := &client.ListOptions{ - Namespace: s.auditConfig.Namespace, - LabelSelector: labels.SelectorFromSet(NodeScanningLabels(s.auditConfig)), - } - deployments := &appsv1.DeploymentList{} - s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) - - // Make sure all deployments are ready - deployments.Items[0].Status.ReadyReplicas = 1 - s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) - deployments.Items[1].Status.ReadyReplicas = 1 - s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[1])) - - // Reconcile to update the audit config status - result, err = d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - // Verify the node scanning status is set to unavailable - condition = d.Mondoo.Status.Conditions[0] - s.Equal("Node Scanning is available", condition.Message) - s.Equal("NodeScanningAvailable", condition.Reason) - s.Equal(corev1.ConditionFalse, condition.Status) - - // // Make a deployment fail again - s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) - deployments.Items[0].Status.ReadyReplicas = 0 - s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) - - // Reconcile to update the audit config status - result, err = d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - // Verify the node scanning status is set to available - condition = d.Mondoo.Status.Conditions[0] - s.Equal("Node Scanning is unavailable", condition.Message) - s.Equal("NodeScanningUnavailable", condition.Reason) - s.Equal(corev1.ConditionTrue, condition.Status) - - d.Mondoo.Spec.Nodes.Enable = false - - // Reconcile to update the audit config status - result, err = d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) - - // Verify the node scanning status is set to disabled - condition = d.Mondoo.Status.Conditions[0] - s.Equal("Node Scanning is disabled", condition.Message) - s.Equal("NodeScanningDisabled", condition.Reason) - s.Equal(corev1.ConditionFalse, condition.Status) -} +// func (s *DeploymentHandlerSuite) TestReconcile_Deployment_NodeScanningStatus() { +// s.seedNodes() +// d := s.createDeploymentHandler() +// s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment +// mondooAuditConfig := &s.auditConfig +// s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) + +// // Reconcile to create all resources +// result, err := d.Reconcile(s.ctx) +// s.NoError(err) +// s.True(result.IsZero()) + +// // Verify the node scanning status is set to available +// s.Equal(1, len(d.Mondoo.Status.Conditions)) +// condition := d.Mondoo.Status.Conditions[0] +// s.Equal("Node Scanning is unavailable", condition.Message) +// s.Equal("NodeScanningUnavailable", condition.Reason) +// s.Equal(corev1.ConditionTrue, condition.Status) + +// listOpts := &client.ListOptions{ +// Namespace: s.auditConfig.Namespace, +// LabelSelector: labels.SelectorFromSet(NodeScanningLabels(s.auditConfig)), +// } +// deployments := &appsv1.DaemonSetList{} +// s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) + +// // Make sure all deployments are ready +// deployments.Items[0].Status.NumberReady = 1 +// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) +// deployments.Items[1].Status.NumberReady = 1 +// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[1])) + +// // Reconcile to update the audit config status +// result, err = d.Reconcile(s.ctx) +// s.NoError(err) +// s.True(result.IsZero()) + +// // Verify the node scanning status is set to unavailable +// condition = d.Mondoo.Status.Conditions[0] +// s.Equal("Node Scanning is available", condition.Message) +// s.Equal("NodeScanningAvailable", condition.Reason) +// s.Equal(corev1.ConditionFalse, condition.Status) + +// // // Make a deployment fail again +// s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) +// deployments.Items[0].Status.NumberReady = 0 +// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) + +// // Reconcile to update the audit config status +// result, err = d.Reconcile(s.ctx) +// s.NoError(err) +// s.True(result.IsZero()) + +// // Verify the node scanning status is set to available +// condition = d.Mondoo.Status.Conditions[0] +// s.Equal("Node Scanning is unavailable", condition.Message) +// s.Equal("NodeScanningUnavailable", condition.Reason) +// s.Equal(corev1.ConditionTrue, condition.Status) + +// d.Mondoo.Spec.Nodes.Enable = false + +// // Reconcile to update the audit config status +// result, err = d.Reconcile(s.ctx) +// s.NoError(err) +// s.True(result.IsZero()) + +// // Verify the node scanning status is set to disabled +// condition = d.Mondoo.Status.Conditions[0] +// s.Equal("Node Scanning is disabled", condition.Message) +// s.Equal("NodeScanningDisabled", condition.Reason) +// s.Equal(corev1.ConditionFalse, condition.Status) +// } func (s *DeploymentHandlerSuite) TestReconcile_NodeScanningOOMStatus() { s.seedNodes() @@ -920,28 +868,28 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_CustomSchedule() { s.Equal(cj.Spec.Schedule, customSchedule) } -func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CustomInterval() { - s.seedNodes() - d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment - mondooAuditConfig := &s.auditConfig - s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) +// func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CustomInterval() { +// s.seedNodes() +// d := s.createDeploymentHandler() +// s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment +// mondooAuditConfig := &s.auditConfig +// s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) - s.auditConfig.Spec.Nodes.IntervalTimer = 1034 +// s.auditConfig.Spec.Nodes.IntervalTimer = 1034 - result, err := d.Reconcile(s.ctx) - s.NoError(err) - s.True(result.IsZero()) +// result, err := d.Reconcile(s.ctx) +// s.NoError(err) +// s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) +// nodes := &corev1.NodeList{} +// s.NoError(d.KubeClient.List(s.ctx, nodes)) - dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace}} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) +// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace}} +// s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) - s.Contains(dep.Spec.Template.Spec.Containers[0].Command, "--timer") - s.Contains(dep.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("%d", s.auditConfig.Spec.Nodes.IntervalTimer)) -} +// s.Contains(dep.Spec.Template.Spec.Containers[0].Command, "--timer") +// s.Contains(dep.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("%d", s.auditConfig.Spec.Nodes.IntervalTimer)) +// } func (s *DeploymentHandlerSuite) createDeploymentHandler() DeploymentHandler { return DeploymentHandler{ From 1638d31539f32d3b49adc63d8e0aae924c5cb02d Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 16:15:05 +0200 Subject: [PATCH 06/26] feat: remove deployments cleanup --- controllers/nodes/deployment_handler.go | 37 ------------------------- 1 file changed, 37 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index c1bb4297e..19fe20349 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -231,11 +231,6 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { } } - // Delete dangling Deployments for nodes that have been deleted from the cluster. - if err := n.cleanupDeploymentsForDeletedNodes(ctx, *nodes); err != nil { - return err - } - // List the Deployments again after they have been synced. deployments, err := n.getDeploymentsForAuditConfig(ctx) if err != nil { @@ -325,38 +320,6 @@ func (n *DeploymentHandler) cleanupCronJobsForDeletedNodes(ctx context.Context, return nil } -// cleanupDeploymentsForDeletedNodes deletes dangling Deployments for nodes that have been deleted from the cluster. -func (n *DeploymentHandler) cleanupDeploymentsForDeletedNodes(ctx context.Context, currentNodes corev1.NodeList) error { - deployments, err := n.getDeploymentsForAuditConfig(ctx) - if err != nil { - return err - } - - for _, d := range deployments { - // Check if the node for that Deployment is still present in the cluster. - found := false - for _, node := range currentNodes.Items { - if DeploymentName(n.Mondoo.Name, node.Name) == d.Name { - found = true - break - } - } - - // If the node is still there, there is nothing to update. - if found { - continue - } - - // If the node for the Deployment has been deleted from the cluster, the Deployment needs to be deleted. - if err := k8s.DeleteIfExists(ctx, n.KubeClient, &d); err != nil { - logger.Error(err, "Failed to deleted Deployment", "namespace", d.Namespace, "name", d.Name) - return err - } - logger.Info("Deleted Deployment", "namespace", d.Namespace, "name", d.Name) - } - return nil -} - func (n *DeploymentHandler) syncGCCronjob(ctx context.Context, mondooOperatorImage, clusterUid string) error { cj := &batchv1.CronJob{ObjectMeta: metav1.ObjectMeta{Name: GarbageCollectCronJobName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}} _, err := k8s.CreateOrUpdate(ctx, n.KubeClient, cj, n.Mondoo, logger, func() error { From e39c66e214d05b7fdb4d5da9936c9fcc10c25c44 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 19 Jun 2024 16:30:46 +0200 Subject: [PATCH 07/26] follow-up: remove deployments audit --- controllers/nodes/deployment_handler.go | 30 ------------------------- 1 file changed, 30 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 19fe20349..744f78696 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -231,36 +231,6 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { } } - // List the Deployments again after they have been synced. - deployments, err := n.getDeploymentsForAuditConfig(ctx) - if err != nil { - return err - } - - // Get Pods for these Deployments - pods := &corev1.PodList{} - if len(deployments) > 0 { - opts := &client.ListOptions{ - Namespace: n.Mondoo.Namespace, - LabelSelector: labels.SelectorFromSet(NodeScanningLabels(*n.Mondoo)), - } - err = n.KubeClient.List(ctx, pods, opts) - if err != nil { - logger.Error(err, "Failed to list Pods for Node Scanning") - return err - } - } - - deploymentsDegraded := false - for _, d := range deployments { - if d.Status.ReadyReplicas < *d.Spec.Replicas { - deploymentsDegraded = true - break - } - } - - updateNodeConditions(n.Mondoo, deploymentsDegraded, pods) - if err := n.syncGCCronjob(ctx, mondooOperatorImage, clusterUid); err != nil { return err } From 3b167d06b3fa7f1baaff7b5fe4004137a7539843 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 10:17:35 +0200 Subject: [PATCH 08/26] follow-up: remove deployments audit --- controllers/nodes/deployment_handler.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 744f78696..d25e1691b 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -312,19 +312,6 @@ func (n *DeploymentHandler) getCronJobsForAuditConfig(ctx context.Context) ([]ba return cronJobs.Items, nil } -func (n *DeploymentHandler) getDeploymentsForAuditConfig(ctx context.Context) ([]appsv1.Deployment, error) { - deps := &appsv1.DeploymentList{} - depLabels := NodeScanningLabels(*n.Mondoo) - - // Lists only the Deployments in the namespace of the MondooAuditConfig and only the ones that exactly match our labels. - listOpts := &client.ListOptions{Namespace: n.Mondoo.Namespace, LabelSelector: labels.SelectorFromSet(depLabels)} - if err := n.KubeClient.List(ctx, deps, listOpts); err != nil { - logger.Error(err, "Failed to list Deployments in namespace", "namespace", n.Mondoo.Namespace) - return nil, err - } - return deps.Items, nil -} - func (n *DeploymentHandler) down(ctx context.Context) error { nodes := &corev1.NodeList{} if err := n.KubeClient.List(ctx, nodes); err != nil { From 1c6e25f296186372a12d8bea8574fe35b0af1695 Mon Sep 17 00:00:00 2001 From: Ivan Milchev Date: Thu, 20 Jun 2024 12:16:50 +0300 Subject: [PATCH 09/26] fix tests Signed-off-by: Ivan Milchev --- tests/framework/utils/asset.go | 2 +- tests/integration/audit_config_base_suite.go | 6 ++++-- tests/integration/audit_config_namespace_test.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/framework/utils/asset.go b/tests/framework/utils/asset.go index 2993e129f..add010d51 100644 --- a/tests/framework/utils/asset.go +++ b/tests/framework/utils/asset.go @@ -17,7 +17,7 @@ import ( func ExcludeClusterAsset(as []assets.AssetWithScore) []assets.AssetWithScore { var newAssets []assets.AssetWithScore for _, asset := range as { - if asset.AssetType != "k8s.cluster" { + if asset.AssetType != "k8s.cluster" && asset.AssetType != "k8s.service" { newAssets = append(newAssets, asset) } } diff --git a/tests/integration/audit_config_base_suite.go b/tests/integration/audit_config_base_suite.go index b851de403..70e8dd9bf 100644 --- a/tests/integration/audit_config_base_suite.go +++ b/tests/integration/audit_config_base_suite.go @@ -221,7 +221,9 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigKubernetesResources(auditCon // TODO: the cluster name is non-deterministic currently so we cannot test for it assetsExceptCluster := utils.ExcludeClusterAsset(assets) - s.Equalf(len(assets)-1, len(assetsExceptCluster), "Cluster asset was sent upstream.") + + // TODO: this number should exclude services and the cluster asset + s.Equalf(len(assets)-4, len(assetsExceptCluster), "Cluster asset was sent upstream.") assetNames := utils.AssetNames(assetsExceptCluster) s.ElementsMatchf(workloadNames, assetNames, "Workloads were not sent upstream.") @@ -1253,7 +1255,7 @@ func (s *AuditConfigBaseSuite) AssetsNotUnscored(assets []assets.AssetWithScore) for _, asset := range assets { // We don't score scratch containers at the moment so they are always unscored. // We don't have policies for a cluster asset enabled at the moment so they are always unscored. - if asset.Platform.Name != "scratch" && asset.Platform.Name != "k8s-cluster" && asset.Platform.Name != "k8s-namespace" { + if asset.Platform.Name != "scratch" && asset.Platform.Name != "k8s-cluster" && asset.Platform.Name != "k8s-namespace" && asset.Platform.Name != "k8s-service" { if asset.Grade == "U" || asset.Grade == "" { zap.S().Infof("Asset %s has no score", asset.Name) } diff --git a/tests/integration/audit_config_namespace_test.go b/tests/integration/audit_config_namespace_test.go index 9bb0eff05..6c3f7af8d 100644 --- a/tests/integration/audit_config_namespace_test.go +++ b/tests/integration/audit_config_namespace_test.go @@ -80,7 +80,7 @@ func (s *AuditConfigCustomNamespaceSuite) TearDownSuite() { s.AuditConfigBaseSuite.TearDownSuite() } -func (s *AuditConfigCustomNamespaceSuite) TestReconcile_KubernetesResources() { +func (s *AuditConfigCustomNamespaceSuite) TestReconcile_KubernetesResources2() { auditConfig := utils.DefaultAuditConfigMinimal(s.ns.Name, true, false, false, false) auditConfig.Spec.Scanner.ServiceAccountName = s.sa.Name s.testMondooAuditConfigKubernetesResources(auditConfig) From 1340f0e39bb37a1764e1bef274996f4ee2f1fe50 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 11:30:47 +0200 Subject: [PATCH 10/26] fix: exclude services from assets score check in the integration test --- tests/integration/audit_config_base_suite.go | 10 +++++++++- tests/integration/audit_config_namespace_test.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/integration/audit_config_base_suite.go b/tests/integration/audit_config_base_suite.go index 70e8dd9bf..f3e108414 100644 --- a/tests/integration/audit_config_base_suite.go +++ b/tests/integration/audit_config_base_suite.go @@ -223,7 +223,15 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigKubernetesResources(auditCon assetsExceptCluster := utils.ExcludeClusterAsset(assets) // TODO: this number should exclude services and the cluster asset - s.Equalf(len(assets)-4, len(assetsExceptCluster), "Cluster asset was sent upstream.") + srvs := &corev1.ServiceList{} + err = s.testCluster.K8sHelper.ExecuteWithRetries(func() (bool, error) { + if err := s.testCluster.K8sHelper.Clientset.List(s.ctx, srvs); err != nil { + return false, nil + } + return true, nil + }) + s.NoError(err, "Failed to list Kubernetes Services") + s.Equalf(len(assets)-1-len(srvs.Items), len(assetsExceptCluster), "Cluster asset was sent upstream.") assetNames := utils.AssetNames(assetsExceptCluster) s.ElementsMatchf(workloadNames, assetNames, "Workloads were not sent upstream.") diff --git a/tests/integration/audit_config_namespace_test.go b/tests/integration/audit_config_namespace_test.go index 6c3f7af8d..9bb0eff05 100644 --- a/tests/integration/audit_config_namespace_test.go +++ b/tests/integration/audit_config_namespace_test.go @@ -80,7 +80,7 @@ func (s *AuditConfigCustomNamespaceSuite) TearDownSuite() { s.AuditConfigBaseSuite.TearDownSuite() } -func (s *AuditConfigCustomNamespaceSuite) TestReconcile_KubernetesResources2() { +func (s *AuditConfigCustomNamespaceSuite) TestReconcile_KubernetesResources() { auditConfig := utils.DefaultAuditConfigMinimal(s.ns.Name, true, false, false, false) auditConfig.Spec.Scanner.ServiceAccountName = s.sa.Name s.testMondooAuditConfigKubernetesResources(auditConfig) From 710611792b51c1079b4c332d24449457fa30605c Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:00:27 +0200 Subject: [PATCH 11/26] fix: allow deamonsets crud in rbac --- config/rbac/role.yaml | 13 +++++++------ controllers/mondooauditconfig_controller.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 69fbd218f..b0f1f5cb5 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,23 +21,24 @@ rules: resources: - daemonsets - deployments - - replicasets - - statefulsets verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - apps resources: + - daemonsets - deployments + - replicasets + - statefulsets verbs: - - create - - delete - get - list - - patch - - update - watch - apiGroups: - batch diff --git a/controllers/mondooauditconfig_controller.go b/controllers/mondooauditconfig_controller.go index b3c672a66..6d16e96ec 100644 --- a/controllers/mondooauditconfig_controller.go +++ b/controllers/mondooauditconfig_controller.go @@ -61,7 +61,7 @@ var MondooClientBuilder = mondooclient.NewClient //+kubebuilder:rbac:groups=k8s.mondoo.com,resources=mondooauditconfigs/status,verbs=get;update;patch //+kubebuilder:rbac:groups=k8s.mondoo.com,resources=mondooauditconfigs/finalizers,verbs=update //+kubebuilder:rbac:groups=k8s.mondoo.com,resources=mondoooperatorconfigs,verbs=get;watch;list -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments;daemonsets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps,resources=deployments;replicasets;daemonsets;statefulsets,verbs=get;list;watch //+kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=batch,resources=jobs,verbs=deletecollection From 03e7d7f4ad599df42f27008bc3146042f8584e66 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:00:32 +0200 Subject: [PATCH 12/26] cleanup --- controllers/nodes/deployment_handler_test.go | 73 -------------------- 1 file changed, 73 deletions(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index d0cf4175b..b5f32b667 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -645,79 +645,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_NodeScanningStatus() { s.Equal(corev1.ConditionFalse, condition.Status) } -// func (s *DeploymentHandlerSuite) TestReconcile_Deployment_NodeScanningStatus() { -// s.seedNodes() -// d := s.createDeploymentHandler() -// s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment -// mondooAuditConfig := &s.auditConfig -// s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) - -// // Reconcile to create all resources -// result, err := d.Reconcile(s.ctx) -// s.NoError(err) -// s.True(result.IsZero()) - -// // Verify the node scanning status is set to available -// s.Equal(1, len(d.Mondoo.Status.Conditions)) -// condition := d.Mondoo.Status.Conditions[0] -// s.Equal("Node Scanning is unavailable", condition.Message) -// s.Equal("NodeScanningUnavailable", condition.Reason) -// s.Equal(corev1.ConditionTrue, condition.Status) - -// listOpts := &client.ListOptions{ -// Namespace: s.auditConfig.Namespace, -// LabelSelector: labels.SelectorFromSet(NodeScanningLabels(s.auditConfig)), -// } -// deployments := &appsv1.DaemonSetList{} -// s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) - -// // Make sure all deployments are ready -// deployments.Items[0].Status.NumberReady = 1 -// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) -// deployments.Items[1].Status.NumberReady = 1 -// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[1])) - -// // Reconcile to update the audit config status -// result, err = d.Reconcile(s.ctx) -// s.NoError(err) -// s.True(result.IsZero()) - -// // Verify the node scanning status is set to unavailable -// condition = d.Mondoo.Status.Conditions[0] -// s.Equal("Node Scanning is available", condition.Message) -// s.Equal("NodeScanningAvailable", condition.Reason) -// s.Equal(corev1.ConditionFalse, condition.Status) - -// // // Make a deployment fail again -// s.NoError(d.KubeClient.List(s.ctx, deployments, listOpts)) -// deployments.Items[0].Status.NumberReady = 0 -// s.NoError(d.KubeClient.Status().Update(s.ctx, &deployments.Items[0])) - -// // Reconcile to update the audit config status -// result, err = d.Reconcile(s.ctx) -// s.NoError(err) -// s.True(result.IsZero()) - -// // Verify the node scanning status is set to available -// condition = d.Mondoo.Status.Conditions[0] -// s.Equal("Node Scanning is unavailable", condition.Message) -// s.Equal("NodeScanningUnavailable", condition.Reason) -// s.Equal(corev1.ConditionTrue, condition.Status) - -// d.Mondoo.Spec.Nodes.Enable = false - -// // Reconcile to update the audit config status -// result, err = d.Reconcile(s.ctx) -// s.NoError(err) -// s.True(result.IsZero()) - -// // Verify the node scanning status is set to disabled -// condition = d.Mondoo.Status.Conditions[0] -// s.Equal("Node Scanning is disabled", condition.Message) -// s.Equal("NodeScanningDisabled", condition.Reason) -// s.Equal(corev1.ConditionFalse, condition.Status) -// } - func (s *DeploymentHandlerSuite) TestReconcile_NodeScanningOOMStatus() { s.seedNodes() d := s.createDeploymentHandler() From f14d8cac6f14afbacd1e276b999ed6c65907753a Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:18:25 +0200 Subject: [PATCH 13/26] fix: integration tests (except oom) --- tests/framework/utils/k8s_helper.go | 16 +++++++ tests/integration/audit_config_base_suite.go | 43 +++++-------------- .../audit_config_namespace_test.go | 4 +- tests/integration/audit_config_oom_test.go | 28 ++++-------- tests/integration/audit_config_test.go | 4 +- 5 files changed, 40 insertions(+), 55 deletions(-) diff --git a/tests/framework/utils/k8s_helper.go b/tests/framework/utils/k8s_helper.go index 82edf9691..ef9663e8a 100644 --- a/tests/framework/utils/k8s_helper.go +++ b/tests/framework/utils/k8s_helper.go @@ -391,6 +391,22 @@ func (k8sh *K8sHelper) UpdateDeploymentWithRetries(ctx context.Context, listOpts }) } +func (k8sh *K8sHelper) UpdateDaemonSetWithRetries(ctx context.Context, key types.NamespacedName, update func(*appsv1.DaemonSet)) error { + ds := &appsv1.DaemonSet{} + return k8sh.ExecuteWithRetries(func() (bool, error) { + if err := k8sh.Clientset.Get(ctx, key, ds); err != nil { + return false, err + } + + // update the daemonset + update(ds) + if err := k8sh.Clientset.Update(ctx, ds); err != nil { + return false, nil // retry + } + return true, nil + }) +} + func (k8sh *K8sHelper) ExecuteWithRetries(f func() (bool, error)) error { for i := 0; i < RetryLoop; i++ { success, err := f() diff --git a/tests/integration/audit_config_base_suite.go b/tests/integration/audit_config_base_suite.go index f3e108414..1246e2c8d 100644 --- a/tests/integration/audit_config_base_suite.go +++ b/tests/integration/audit_config_base_suite.go @@ -446,7 +446,7 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigNodesCronjobs(auditConfig mo s.Equal("ACTIVE", status) } -func (s *AuditConfigBaseSuite) testMondooAuditConfigNodesDeployments(auditConfig mondoov2.MondooAuditConfig) { +func (s *AuditConfigBaseSuite) testMondooAuditConfigNodesDaemonSets(auditConfig mondoov2.MondooAuditConfig) { s.auditConfig = auditConfig // Disable container image resolution to be able to run the k8s resources scan CronJob with a local image. @@ -460,39 +460,20 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigNodesDeployments(auditConfig s.Require().True(s.testCluster.K8sHelper.WaitUntilMondooClientSecretExists(s.ctx, s.auditConfig.Namespace), "Mondoo SA not created") - zap.S().Info("Verify the nodes scanning deployments are created.") - - deployments := &appsv1.DeploymentList{} - lbls := nodes.NodeScanningLabels(auditConfig) - - // List only the Deployments in the namespace of the MondooAuditConfig and only the ones that exactly match our labels. - listOpts := &client.ListOptions{Namespace: auditConfig.Namespace, LabelSelector: labels.SelectorFromSet(lbls)} + zap.S().Info("Verify the nodes scanning daemonset is created.") nodeList := &corev1.NodeList{} s.NoError(s.testCluster.K8sHelper.Clientset.List(s.ctx, nodeList)) - // Verify the amount of Deployments created is equal to the amount of nodes + // Verify DaemonSet is created + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: nodes.DaemonSetName(auditConfig.Name), Namespace: auditConfig.Namespace}} err := s.testCluster.K8sHelper.ExecuteWithRetries(func() (bool, error) { - s.NoError(s.testCluster.K8sHelper.Clientset.List(s.ctx, deployments, listOpts)) - if len(nodeList.Items) == len(deployments.Items) { - return true, nil + if err := s.testCluster.K8sHelper.Clientset.Get(s.ctx, client.ObjectKeyFromObject(ds), ds); err != nil { + return false, nil } - return false, nil + return true, nil }) - s.NoErrorf( - err, - "The amount of node scanning Deployments is not equal to the amount of cluster nodes. expected: %d; actual: %d", - len(nodeList.Items), len(deployments.Items)) - - for _, d := range deployments.Items { - found := false - for _, n := range nodeList.Items { - if n.Name == d.Spec.Template.Spec.NodeSelector["kubernetes.io/hostname"] { - found = true - } - } - s.Truef(found, "Deployment %s/%s does not have a corresponding cluster node.", d.Namespace, d.Name) - } + s.NoError(err, "DaemonSet was not created.") // Verify the garbage collect cron job gcCronJobs := &batchv1.CronJobList{} @@ -560,11 +541,9 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigNodesDeployments(auditConfig s.NoError(err, "Failed to get status") s.Equal("ACTIVE", status) - // Verify that the node scanning deployments aren't constantly updating - s.NoError(s.testCluster.K8sHelper.Clientset.List(s.ctx, deployments, listOpts)) - for _, d := range deployments.Items { - s.Less(d.Generation, int64(10)) - } + // Verify that the node scanning daemonset isn't constantly updating + s.NoError(s.testCluster.K8sHelper.Clientset.Get(s.ctx, client.ObjectKeyFromObject(ds), ds)) + s.Less(ds.Generation, int64(10)) } func (s *AuditConfigBaseSuite) testMondooAuditConfigAdmission(auditConfig mondoov2.MondooAuditConfig) { diff --git a/tests/integration/audit_config_namespace_test.go b/tests/integration/audit_config_namespace_test.go index 9bb0eff05..b2063ce33 100644 --- a/tests/integration/audit_config_namespace_test.go +++ b/tests/integration/audit_config_namespace_test.go @@ -102,12 +102,12 @@ func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Nodes_CronJobs() { s.testMondooAuditConfigNodesCronjobs(auditConfig) } -func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Nodes_Deployments() { +func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Nodes_DaemonSet() { auditConfig := utils.DefaultAuditConfigMinimal(s.ns.Name, false, false, true, false) auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment auditConfig.Spec.Nodes.IntervalTimer = 1 auditConfig.Spec.Scanner.ServiceAccountName = s.sa.Name - s.testMondooAuditConfigNodesDeployments(auditConfig) + s.testMondooAuditConfigNodesDaemonSets(auditConfig) } func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Admission() { diff --git a/tests/integration/audit_config_oom_test.go b/tests/integration/audit_config_oom_test.go index cf6e8247f..d9f5689eb 100644 --- a/tests/integration/audit_config_oom_test.go +++ b/tests/integration/audit_config_oom_test.go @@ -18,6 +18,7 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -277,9 +278,9 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_CronJob() { s.Equal("ACTIVE", status) } -func (s *AuditConfigOOMSuite) TestOOMNodeScan_Deployment() { +func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { auditConfig := utils.DefaultAuditConfigMinimal(s.testCluster.Settings.Namespace, false, false, true, false) - auditConfig.Spec.Nodes.Style = mondoov2.NodeScanStyle_Deployment + auditConfig.Spec.Nodes.Style = mondoov2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) s.auditConfig = auditConfig auditConfig.Spec.Nodes.Resources.Limits = corev1.ResourceList{ @@ -297,27 +298,16 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_Deployment() { s.Require().True(s.testCluster.K8sHelper.WaitUntilMondooClientSecretExists(s.ctx, s.auditConfig.Namespace), "Mondoo SA not created") - deployments := &appsv1.DeploymentList{} - lbls := nodes.NodeScanningLabels(auditConfig) - - // List only the Deployments in the namespace of the MondooAuditConfig and only the ones that exactly match our labels. - listOpts := &client.ListOptions{Namespace: auditConfig.Namespace, LabelSelector: labels.SelectorFromSet(lbls)} - - nodeList := &corev1.NodeList{} - s.NoError(s.testCluster.K8sHelper.Clientset.List(s.ctx, nodeList)) + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: nodes.DaemonSetName(auditConfig.Name), Namespace: auditConfig.Namespace}} - // Verify the amount of Deployments created is equal to the amount of nodes + // Verify that DaemonSet was created err := s.testCluster.K8sHelper.ExecuteWithRetries(func() (bool, error) { - s.NoError(s.testCluster.K8sHelper.Clientset.List(s.ctx, deployments, listOpts)) - if len(nodeList.Items) == len(deployments.Items) { - return true, nil + if err := s.testCluster.K8sHelper.Clientset.Get(s.ctx, client.ObjectKeyFromObject(ds), ds); err != nil { + return false, nil } - return false, nil + return true, nil }) - s.NoErrorf( - err, - "The amount of node scanning Deployments is not equal to the amount of cluster nodes. expected: %d; actual: %d", - len(nodeList.Items), len(deployments.Items)) + s.NoError(err, "DaemonSet was not created.") // This will take some time, because: // reconcile needs to happen diff --git a/tests/integration/audit_config_test.go b/tests/integration/audit_config_test.go index 1e86b5d52..d4d70cc60 100644 --- a/tests/integration/audit_config_test.go +++ b/tests/integration/audit_config_test.go @@ -46,11 +46,11 @@ func (s *AuditConfigSuite) TestReconcile_Nodes_CronJobs() { s.testMondooAuditConfigNodesCronjobs(auditConfig) } -func (s *AuditConfigSuite) TestReconcile_Nodes_Deployments() { +func (s *AuditConfigSuite) TestReconcile_Nodes_DaemonSet() { auditConfig := utils.DefaultAuditConfigMinimal(s.testCluster.Settings.Namespace, false, false, true, false) auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment auditConfig.Spec.Nodes.IntervalTimer = 1 - s.testMondooAuditConfigNodesDeployments(auditConfig) + s.testMondooAuditConfigNodesDaemonSets(auditConfig) } func (s *AuditConfigSuite) TestReconcile_AdmissionPermissive() { From 48f74b9163ad2f02a577fe81a1c1c53630980853 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:18:37 +0200 Subject: [PATCH 14/26] follow-up on tests naming --- controllers/nodes/deployment_handler_test.go | 51 ++++++++++---------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index b5f32b667..d5b4239a9 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -6,6 +6,7 @@ package nodes import ( "context" "encoding/json" + "fmt" "testing" "time" @@ -193,7 +194,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_CleanConfigMapsForDeleted func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CleanConfigMapsForDeletedNodes() { s.seedNodes() d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) mondooAuditConfig := &s.auditConfig s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) @@ -340,7 +341,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateCronJobs_Switch() { s.True(equality.Semantic.DeepEqual(cjExpected, cj)) } - mondooAuditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + mondooAuditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) result, err = d.Reconcile(s.ctx) s.NoError(err) s.True(result.IsZero()) @@ -448,10 +449,10 @@ func (s *DeploymentHandlerSuite) TestReconcile_CleanCronJobsForDeletedNodes() { s.True(equality.Semantic.DeepEqual(cjExpected, cj)) } -func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments() { +func (s *DeploymentHandlerSuite) TestReconcile_CreateDaemonSets() { s.seedNodes() d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) mondooAuditConfig := &s.auditConfig s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) @@ -488,10 +489,10 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments() { s.True(equality.Semantic.DeepEqual(gcCjExpected, gcCj)) } -func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments_Switch() { +func (s *DeploymentHandlerSuite) TestReconcile_CreateDaemonSets_Switch() { s.seedNodes() d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) mondooAuditConfig := &s.auditConfig s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) @@ -539,10 +540,10 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateDeployments_Switch() { s.True(equality.Semantic.DeepEqual(gcCjExpected, gcCj)) } -func (s *DeploymentHandlerSuite) TestReconcile_UpdateDeployments() { +func (s *DeploymentHandlerSuite) TestReconcile_UpdateDaemonSets() { s.seedNodes() d := s.createDeploymentHandler() - s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) mondooAuditConfig := &s.auditConfig s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) @@ -795,28 +796,28 @@ func (s *DeploymentHandlerSuite) TestReconcile_CronJob_CustomSchedule() { s.Equal(cj.Spec.Schedule, customSchedule) } -// func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CustomInterval() { -// s.seedNodes() -// d := s.createDeploymentHandler() -// s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment -// mondooAuditConfig := &s.auditConfig -// s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) +func (s *DeploymentHandlerSuite) TestReconcile_Deployment_CustomInterval() { + s.seedNodes() + d := s.createDeploymentHandler() + s.auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) + mondooAuditConfig := &s.auditConfig + s.NoError(d.KubeClient.Create(s.ctx, mondooAuditConfig)) -// s.auditConfig.Spec.Nodes.IntervalTimer = 1034 + s.auditConfig.Spec.Nodes.IntervalTimer = 1034 -// result, err := d.Reconcile(s.ctx) -// s.NoError(err) -// s.True(result.IsZero()) + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) -// nodes := &corev1.NodeList{} -// s.NoError(d.KubeClient.List(s.ctx, nodes)) + nodes := &corev1.NodeList{} + s.NoError(d.KubeClient.List(s.ctx, nodes)) -// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(s.auditConfig.Name, nodes.Items[0].Name), Namespace: s.auditConfig.Namespace}} -// s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) + ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(ds), ds)) -// s.Contains(dep.Spec.Template.Spec.Containers[0].Command, "--timer") -// s.Contains(dep.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("%d", s.auditConfig.Spec.Nodes.IntervalTimer)) -// } + s.Contains(ds.Spec.Template.Spec.Containers[0].Command, "--timer") + s.Contains(ds.Spec.Template.Spec.Containers[0].Command, fmt.Sprintf("%d", s.auditConfig.Spec.Nodes.IntervalTimer)) +} func (s *DeploymentHandlerSuite) createDeploymentHandler() DeploymentHandler { return DeploymentHandler{ From c64fd0b27d0c6d9138c6c34b8fd370420a1e471d Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:18:44 +0200 Subject: [PATCH 15/26] follow-up on tests naming --- tests/integration/audit_config_namespace_test.go | 2 +- tests/integration/audit_config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/audit_config_namespace_test.go b/tests/integration/audit_config_namespace_test.go index b2063ce33..f17ea94ec 100644 --- a/tests/integration/audit_config_namespace_test.go +++ b/tests/integration/audit_config_namespace_test.go @@ -104,7 +104,7 @@ func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Nodes_CronJobs() { func (s *AuditConfigCustomNamespaceSuite) TestReconcile_Nodes_DaemonSet() { auditConfig := utils.DefaultAuditConfigMinimal(s.ns.Name, false, false, true, false) - auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) auditConfig.Spec.Nodes.IntervalTimer = 1 auditConfig.Spec.Scanner.ServiceAccountName = s.sa.Name s.testMondooAuditConfigNodesDaemonSets(auditConfig) diff --git a/tests/integration/audit_config_test.go b/tests/integration/audit_config_test.go index d4d70cc60..7b832e1b7 100644 --- a/tests/integration/audit_config_test.go +++ b/tests/integration/audit_config_test.go @@ -48,7 +48,7 @@ func (s *AuditConfigSuite) TestReconcile_Nodes_CronJobs() { func (s *AuditConfigSuite) TestReconcile_Nodes_DaemonSet() { auditConfig := utils.DefaultAuditConfigMinimal(s.testCluster.Settings.Namespace, false, false, true, false) - auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment + auditConfig.Spec.Nodes.Style = v1alpha2.NodeScanStyle_Deployment // TODO: Change to DaemonSet (no effect on reconsile logic) auditConfig.Spec.Nodes.IntervalTimer = 1 s.testMondooAuditConfigNodesDaemonSets(auditConfig) } From 9ce42fe89604e4c4b1dd50bfebee23a0af7324ef Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 13:51:33 +0200 Subject: [PATCH 16/26] fix: report node status and oom test --- controllers/nodes/deployment_handler.go | 18 +++++++++ tests/integration/audit_config_oom_test.go | 47 ++++++++-------------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index d25e1691b..161028cac 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -231,6 +231,24 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { } } + if err := n.KubeClient.Get(ctx, client.ObjectKeyFromObject(ds), ds); err != nil { + logger.Error(err, "Failed to get DaemonSet", "namespace", ds.Namespace, "name", ds.Name) + } + + // Get Pods for these Deployments + pods := &corev1.PodList{} + opts := &client.ListOptions{ + Namespace: n.Mondoo.Namespace, + LabelSelector: labels.SelectorFromSet(NodeScanningLabels(*n.Mondoo)), + } + err = n.KubeClient.List(ctx, pods, opts) + if err != nil { + logger.Error(err, "Failed to list Pods for Node Scanning") + return err + } + + updateNodeConditions(n.Mondoo, ds.Status.CurrentNumberScheduled < ds.Status.DesiredNumberScheduled, pods) + if err := n.syncGCCronjob(ctx, mondooOperatorImage, clusterUid); err != nil { return err } diff --git a/tests/integration/audit_config_oom_test.go b/tests/integration/audit_config_oom_test.go index d9f5689eb..d771dbfe3 100644 --- a/tests/integration/audit_config_oom_test.go +++ b/tests/integration/audit_config_oom_test.go @@ -284,14 +284,14 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { s.auditConfig = auditConfig auditConfig.Spec.Nodes.Resources.Limits = corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("10Mi"), // this should be low enough to trigger an OOMkilled + corev1.ResourceMemory: resource.MustParse("200Mi"), // this should be low enough to trigger an OOMkilled } // Disable container image resolution to be able to run the k8s resources scan CronJob with a local image. cleanup := s.disableContainerImageResolution() defer cleanup() - zap.S().Info("Create an audit config that enables only nodes scanning. (with reduced memory limit)") + zap.S().Info("Create an audit config that enables only nodes scanning.") s.NoErrorf( s.testCluster.K8sHelper.Clientset.Create(s.ctx, &auditConfig), "Failed to create Mondoo audit config.") @@ -309,25 +309,25 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { }) s.NoError(err, "DaemonSet was not created.") - // This will take some time, because: - // reconcile needs to happen - // a new replicaset should be created - // the first Pod tries to start and gets killed - // on the 2nd start we should get an OOMkilled status update - err = s.testCluster.K8sHelper.CheckForDegradedCondition(&auditConfig, mondoov2.NodeScanningDegraded, corev1.ConditionTrue, "OOM") - s.Require().NoError(err, "Failed to find degraded condition") + // Give the integration a chance to update + time.Sleep(20 * time.Second) - foundMondooAuditConfig, err := s.testCluster.K8sHelper.GetMondooAuditConfigFromCluster(auditConfig.Name, auditConfig.Namespace) - s.NoError(err, "Failed to find MondooAuditConfig") - cond := mondoo.FindMondooAuditConditions(foundMondooAuditConfig.Status.Conditions, mondoov2.NodeScanningDegraded) - s.Require().NotNil(cond) - s.Containsf(cond.Message, "OOM", "Failed to find OOMKilled message in degraded condition") - s.Len(cond.AffectedPods, 1, "Failed to find only one pod in degraded condition") + status, err := s.integration.GetStatus(s.ctx) + s.NoError(err, "Failed to get status") + s.Equal("ACTIVE", status) + + zap.S().Info("Decreasing memory limit to get node Scans running again.") + err = s.testCluster.K8sHelper.UpdateAuditConfigWithRetries(auditConfig.Name, auditConfig.Namespace, func(config *mondoov2.MondooAuditConfig) { + config.Spec.Nodes.Resources.Limits = corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("10Mi"), // this should be enough to get the ScanAPI running again + } + }) + s.Require().NoError(err) // Give the integration a chance to update - time.Sleep(2 * time.Second) + time.Sleep(20 * time.Second) - status, err := s.integration.GetStatus(s.ctx) + status, err = s.integration.GetStatus(s.ctx) s.NoError(err, "Failed to get status") s.Equal("ERROR", status) @@ -336,25 +336,12 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { config.Spec.Nodes.Resources.Limits = corev1.ResourceList{ corev1.ResourceMemory: resource.MustParse("200Mi"), // this should be enough to get the ScanAPI running again } - foundMondooAuditConfig.Spec.Nodes.IntervalTimer = 1 }) s.Require().NoError(err) // Wait for the next run of the CronJob time.Sleep(30 * time.Second) - err = s.testCluster.K8sHelper.CheckForDegradedCondition(&auditConfig, mondoov2.NodeScanningDegraded, corev1.ConditionFalse, "") - s.Require().NoError(err, "Failed to find degraded condition") - foundMondooAuditConfig, err = s.testCluster.K8sHelper.GetMondooAuditConfigFromCluster(auditConfig.Name, auditConfig.Namespace) - s.NoError(err, "Failed to find MondooAuditConfig") - cond = mondoo.FindMondooAuditConditions(foundMondooAuditConfig.Status.Conditions, mondoov2.ScanAPIDegraded) - s.Require().NotNil(cond) - s.NotContains(cond.Message, "OOM", "Found OOMKilled message in condition") - s.Len(cond.AffectedPods, 0, "Found a pod in condition") - - // Give the integration a chance to update - time.Sleep(2 * time.Second) - status, err = s.integration.GetStatus(s.ctx) s.NoError(err, "Failed to get status") s.Equal("ACTIVE", status) From 3fb411ade36bb5759d6b204ac587caccc005bebd Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Thu, 20 Jun 2024 17:31:39 +0200 Subject: [PATCH 17/26] fix: update cronjobs spec to match updated configmap --- controllers/nodes/resources.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index fbde6403a..4a6977bab 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -44,7 +44,7 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp cmd := []string{ "cnspec", "scan", "local", "--config", "/etc/opt/mondoo/mondoo.yml", - "--inventory-file", "/etc/opt/mondoo/inventory.yml", + "--inventory-template", "/etc/opt/mondoo/inventory_template.yml", "--score-threshold", "0", } @@ -124,12 +124,8 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp Value: "false", }, { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, + Name: "NODE_NAME", + Value: node.Name, }, }, m.Spec.Nodes.Env), TerminationMessagePath: "/dev/termination-log", @@ -155,7 +151,7 @@ func UpdateCronJob(cj *batchv1.CronJob, image string, node corev1.Node, m *v1alp LocalObjectReference: corev1.LocalObjectReference{Name: ConfigMapName(m.Name)}, Items: []corev1.KeyToPath{{ Key: "inventory", - Path: "mondoo/inventory.yml", + Path: "mondoo/inventory_template.yml", }}, }, }, From 49621b083dd0e2f07a70079accb23d55a34a54c4 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:19:17 +0200 Subject: [PATCH 18/26] docs: remove misleading comment (daemon set name) --- controllers/nodes/resources.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index 4a6977bab..4435bf948 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -431,9 +431,6 @@ func DeploymentName(prefix, suffix string) string { } func DaemonSetName(prefix string) string { - // If the name becomes longer than 52 chars, then we hash the suffix and trim - // it such that the full name fits within 52 chars. This is needed because in - // manager Kubernetes services such as EKS or GKE the node names can be very long. return fmt.Sprintf("%s%s", prefix, DaemonSetNameBase) } From 3fb50ffee326eca9e0b353b042fce87f9be363a2 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:29:02 +0200 Subject: [PATCH 19/26] follow-up: update misleading or unclear naming, comments and messages in test --- tests/framework/utils/asset.go | 2 +- tests/integration/audit_config_base_suite.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/framework/utils/asset.go b/tests/framework/utils/asset.go index add010d51..3c37adee4 100644 --- a/tests/framework/utils/asset.go +++ b/tests/framework/utils/asset.go @@ -14,7 +14,7 @@ import ( v1 "k8s.io/api/core/v1" ) -func ExcludeClusterAsset(as []assets.AssetWithScore) []assets.AssetWithScore { +func ExcludeNonDetermenisticAssets(as []assets.AssetWithScore) []assets.AssetWithScore { var newAssets []assets.AssetWithScore for _, asset := range as { if asset.AssetType != "k8s.cluster" && asset.AssetType != "k8s.service" { diff --git a/tests/integration/audit_config_base_suite.go b/tests/integration/audit_config_base_suite.go index 1246e2c8d..71928dbf0 100644 --- a/tests/integration/audit_config_base_suite.go +++ b/tests/integration/audit_config_base_suite.go @@ -220,7 +220,7 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigKubernetesResources(auditCon zap.S().Info("number of assets from upstream: ", len(assets)) // TODO: the cluster name is non-deterministic currently so we cannot test for it - assetsExceptCluster := utils.ExcludeClusterAsset(assets) + nonDetermenisticAssets := utils.ExcludeNonDetermenisticAssets(assets) // TODO: this number should exclude services and the cluster asset srvs := &corev1.ServiceList{} @@ -231,9 +231,9 @@ func (s *AuditConfigBaseSuite) testMondooAuditConfigKubernetesResources(auditCon return true, nil }) s.NoError(err, "Failed to list Kubernetes Services") - s.Equalf(len(assets)-1-len(srvs.Items), len(assetsExceptCluster), "Cluster asset was sent upstream.") + s.Equalf(len(assets)-1-len(srvs.Items), len(nonDetermenisticAssets), "Cluster and/or Services assets were sent upstream.") - assetNames := utils.AssetNames(assetsExceptCluster) + assetNames := utils.AssetNames(nonDetermenisticAssets) s.ElementsMatchf(workloadNames, assetNames, "Workloads were not sent upstream.") s.AssetsNotUnscored(assets) From 805fc32862591a63fe7d6f4782b76711347039be Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:31:16 +0200 Subject: [PATCH 20/26] follow-up: copy-pasted comment not matching the code --- tests/integration/audit_config_oom_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/audit_config_oom_test.go b/tests/integration/audit_config_oom_test.go index d771dbfe3..1081c516d 100644 --- a/tests/integration/audit_config_oom_test.go +++ b/tests/integration/audit_config_oom_test.go @@ -284,7 +284,7 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { s.auditConfig = auditConfig auditConfig.Spec.Nodes.Resources.Limits = corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("200Mi"), // this should be low enough to trigger an OOMkilled + corev1.ResourceMemory: resource.MustParse("200Mi"), } // Disable container image resolution to be able to run the k8s resources scan CronJob with a local image. @@ -319,7 +319,7 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { zap.S().Info("Decreasing memory limit to get node Scans running again.") err = s.testCluster.K8sHelper.UpdateAuditConfigWithRetries(auditConfig.Name, auditConfig.Namespace, func(config *mondoov2.MondooAuditConfig) { config.Spec.Nodes.Resources.Limits = corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("10Mi"), // this should be enough to get the ScanAPI running again + corev1.ResourceMemory: resource.MustParse("10Mi"), // this should be low enough to trigger an OOMkilled } }) s.Require().NoError(err) From 85af9ef20fce4f2767d3c20aea3b716048bd956d Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:38:58 +0200 Subject: [PATCH 21/26] bring back the audit conditions test --- tests/integration/audit_config_oom_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/integration/audit_config_oom_test.go b/tests/integration/audit_config_oom_test.go index 1081c516d..f78015781 100644 --- a/tests/integration/audit_config_oom_test.go +++ b/tests/integration/audit_config_oom_test.go @@ -342,6 +342,16 @@ func (s *AuditConfigOOMSuite) TestOOMNodeScan_DaemonSet() { // Wait for the next run of the CronJob time.Sleep(30 * time.Second) + err = s.testCluster.K8sHelper.CheckForDegradedCondition(&auditConfig, mondoov2.NodeScanningDegraded, corev1.ConditionFalse, "") + s.Require().NoError(err, "Failed to find degraded condition") + foundMondooAuditConfig, err := s.testCluster.K8sHelper.GetMondooAuditConfigFromCluster(auditConfig.Name, auditConfig.Namespace) + s.NoError(err, "Failed to find MondooAuditConfig") + cond := mondoo.FindMondooAuditConditions(foundMondooAuditConfig.Status.Conditions, mondoov2.ScanAPIDegraded) + s.Require().NotNil(cond) + s.NotContains(cond.Message, "OOM", "Found OOMKilled message in condition") + + s.Len(cond.AffectedPods, 0, "Found a pod in condition") + status, err = s.integration.GetStatus(s.ctx) s.NoError(err, "Failed to get status") s.Equal("ACTIVE", status) From 4cbf4608f2ad61d7b3f73e548c1d62e1eba41f08 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:40:46 +0200 Subject: [PATCH 22/26] remove reduntant for loops in configmap related unit-tests --- controllers/nodes/deployment_handler_test.go | 38 ++++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index d5b4239a9..67fa6f88d 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -65,19 +65,14 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateConfigMap() { s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - - for range nodes.Items { - cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, - }} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) + cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, + }} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) - cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) - s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) - } + cfgMapExpected := cfgMap.DeepCopy() + s.Require().NoError(UpdateConfigMap(cfgMapExpected, "", testClusterUID, s.auditConfig)) + s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } func (s *DeploymentHandlerSuite) TestReconcile_CreateConfigMapWithIntegrationMRN() { @@ -109,19 +104,14 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateConfigMapWithIntegrationMRN s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - - for range nodes.Items { - cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, - }} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) + cfgMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: ConfigMapName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace, + }} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(cfgMap), cfgMap)) - cfgMapExpected := cfgMap.DeepCopy() - s.Require().NoError(UpdateConfigMap(cfgMapExpected, testIntegrationMRN, testClusterUID, s.auditConfig)) - s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) - } + cfgMapExpected := cfgMap.DeepCopy() + s.Require().NoError(UpdateConfigMap(cfgMapExpected, testIntegrationMRN, testClusterUID, s.auditConfig)) + s.True(equality.Semantic.DeepEqual(cfgMapExpected, cfgMap)) } func (s *DeploymentHandlerSuite) TestReconcile_UpdateConfigMap() { From 2b9d174d06098f322cdccd2a5d2c2fc18bc6bb55 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:43:36 +0200 Subject: [PATCH 23/26] follow-up: correct misleading comment --- controllers/nodes/deployment_handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index 67fa6f88d..3ff1fb626 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -544,7 +544,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateDaemonSets() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - // Make sure a daemonset exists for one of the nodes + // Make sure a daemonset exists ds := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} UpdateDaemonSet(ds, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) ds.Spec.Template.Spec.Containers[0].Command = []string{"test-command"} From bb3f86dcc7074dfdbbbedfbf15573e14a066e266 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 12:46:04 +0200 Subject: [PATCH 24/26] follow-up: correct var naming --- controllers/nodes/deployment_handler_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index 3ff1fb626..b0a1ac672 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -554,12 +554,12 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateDaemonSets() { s.NoError(err) s.True(result.IsZero()) - dep := &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} - s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(dep), dep)) + ds = &appsv1.DaemonSet{ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(s.auditConfig.Name), Namespace: s.auditConfig.Namespace}} + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(ds), ds)) - depExpected := dep.DeepCopy() + depExpected := ds.DeepCopy() UpdateDaemonSet(depExpected, s.auditConfig, false, image, v1alpha2.MondooOperatorConfig{}) - s.True(equality.Semantic.DeepEqual(depExpected, dep)) + s.True(equality.Semantic.DeepEqual(depExpected, ds)) } func (s *DeploymentHandlerSuite) TestReconcile_CronJob_NodeScanningStatus() { From 1070320583c3c60ee3ada0f558dc52edc57d5db7 Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 13:58:13 +0200 Subject: [PATCH 25/26] fix: delete daemonset on down and style change --- controllers/nodes/deployment_handler.go | 48 ++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 161028cac..cee04849c 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -72,17 +72,17 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { return err } + // Delete DaemonSet if it exists + ds := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}, + } + if err := k8s.DeleteIfExists(ctx, n.KubeClient, ds); err != nil { + logger.Error(err, "Failed to clean up node scanning DaemonSet", "namespace", ds.Namespace, "name", ds.Name) + return err + } + // Create/update CronJobs for nodes for _, node := range nodes.Items { - // Delete Deployment if it exists - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}, - } - if err := k8s.DeleteIfExists(ctx, n.KubeClient, dep); err != nil { - logger.Error(err, "Failed to clean up node scanning Deployment", "namespace", dep.Namespace, "name", dep.Name) - return err - } - updated, err := n.syncConfigMap(ctx, clusterUid) if err != nil { return err @@ -201,7 +201,7 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { if updated { logger.Info( - "Inventory ConfigMap was just updated. The deployment will use the new config during the next scheduled run.", + "Inventory ConfigMap was just updated. The daemonset will use the new config during the next scheduled run.", "namespace", n.Mondoo.Namespace, "name", DeploymentName(n.Mondoo.Name, node.Name)) } @@ -345,22 +345,22 @@ func (n *DeploymentHandler) down(ctx context.Context) error { logger.Error(err, "Failed to clean up node scanning CronJob", "namespace", cronJob.Namespace, "name", cronJob.Name) return err } + } - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: DeploymentName(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}, - } - if err := k8s.DeleteIfExists(ctx, n.KubeClient, dep); err != nil { - logger.Error(err, "Failed to clean up node scanning Deployment", "namespace", dep.Namespace, "name", dep.Name) - return err - } + ds := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: DaemonSetName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}, + } + if err := k8s.DeleteIfExists(ctx, n.KubeClient, ds); err != nil { + logger.Error(err, "Failed to clean up node scanning DaemonSet", "namespace", ds.Namespace, "name", ds.Name) + return err + } - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}, - } - if err := k8s.DeleteIfExists(ctx, n.KubeClient, configMap); err != nil { - logger.Error(err, "Failed to clean up inventory ConfigMap", "namespace", configMap.Namespace, "name", configMap.Name) - return err - } + configMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: ConfigMapName(n.Mondoo.Name), Namespace: n.Mondoo.Namespace}, + } + if err := k8s.DeleteIfExists(ctx, n.KubeClient, configMap); err != nil { + logger.Error(err, "Failed to clean up inventory ConfigMap", "namespace", configMap.Namespace, "name", configMap.Name) + return err } gcCronJob := &batchv1.CronJob{ From 9dc19970b4fda8b0e073656900e4969545c7e67b Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Fri, 21 Jun 2024 15:00:46 +0200 Subject: [PATCH 26/26] fix: delete deprecated config maps --- controllers/nodes/deployment_handler.go | 12 ++++++++++++ controllers/nodes/resources.go | 16 +++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index cee04849c..18920df2d 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -83,6 +83,12 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { // Create/update CronJobs for nodes for _, node := range nodes.Items { + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: ConfigMapNameWithNode(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}} + if err := k8s.DeleteIfExists(ctx, n.KubeClient, cm); err != nil { + logger.Error(err, "Failed to clean up old ConfigMap for node scanning", "namespace", cm.Namespace, "name", cm.Name) + return err + } + updated, err := n.syncConfigMap(ctx, clusterUid) if err != nil { return err @@ -194,6 +200,12 @@ func (n *DeploymentHandler) syncDaemonSet(ctx context.Context) error { return err } + cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: ConfigMapNameWithNode(n.Mondoo.Name, node.Name), Namespace: n.Mondoo.Namespace}} + if err := k8s.DeleteIfExists(ctx, n.KubeClient, cm); err != nil { + logger.Error(err, "Failed to clean up old ConfigMap for node scanning", "namespace", cm.Namespace, "name", cm.Name) + return err + } + updated, err := n.syncConfigMap(ctx, clusterUid) if err != nil { return err diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index 4435bf948..f48feb775 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -28,11 +28,12 @@ import ( ) const ( - CronJobNameBase = "-node-" - DeploymentNameBase = "-node-" - DaemonSetNameBase = "-node" - GarbageCollectCronJobNameBase = "-node-gc" - InventoryConfigMapBase = "-node-inventory" + CronJobNameBase = "-node-" + DeploymentNameBase = "-node-" + DaemonSetNameBase = "-node" + GarbageCollectCronJobNameBase = "-node-gc" + InventoryConfigMapBase = "-node-inventory" + InventoryConfigMapWithNodeBase = "-node-inventory-" ignoreQueryAnnotationPrefix = "policies.k8s.mondoo.com/" @@ -442,6 +443,11 @@ func ConfigMapName(prefix string) string { return fmt.Sprintf("%s%s", prefix, InventoryConfigMapBase) } +func ConfigMapNameWithNode(prefix, nodeName string) string { + base := fmt.Sprintf("%s%s", prefix, InventoryConfigMapWithNodeBase) + return fmt.Sprintf("%s%s", base, NodeNameOrHash(k8s.ResourceNameMaxLength-len(base), nodeName)) +} + func Inventory(integrationMRN, clusterUID string, m v1alpha2.MondooAuditConfig) (string, error) { inv := &inventory.Inventory{ Metadata: &inventory.ObjectMeta{