diff --git a/changes/bug-7320-duplicates-cves-in-software-page b/changes/bug-7320-duplicates-cves-in-software-page new file mode 100644 index 000000000..6057f5c39 --- /dev/null +++ b/changes/bug-7320-duplicates-cves-in-software-page @@ -0,0 +1 @@ +The software details page was showing duplicated vulnerabilities if the software was used by many hosts. diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index 88cdd619e..a5a20b52c 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -756,12 +756,6 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, includeCVEScores "s.arch", "scv.cve", ). - Join( // filter software that is not associated with any hosts - goqu.I("host_software").As("hs"), - goqu.On( - goqu.I("hs.software_id").Eq(goqu.I("s.id")), - ), - ). LeftJoin( goqu.I("software_cpe").As("scp"), goqu.On( @@ -787,6 +781,8 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, includeCVEScores } q = q.Where(goqu.I("s.id").Eq(id)) + // filter software that is not associated with any hosts + q = q.Where(goqu.L("EXISTS (SELECT 1 FROM host_software WHERE software_id = ? LIMIT 1)", id)) sql, args, err := q.ToSQL() if err != nil { diff --git a/server/datastore/mysql/software_test.go b/server/datastore/mysql/software_test.go index b2aeafe89..98e40802b 100644 --- a/server/datastore/mysql/software_test.go +++ b/server/datastore/mysql/software_test.go @@ -41,6 +41,7 @@ func TestSoftware(t *testing.T) { {"InsertVulnerabilities", testInsertVulnerabilities}, {"ListCVEs", testListCVEs}, {"ListSoftwareForVulnDetection", testListSoftwareForVulnDetection}, + {"SoftwareByID", testSoftwareByID}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { @@ -1609,3 +1610,48 @@ func testListSoftwareForVulnDetection(t *testing.T, ds *Datastore) { } }) } + +func testSoftwareByID(t *testing.T, ds *Datastore) { + t.Run("software installed in multiple hosts does not have duplicated vulnerabilities", func(t *testing.T) { + ctx := context.Background() + + hostA := test.NewHost(t, ds, "hostA", "", "hostAkey", "hostAuuid", time.Now()) + hostA.Platform = "ubuntu" + ds.UpdateHost(ctx, hostA) + + hostB := test.NewHost(t, ds, "hostB", "", "hostBkey", "hostBuuid", time.Now()) + hostB.Platform = "ubuntu" + ds.UpdateHost(ctx, hostB) + + software := []fleet.Software{ + {Name: "foo_123", Version: "0.0.1", Source: "chrome_extensions"}, + {Name: "bar_123", Version: "0.0.3", Source: "apps"}, + {Name: "biz_123", Version: "0.0.1", Source: "deb_packages"}, + {Name: "baz_123", Version: "0.0.3", Source: "deb_packages"}, + } + + require.NoError(t, ds.UpdateHostSoftware(ctx, hostA.ID, software)) + require.NoError(t, ds.UpdateHostSoftware(ctx, hostB.ID, software)) + + require.NoError(t, ds.LoadHostSoftware(ctx, hostA, false)) + require.NoError(t, ds.LoadHostSoftware(ctx, hostB, false)) + + // Add one vulnerability to each software + var vulns []fleet.SoftwareVulnerability + for i, s := range hostA.Software { + vulns = append(vulns, fleet.SoftwareVulnerability{ + SoftwareID: s.ID, + CVE: fmt.Sprintf("cve-%d", i), + }) + } + n, err := ds.InsertVulnerabilities(ctx, vulns, fleet.UbuntuOVALSource) + require.NoError(t, err) + require.Equal(t, 4, int(n)) + + for _, s := range hostA.Software { + result, err := ds.SoftwareByID(ctx, s.ID, true) + require.NoError(t, err) + require.Len(t, result.Vulnerabilities, 1) + } + }) +}