From 81a9e2cc08e8b8dcf07cbfe2cd4c8e83039c099e Mon Sep 17 00:00:00 2001 From: Kevin Conner Date: Fri, 19 Jan 2024 07:35:25 -0800 Subject: [PATCH] UD-1129: Update reconciliation of misconfigurations and vulnerabilities to check for deltas before sending to SaaS Signed-off-by: Kevin Conner --- api/zora/v1alpha1/clusterscan_types.go | 8 +++ api/zora/v1alpha1/zz_generated.deepcopy.go | 57 +++++++++++++++++ .../crds/zora.undistro.io_clusterscans.yaml | 14 +++++ .../bases/zora.undistro.io_clusterscans.yaml | 14 +++++ internal/saas/hooks.go | 63 +++++++++++++++++++ 5 files changed, 156 insertions(+) diff --git a/api/zora/v1alpha1/clusterscan_types.go b/api/zora/v1alpha1/clusterscan_types.go index f3b7fe99..20f28470 100644 --- a/api/zora/v1alpha1/clusterscan_types.go +++ b/api/zora/v1alpha1/clusterscan_types.go @@ -71,6 +71,8 @@ func (in *PluginReference) PluginKey(defaultNamespace string) types.NamespacedNa return types.NamespacedName{Name: in.Name, Namespace: ns} } +type PluginScanProcessedResources map[string]string + // ClusterScanStatus defines the observed state of ClusterScan type ClusterScanStatus struct { Status `json:",inline"` @@ -104,6 +106,12 @@ type ClusterScanStatus struct { // Total of ClusterIssues reported in the last successful scan TotalIssues *int `json:"totalIssues,omitempty"` + + // Resource versions of processed vulnerabilities + ProcessedVulnerabilities map[string]PluginScanProcessedResources `json:"processedVulnerabilities,omitempty"` + + // Resource versions of processed misconfigurations + ProcessedMisconfigurations map[string]PluginScanProcessedResources `json:"processedMisconfigurations,omitempty"` } // GetPluginStatus returns a PluginScanStatus of a plugin diff --git a/api/zora/v1alpha1/zz_generated.deepcopy.go b/api/zora/v1alpha1/zz_generated.deepcopy.go index 0043cafa..dc303668 100644 --- a/api/zora/v1alpha1/zz_generated.deepcopy.go +++ b/api/zora/v1alpha1/zz_generated.deepcopy.go @@ -329,6 +329,42 @@ func (in *ClusterScanStatus) DeepCopyInto(out *ClusterScanStatus) { *out = new(int) **out = **in } + if in.ProcessedVulnerabilities != nil { + in, out := &in.ProcessedVulnerabilities, &out.ProcessedVulnerabilities + *out = make(map[string]PluginScanProcessedResources, len(*in)) + for key, val := range *in { + var outVal map[string]string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(PluginScanProcessedResources, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } + if in.ProcessedMisconfigurations != nil { + in, out := &in.ProcessedMisconfigurations, &out.ProcessedMisconfigurations + *out = make(map[string]PluginScanProcessedResources, len(*in)) + for key, val := range *in { + var outVal map[string]string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(PluginScanProcessedResources, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterScanStatus. @@ -603,6 +639,27 @@ func (in *PluginReference) DeepCopy() *PluginReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in PluginScanProcessedResources) DeepCopyInto(out *PluginScanProcessedResources) { + { + in := &in + *out = make(PluginScanProcessedResources, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginScanProcessedResources. +func (in PluginScanProcessedResources) DeepCopy() PluginScanProcessedResources { + if in == nil { + return nil + } + out := new(PluginScanProcessedResources) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginScanStatus) DeepCopyInto(out *PluginScanStatus) { *out = *in diff --git a/charts/zora/crds/zora.undistro.io_clusterscans.yaml b/charts/zora/crds/zora.undistro.io_clusterscans.yaml index ad2923f1..76e019ab 100644 --- a/charts/zora/crds/zora.undistro.io_clusterscans.yaml +++ b/charts/zora/crds/zora.undistro.io_clusterscans.yaml @@ -409,6 +409,20 @@ spec: type: object description: Information of the last scans of plugins type: object + processedMisconfigurations: + additionalProperties: + additionalProperties: + type: string + type: object + description: Resource versions of processed misconfigurations + type: object + processedVulnerabilities: + additionalProperties: + additionalProperties: + type: string + type: object + description: Resource versions of processed vulnerabilities + type: object suspend: description: Suspend field value from ClusterScan spec type: boolean diff --git a/config/crd/bases/zora.undistro.io_clusterscans.yaml b/config/crd/bases/zora.undistro.io_clusterscans.yaml index 2993a960..fe156230 100644 --- a/config/crd/bases/zora.undistro.io_clusterscans.yaml +++ b/config/crd/bases/zora.undistro.io_clusterscans.yaml @@ -395,6 +395,20 @@ spec: type: object description: Information of the last scans of plugins type: object + processedMisconfigurations: + additionalProperties: + additionalProperties: + type: string + type: object + description: Resource versions of processed misconfigurations + type: object + processedVulnerabilities: + additionalProperties: + additionalProperties: + type: string + type: object + description: Resource versions of processed vulnerabilities + type: object suspend: description: Suspend field value from ClusterScan spec type: boolean diff --git a/internal/saas/hooks.go b/internal/saas/hooks.go index 330183a8..1f394f78 100644 --- a/internal/saas/hooks.go +++ b/internal/saas/hooks.go @@ -17,10 +17,13 @@ package saas import ( "context" "errors" + "fmt" + "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "github.com/undistro/zora/api/zora/v1alpha1" ) @@ -107,6 +110,13 @@ func pushMisconfigs(saasClient Client, c ctrlClient.Client, ctx context.Context, return err } + pluginProcessedResources := getMisconfigProcessedResources(clusterScan.Status.Plugins, issueList.Items) + if reflect.DeepEqual(pluginProcessedResources, clusterScan.Status.ProcessedMisconfigurations) { + log := log.FromContext(ctx) + log.Info("Skipping misconfigurations, no changes from processed misconfigurations") + return nil + } + status := NewScanStatusWithIssues(scanList.Items, issueList.Items) if status == nil { return nil @@ -120,6 +130,7 @@ func pushMisconfigs(saasClient Client, c ctrlClient.Client, ctx context.Context, clusterScan.SetSaaSStatus(metav1.ConditionFalse, "Error", err.Error()) return err } + clusterScan.Status.ProcessedMisconfigurations = pluginProcessedResources clusterScan.SetSaaSStatus(metav1.ConditionTrue, "OK", "cluster scan successfully synced with SaaS") return nil } @@ -142,6 +153,14 @@ func pushVulns(scl Client, cl ctrlClient.Client, ctx context.Context, cs *v1alph if len(metaList.Items) == 0 { return nil } + + pluginProcessedResources := getVulnerabilityProcessedResources(metaList.Items) + if reflect.DeepEqual(pluginProcessedResources, cs.Status.ProcessedVulnerabilities) { + log := log.FromContext(ctx) + log.Info("Skipping vulnerabilities, no changes from processed vulnerabilities") + return nil + } + for _, i := range metaList.Items { vulnReport := &v1alpha1.VulnerabilityReport{} if err := cl.Get(ctx, types.NamespacedName{Namespace: i.Namespace, Name: i.Name}, vulnReport); err != nil { @@ -161,6 +180,7 @@ func pushVulns(scl Client, cl ctrlClient.Client, ctx context.Context, cs *v1alph return err } } + cs.Status.ProcessedVulnerabilities = pluginProcessedResources cs.SetSaaSStatus(metav1.ConditionTrue, "OK", "cluster scan successfully synced with SaaS") return nil } @@ -180,3 +200,46 @@ func buildLabelSelector(clusterName string, scanIDs []string) (*ctrlClient.Match } return &ctrlClient.MatchingLabelsSelector{Selector: ls}, nil } + +func getMisconfigProcessedResources(pluginStatus map[string]*v1alpha1.PluginScanStatus, clusterIssues []v1alpha1.ClusterIssue) map[string]v1alpha1.PluginScanProcessedResources { + var pluginProcessedResources map[string]v1alpha1.PluginScanProcessedResources + issuePluginIncluded := false + for _, issue := range clusterIssues { + plugin := issue.Labels[v1alpha1.LabelPlugin] + if !issuePluginIncluded { + _, issuePluginIncluded = pluginStatus[plugin] + } + if pluginProcessedResources == nil { + pluginProcessedResources = map[string]v1alpha1.PluginScanProcessedResources{} + } + processedResources, ok := pluginProcessedResources[plugin] + if !ok { + processedResources = v1alpha1.PluginScanProcessedResources{} + pluginProcessedResources[plugin] = processedResources + } + processedResources[fmt.Sprintf("%s/%s", issue.Namespace, issue.Name)] = issue.ResourceVersion + } + if issuePluginIncluded { + return pluginProcessedResources + } else { + return nil + } +} + +func getVulnerabilityProcessedResources(vulnerabiltiyIssues []metav1.PartialObjectMetadata) map[string]v1alpha1.PluginScanProcessedResources { + var pluginProcessedResources map[string]v1alpha1.PluginScanProcessedResources + for _, issue := range vulnerabiltiyIssues { + plugin := issue.Labels[v1alpha1.LabelPlugin] + if pluginProcessedResources == nil { + pluginProcessedResources = map[string]v1alpha1.PluginScanProcessedResources{} + } + processedResources, ok := pluginProcessedResources[plugin] + if !ok { + processedResources = v1alpha1.PluginScanProcessedResources{} + pluginProcessedResources[plugin] = processedResources + } + processedResources[fmt.Sprintf("%s/%s", issue.Namespace, issue.Name)] = issue.ResourceVersion + + } + return pluginProcessedResources +}