diff --git a/core/ports/repositories.go b/core/ports/repositories.go index 6aa5fc1..a079a11 100644 --- a/core/ports/repositories.go +++ b/core/ports/repositories.go @@ -10,6 +10,7 @@ import ( type CVERepository interface { GetCVE(ctx context.Context, name, SBOMCreatorVersion, CVEScannerVersion, CVEDBVersion string) (domain.CVEManifest, error) StoreCVE(ctx context.Context, cve domain.CVEManifest, withRelevancy bool) error + StoreCVESummary(ctx context.Context, cve domain.CVEManifest, cvep domain.CVEManifest, withRelevancy bool) error } // SBOMRepository is the port implemented by adapters to be used in ScanService to store SBOMs diff --git a/core/services/scan.go b/core/services/scan.go index f59912d..ae55672 100644 --- a/core/services/scan.go +++ b/core/services/scan.go @@ -195,6 +195,11 @@ func (s *ScanService) ScanCVE(ctx context.Context) error { logger.L().Ctx(ctx).Warning("error storing CVE", helpers.Error(err), helpers.String("imageSlug", workload.ImageSlug)) } + err = s.cveRepository.StoreCVESummary(ctx, cve, domain.CVEManifest{}, false) + if err != nil { + logger.L().Ctx(ctx).Warning("error storing CVE summary", helpers.Error(err), + helpers.String("imageSlug", workload.ImageSlug)) + } } } @@ -224,6 +229,11 @@ func (s *ScanService) ScanCVE(ctx context.Context) error { logger.L().Ctx(ctx).Warning("error storing CVEp", helpers.Error(err), helpers.String("instanceID", workload.InstanceID)) } + err = s.cveRepository.StoreCVESummary(ctx, cve, cvep, true) + if err != nil { + logger.L().Ctx(ctx).Warning("error storing CVE summary", helpers.Error(err), + helpers.String("imageSlug", workload.ImageSlug)) + } } } diff --git a/repositories/apiserver.go b/repositories/apiserver.go index 92c2b5b..f949eb1 100644 --- a/repositories/apiserver.go +++ b/repositories/apiserver.go @@ -105,7 +105,7 @@ func (a *APIServerStore) GetCVE(ctx context.Context, name, SBOMCreatorVersion, C }, nil } -func (a *APIServerStore) storeCVEWithFullContent(ctx context.Context, cve domain.CVEManifest, withRelevancy bool) error { +func (a *APIServerStore) StoreCVE(ctx context.Context, cve domain.CVEManifest, withRelevancy bool) error { _, span := otel.Tracer("").Start(ctx, "APIServerStore.StoreCVEWithFullContent") defer span.End() @@ -183,22 +183,22 @@ func (a *APIServerStore) storeCVEWithFullContent(ctx context.Context, cve domain return nil } -func parseVulnerabilitiesComponents(name, namespace string, withRelevancy bool) v1beta1.VulnerabilitiesComponents { +func parseVulnerabilitiesComponents(cve domain.CVEManifest, cvep domain.CVEManifest, namespace string, withRelevancy bool) v1beta1.VulnerabilitiesComponents { vulComp := v1beta1.VulnerabilitiesComponents{} if withRelevancy { - vulComp.WorkloadVulnerabilitiesObj.Name = name + vulComp.WorkloadVulnerabilitiesObj.Name = cvep.Name vulComp.WorkloadVulnerabilitiesObj.Kind = vulnerabilityManifestSummaryKindPlural vulComp.WorkloadVulnerabilitiesObj.Namespace = namespace - } else { - vulComp.ImageVulnerabilitiesObj.Name = name - vulComp.ImageVulnerabilitiesObj.Kind = vulnerabilityManifestSummaryKindPlural - vulComp.ImageVulnerabilitiesObj.Namespace = namespace } + vulComp.ImageVulnerabilitiesObj.Name = cve.Name + vulComp.ImageVulnerabilitiesObj.Kind = vulnerabilityManifestSummaryKindPlural + vulComp.ImageVulnerabilitiesObj.Namespace = namespace + return vulComp } -func parseSeverities(cve domain.CVEManifest, withRelevancy bool) v1beta1.SeveritySummary { +func parseSeverities(cve domain.CVEManifest, cvep domain.CVEManifest, withRelevancy bool) v1beta1.SeveritySummary { critical := 0 criticalRelevant := 0 high := 0 @@ -215,40 +215,34 @@ func parseSeverities(cve domain.CVEManifest, withRelevancy bool) v1beta1.Severit for i := range cve.Content.Matches { switch cve.Content.Matches[i].Vulnerability.Severity { case domain.CriticalSeverity: - if withRelevancy { - criticalRelevant += 1 - } else { - critical += 1 - } + critical += 1 case domain.HighSeverity: - if withRelevancy { - highRelevant += 1 - } else { - high += 1 - } + high += 1 case domain.MediumSeverity: - if withRelevancy { - mediumRelevant += 1 - } else { - medium += 1 - } + medium += 1 case domain.LowSeverity: - if withRelevancy { - lowRelevant += 1 - } else { - low += 1 - } + low += 1 case domain.NegligibleSeverity: - if withRelevancy { - negligibleRelevant += 1 - } else { - negligible += 1 - } + negligible += 1 case domain.UnknownSeverity: - if withRelevancy { + unknown += 1 + } + } + if withRelevancy { + for i := range cvep.Content.Matches { + switch cvep.Content.Matches[i].Vulnerability.Severity { + case domain.CriticalSeverity: + criticalRelevant += 1 + case domain.HighSeverity: + highRelevant += 1 + case domain.MediumSeverity: + mediumRelevant += 1 + case domain.LowSeverity: + lowRelevant += 1 + case domain.NegligibleSeverity: + negligibleRelevant += 1 + case domain.UnknownSeverity: unknownRelevant += 1 - } else { - unknown += 1 } } } @@ -323,8 +317,17 @@ func GetCVESummaryK8sResourceName(ctx context.Context) (string, error) { return fmt.Sprintf(vulnSummaryContNameFormat, kind, name, contName), nil } -func (a *APIServerStore) storeCVESummary(ctx context.Context, cve domain.CVEManifest, withRelevancy bool) error { - _, span := otel.Tracer("").Start(ctx, "APIServerStore.storeCVESummary") +func GetCVESummaryK8sResourceNamespace(ctx context.Context) (string, error) { + workload, ok := ctx.Value(domain.WorkloadKey{}).(domain.ScanCommand) + if !ok { + return "", domain.ErrCastingWorkload + } + + return wlid.GetNamespaceFromWlid(workload.Wlid), nil +} + +func (a *APIServerStore) StoreCVESummary(ctx context.Context, cve domain.CVEManifest, cvep domain.CVEManifest, withRelevancy bool) error { + _, span := otel.Tracer("").Start(ctx, "APIServerStore.StoreCVESummary") defer span.End() if cve.Name == "" { @@ -345,6 +348,10 @@ func (a *APIServerStore) storeCVESummary(ctx context.Context, cve domain.CVEMani if err != nil { return err } + workloadNamespace, err := GetCVESummaryK8sResourceNamespace(ctx) + if err != nil { + return err + } manifest := v1beta1.VulnerabilityManifestSummary{ ObjectMeta: metav1.ObjectMeta{ @@ -353,17 +360,17 @@ func (a *APIServerStore) storeCVESummary(ctx context.Context, cve domain.CVEMani Labels: labels, }, Spec: v1beta1.VulnerabilityManifestSummarySpec{ - Severities: parseSeverities(cve, withRelevancy), - Vulnerabilities: parseVulnerabilitiesComponents(cve.Name, a.Namespace, withRelevancy), + Severities: parseSeverities(cve, cvep, withRelevancy), + Vulnerabilities: parseVulnerabilitiesComponents(cve, cvep, workloadNamespace, withRelevancy), }, } - _, err = a.StorageClient.VulnerabilityManifestSummaries(a.Namespace).Create(context.Background(), &manifest, metav1.CreateOptions{}) + _, err = a.StorageClient.VulnerabilityManifestSummaries(workloadNamespace).Create(context.Background(), &manifest, metav1.CreateOptions{}) switch { case errors.IsAlreadyExists(err): retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { // retrieve the latest version before attempting update // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := a.StorageClient.VulnerabilityManifestSummaries(a.Namespace).Get(context.Background(), cve.Name, metav1.GetOptions{}) + result, getErr := a.StorageClient.VulnerabilityManifestSummaries(workloadNamespace).Get(context.Background(), cve.Name, metav1.GetOptions{}) if getErr != nil { return getErr } @@ -372,7 +379,7 @@ func (a *APIServerStore) storeCVESummary(ctx context.Context, cve domain.CVEMani result.Labels = manifest.Labels result.Spec = manifest.Spec // try to send the updated vulnerability manifest - _, updateErr := a.StorageClient.VulnerabilityManifestSummaries(a.Namespace).Update(context.Background(), result, metav1.UpdateOptions{}) + _, updateErr := a.StorageClient.VulnerabilityManifestSummaries(workloadNamespace).Update(context.Background(), result, metav1.UpdateOptions{}) return updateErr }) if retryErr != nil { @@ -396,23 +403,6 @@ func (a *APIServerStore) storeCVESummary(ctx context.Context, cve domain.CVEMani return nil } -func (a *APIServerStore) StoreCVE(ctx context.Context, cve domain.CVEManifest, withRelevancy bool) error { - innerCtx, span := otel.Tracer("").Start(ctx, "APIServerStore.StoreCVE") - defer span.End() - - err := a.storeCVEWithFullContent(innerCtx, cve, withRelevancy) - if err != nil { - return err - } - - err = a.storeCVESummary(innerCtx, cve, withRelevancy) - if err != nil { - return err - } - - return nil -} - func (a *APIServerStore) GetSBOM(ctx context.Context, name, SBOMCreatorVersion string) (domain.SBOM, error) { _, span := otel.Tracer("").Start(ctx, "APIServerStore.GetSBOM") defer span.End() diff --git a/repositories/apiserver_test.go b/repositories/apiserver_test.go index 6c7dc6b..a4ad82d 100644 --- a/repositories/apiserver_test.go +++ b/repositories/apiserver_test.go @@ -391,7 +391,22 @@ func TestAPIServerStore_parseSeverities(t *testing.T) { var nginxCVEUnknownSeveritiesNumber = 0 cveManifest := tools.FileToCVEManifest("testdata/nginx-cve.json") - severities := parseSeverities(cveManifest, false) + severities := parseSeverities(cveManifest, cveManifest, false) + assert.Equal(t, nginxCVECriticalSeveritiesNumber, severities.Critical.All) + assert.Equal(t, nginxCVEHighSeveritiesNumber, severities.High.All) + assert.Equal(t, nginxCVEMediumSeveritiesNumber, severities.Medium.All) + assert.Equal(t, nginxCVELowSeveritiesNumber, severities.Low.All) + assert.Equal(t, nginxCVENegligibleSeveritiesNumber, severities.Negligible.All) + assert.Equal(t, nginxCVEUnknownSeveritiesNumber, severities.Unknown.All) + + assert.Equal(t, 0, severities.Critical.Relevant) + assert.Equal(t, 0, severities.High.Relevant) + assert.Equal(t, 0, severities.Medium.Relevant) + assert.Equal(t, 0, severities.Low.Relevant) + assert.Equal(t, 0, severities.Negligible.Relevant) + assert.Equal(t, 0, severities.Unknown.Relevant) + + severities = parseSeverities(cveManifest, cveManifest, true) assert.Equal(t, nginxCVECriticalSeveritiesNumber, severities.Critical.All) assert.Equal(t, nginxCVEHighSeveritiesNumber, severities.High.All) assert.Equal(t, nginxCVEMediumSeveritiesNumber, severities.Medium.All) @@ -399,7 +414,6 @@ func TestAPIServerStore_parseSeverities(t *testing.T) { assert.Equal(t, nginxCVENegligibleSeveritiesNumber, severities.Negligible.All) assert.Equal(t, nginxCVEUnknownSeveritiesNumber, severities.Unknown.All) - severities = parseSeverities(cveManifest, true) assert.Equal(t, nginxCVECriticalSeveritiesNumber, severities.Critical.Relevant) assert.Equal(t, nginxCVEHighSeveritiesNumber, severities.High.Relevant) assert.Equal(t, nginxCVEMediumSeveritiesNumber, severities.Medium.Relevant) @@ -409,15 +423,19 @@ func TestAPIServerStore_parseSeverities(t *testing.T) { } func TestAPIServerStore_parseVulnerabilitiesComponents(t *testing.T) { - any := "any" namespace := "namespace" - res := parseVulnerabilitiesComponents(any, namespace, false) - assert.Equal(t, res.ImageVulnerabilitiesObj.Name, any) + cveManifest := tools.FileToCVEManifest("testdata/nginx-cve.json") + res := parseVulnerabilitiesComponents(cveManifest, cveManifest, namespace, false) + assert.Equal(t, res.ImageVulnerabilitiesObj.Name, cveManifest.Name) assert.Equal(t, res.ImageVulnerabilitiesObj.Namespace, namespace) + assert.Equal(t, res.WorkloadVulnerabilitiesObj.Name, "") + assert.Equal(t, res.WorkloadVulnerabilitiesObj.Namespace, "") - res = parseVulnerabilitiesComponents(any, namespace, true) - assert.Equal(t, res.WorkloadVulnerabilitiesObj.Name, any) + res = parseVulnerabilitiesComponents(cveManifest, cveManifest, namespace, true) + assert.Equal(t, res.ImageVulnerabilitiesObj.Name, cveManifest.Name) + assert.Equal(t, res.ImageVulnerabilitiesObj.Namespace, namespace) + assert.Equal(t, res.WorkloadVulnerabilitiesObj.Name, cveManifest.Name) assert.Equal(t, res.WorkloadVulnerabilitiesObj.Namespace, namespace) } @@ -425,10 +443,10 @@ func TestAPIServerStore_storeCVESummary(t *testing.T) { cveManifest := tools.FileToCVEManifest("testdata/nginx-cve.json") a := NewFakeAPIServerStorage("kubescape") - err := a.storeCVESummary(context.TODO(), cveManifest, false) + err := a.StoreCVESummary(context.TODO(), cveManifest, cveManifest, false) assert.Equal(t, err, nil) - err = a.storeCVESummary(context.TODO(), cveManifest, true) + err = a.StoreCVESummary(context.TODO(), cveManifest, cveManifest, true) assert.Equal(t, err, nil) } diff --git a/repositories/broken.go b/repositories/broken.go index 3d7d6ef..4a4ae1f 100644 --- a/repositories/broken.go +++ b/repositories/broken.go @@ -47,3 +47,9 @@ func (b BrokenStore) StoreCVE(ctx context.Context, _ domain.CVEManifest, _ bool) defer span.End() return domain.ErrExpectedError } + +func (b BrokenStore) StoreCVESummary(ctx context.Context, _ domain.CVEManifest, _ domain.CVEManifest, _ bool) error { + _, span := otel.Tracer("").Start(ctx, "BrokenStore.StoreCVESummary") + defer span.End() + return domain.ErrExpectedError +} diff --git a/repositories/memory.go b/repositories/memory.go index 1e40277..136a909 100644 --- a/repositories/memory.go +++ b/repositories/memory.go @@ -82,6 +82,36 @@ func (m *MemoryStore) StoreCVE(ctx context.Context, cve domain.CVEManifest, _ bo return nil } +// StoreCVE stores a CVE Summary to an in-memory map +func (m *MemoryStore) StoreCVESummary(ctx context.Context, cve domain.CVEManifest, cvep domain.CVEManifest, withRelevancy bool) error { + _, span := otel.Tracer("").Start(ctx, "MemoryStore.StoreCVESummary") + defer span.End() + + if m.storeError { + return domain.ErrMockError + } + + id := cveID{ + Name: cve.Name, + SBOMCreatorVersion: cve.SBOMCreatorVersion, + CVEScannerVersion: cve.CVEScannerVersion, + CVEDBVersion: cve.CVEDBVersion, + } + + if withRelevancy { + idSumm := cveID{ + Name: cvep.Name, + SBOMCreatorVersion: cvep.SBOMCreatorVersion, + CVEScannerVersion: cvep.CVEScannerVersion, + CVEDBVersion: cvep.CVEDBVersion, + } + m.cveManifests[idSumm] = cvep + } + + m.cveManifests[id] = cve + return nil +} + // GetSBOM returns a SBOM from an in-memory map func (m *MemoryStore) GetSBOM(ctx context.Context, name, SBOMCreatorVersion string) (domain.SBOM, error) { _, span := otel.Tracer("").Start(ctx, "MemoryStore.GetSBOM")