diff --git a/changes/26618-software-vuln-detected-dates b/changes/26618-software-vuln-detected-dates new file mode 100644 index 0000000000..4bfadecd87 --- /dev/null +++ b/changes/26618-software-vuln-detected-dates @@ -0,0 +1 @@ +- Fixed an issue where the detected date for software vulnerabilities was not being pulled correctly from the database. diff --git a/server/datastore/mysql/vulnerabilities.go b/server/datastore/mysql/vulnerabilities.go index 2b4477e370..2dab5e127b 100644 --- a/server/datastore/mysql/vulnerabilities.go +++ b/server/datastore/mysql/vulnerabilities.go @@ -20,7 +20,7 @@ func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint eeSelectStmt := ` SELECT DISTINCT cm.cve, - COALESCE(LEAST(osv.created_at, sc.created_at), NOW()) AS created_at, + LEAST(COALESCE(osv.created_at, NOW()), COALESCE(sc.created_at, NOW())) AS created_at, COALESCE(osv.source, sc.source, 0) AS source, cm.cvss_score, cm.epss_probability, @@ -34,9 +34,9 @@ func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint SELECT cve FROM software_cve WHERE cve = ? - + UNION - + SELECT cve FROM operating_system_vulnerabilities WHERE cve = ? @@ -49,7 +49,7 @@ func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint freeSelectStmt := ` SELECT DISTINCT union_cve.cve, - COALESCE(LEAST(osv.created_at, sc.created_at), NOW()) AS created_at, + LEAST(COALESCE(osv.created_at, NOW()), COALESCE(sc.created_at, NOW())) AS created_at, COALESCE(osv.source, sc.source, 0) AS source, COALESCE(vhc.host_count, 0) as hosts_count, COALESCE(vhc.updated_at, NOW()) as hosts_count_updated_at @@ -57,9 +57,9 @@ func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint SELECT cve, created_at, source FROM operating_system_vulnerabilities WHERE cve = ? - + UNION - + SELECT cve, created_at, source FROM software_cve WHERE cve = ? diff --git a/server/datastore/mysql/vulnerabilities_test.go b/server/datastore/mysql/vulnerabilities_test.go index 47aafc1fc0..a2452f4fc8 100644 --- a/server/datastore/mysql/vulnerabilities_test.go +++ b/server/datastore/mysql/vulnerabilities_test.go @@ -10,6 +10,7 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/test" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -216,14 +217,22 @@ func testVulnerabilityWithOS(t *testing.T, ds *Datastore) { }, HostsCount: 10, Source: fleet.MSRCSource, + CreatedAt: mockTime, } + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + // Mock the time to make it easier to check + _, err := q.ExecContext(ctx, "UPDATE operating_system_vulnerabilities SET created_at = ? WHERE cve = ?", mockTime, expected.CVE.CVE) + return err + }) + // No CVSSScores v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, false) require.NoError(t, err) require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) // Team 1 expected.HostsCount = 4 @@ -232,6 +241,7 @@ func testVulnerabilityWithOS(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) // No Team expected.HostsCount = 6 @@ -240,6 +250,7 @@ func testVulnerabilityWithOS(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) expected = fleet.VulnerabilityWithMetadata{ CVE: fleet.CVE{ @@ -252,6 +263,7 @@ func testVulnerabilityWithOS(t *testing.T, ds *Datastore) { }, HostsCount: 10, Source: fleet.MSRCSource, + CreatedAt: mockTime, } // With CVSSScores @@ -260,6 +272,7 @@ func testVulnerabilityWithOS(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) } func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { @@ -312,14 +325,22 @@ func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { }, HostsCount: 10, Source: fleet.NVDSource, + CreatedAt: mockTime, } + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + // Mock the time to make it easier to check + _, err := q.ExecContext(ctx, "UPDATE software_cve SET created_at = ? WHERE cve = ?", mockTime, expected.CVE.CVE) + return err + }) + // Global (all teams) v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, false) require.NoError(t, err) require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) // Team 1 expected.HostsCount = 4 @@ -328,6 +349,7 @@ func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) // No Team expected.HostsCount = 6 @@ -336,6 +358,7 @@ func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) // With CVSSScores expected = fleet.VulnerabilityWithMetadata{ @@ -349,6 +372,7 @@ func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { }, HostsCount: 10, Source: fleet.NVDSource, + CreatedAt: mockTime, } v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, true) @@ -356,6 +380,7 @@ func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) { require.Equal(t, expected.CVE, v.CVE) require.Equal(t, expected.HostsCount, v.HostsCount) require.Equal(t, expected.Source, v.Source) + require.Equal(t, expected.CreatedAt, v.CreatedAt) } func testVulnerabilitiesPagination(t *testing.T, ds *Datastore) {