mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Feature 9834: Add published date to vulnerability object (#10434)
This only applies to Premium users, we want to show the vulnerabilities' published date anywhere vulnerabilities are shown including API endpoints and third party integrations.
This commit is contained in:
parent
8a1a700383
commit
aecc2fed75
2
changes/9834-add-published-date-to-vulns
Normal file
2
changes/9834-add-published-date-to-vulns
Normal file
@ -0,0 +1,2 @@
|
||||
- Include the published date from NVD in the vulnerability object in the API and the vulnerability
|
||||
webhooks (premium feature only).
|
@ -354,6 +354,7 @@ func TestScanVulnerabilities(t *testing.T) {
|
||||
expected := `
|
||||
{
|
||||
"cve": "CVE-2022-39348",
|
||||
"cve_published": "2022-10-26T14:15:00Z",
|
||||
"details_link": "https://nvd.nist.gov/vuln/detail/CVE-2022-39348",
|
||||
"epss_probability": 0.0089,
|
||||
"cvss_score": 5.4,
|
||||
|
@ -38,6 +38,7 @@ POST https://server.com/example
|
||||
"epss_probability": 0.7, // Premium feature only
|
||||
"cvss_score": 5.7, // Premium feature only
|
||||
"cisa_known_exploit": true, // Premium feature only
|
||||
"cve_published": "2020-10-28T00:00:00Z", // Premium feature only
|
||||
"hosts_affected": [
|
||||
{
|
||||
"id": 1,
|
||||
|
@ -5043,7 +5043,7 @@ Deletes the session specified by ID. When the user associated with the session n
|
||||
| ----------------------- | ------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| page | integer | query | Page number of the results to fetch. |
|
||||
| per_page | integer | query | Results per page. |
|
||||
| order_key | string | query | What to order results by. Allowed fields are `name`, `hosts_count`, `cvss_score`, `epss_probability` and `cisa_known_exploit`. Default is `hosts_count` (descending). |
|
||||
| order_key | string | query | What to order results by. Allowed fields are `name`, `hosts_count`, `cve_published`, `cvss_score`, `epss_probability` and `cisa_known_exploit`. Default is `hosts_count` (descending). |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name`, `version`, and `cve`. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
|
||||
@ -5076,7 +5076,8 @@ Deletes the session specified by ID. When the user associated with the session n
|
||||
"details_link": "https://nvd.nist.gov/vuln/detail/CVE-2009-5155",
|
||||
"cvss_score": 7.5,
|
||||
"epss_probability": 0.01537,
|
||||
"cisa_known_exploit": false
|
||||
"cisa_known_exploit": false,
|
||||
"cve_published": "2022-01-01 12:32:00"
|
||||
}
|
||||
],
|
||||
"hosts_count": 1
|
||||
|
@ -29,5 +29,6 @@ func (m *Mapper) GetPayload(
|
||||
r.EPSSProbability = meta.EPSSProbability
|
||||
r.CVSSScore = meta.CVSSScore
|
||||
r.CISAKnownExploit = meta.CISAKnownExploit
|
||||
r.CVEPublished = meta.Published
|
||||
return r
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package webhooks
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
@ -13,6 +14,7 @@ func TestGetPayload(t *testing.T) {
|
||||
serverURL, err := url.Parse("http://mywebsite.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
vuln := fleet.SoftwareVulnerability{
|
||||
CVE: "cve-1",
|
||||
SoftwareID: 1,
|
||||
@ -22,6 +24,7 @@ func TestGetPayload(t *testing.T) {
|
||||
CVSSScore: ptr.Float64(1),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(now),
|
||||
}
|
||||
|
||||
sut := Mapper{}
|
||||
@ -30,4 +33,6 @@ func TestGetPayload(t *testing.T) {
|
||||
require.Equal(t, *meta.CISAKnownExploit, *result.CISAKnownExploit)
|
||||
require.Equal(t, *meta.EPSSProbability, *result.EPSSProbability)
|
||||
require.Equal(t, *meta.CVSSScore, *result.CVSSScore)
|
||||
require.NotNil(t, result.CVEPublished)
|
||||
require.Equal(t, *meta.Published, *result.CVEPublished)
|
||||
}
|
||||
|
@ -402,6 +402,7 @@ func listSoftwareDB(
|
||||
cve.CVSSScore = &result.CVSSScore
|
||||
cve.EPSSProbability = &result.EPSSProbability
|
||||
cve.CISAKnownExploit = &result.CISAKnownExploit
|
||||
cve.CVEPublished = &result.CVEPublished
|
||||
}
|
||||
softwares[idx].Vulnerabilities = append(softwares[idx].Vulnerabilities, cve)
|
||||
}
|
||||
@ -413,10 +414,11 @@ func listSoftwareDB(
|
||||
// softwareCVE is used for left joins with cve
|
||||
type softwareCVE struct {
|
||||
fleet.Software
|
||||
CVE *string `db:"cve"`
|
||||
CVSSScore *float64 `db:"cvss_score"`
|
||||
EPSSProbability *float64 `db:"epss_probability"`
|
||||
CISAKnownExploit *bool `db:"cisa_known_exploit"`
|
||||
CVE *string `db:"cve"`
|
||||
CVSSScore *float64 `db:"cvss_score"`
|
||||
EPSSProbability *float64 `db:"epss_probability"`
|
||||
CISAKnownExploit *bool `db:"cisa_known_exploit"`
|
||||
CVEPublished *time.Time `db:"cve_published"`
|
||||
}
|
||||
|
||||
func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, error) {
|
||||
@ -509,6 +511,7 @@ func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, e
|
||||
goqu.MAX("c.cvss_score").As("cvss_score"), // for ordering
|
||||
goqu.MAX("c.epss_probability").As("epss_probability"), // for ordering
|
||||
goqu.MAX("c.cisa_known_exploit").As("cisa_known_exploit"), // for ordering
|
||||
goqu.MAX("c.published").As("cve_published"), // for ordering
|
||||
)
|
||||
}
|
||||
|
||||
@ -576,6 +579,7 @@ func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, e
|
||||
"c.cvss_score",
|
||||
"c.epss_probability",
|
||||
"c.cisa_known_exploit",
|
||||
goqu.I("c.published").As("cve_published"),
|
||||
)
|
||||
}
|
||||
|
||||
@ -812,6 +816,7 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, includeCVEScores
|
||||
"c.cvss_score",
|
||||
"c.epss_probability",
|
||||
"c.cisa_known_exploit",
|
||||
goqu.I("c.published").As("cve_published"),
|
||||
)
|
||||
}
|
||||
|
||||
@ -852,6 +857,7 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, includeCVEScores
|
||||
cve.CVSSScore = &result.CVSSScore
|
||||
cve.EPSSProbability = &result.EPSSProbability
|
||||
cve.CISAKnownExploit = &result.CISAKnownExploit
|
||||
cve.CVEPublished = &result.CVEPublished
|
||||
}
|
||||
software.Vulnerabilities = append(software.Vulnerabilities, cve)
|
||||
}
|
||||
|
@ -43,7 +43,8 @@ func TestSoftware(t *testing.T) {
|
||||
{"InsertSoftwareVulnerabilities", testInsertSoftwareVulnerabilities},
|
||||
{"ListCVEs", testListCVEs},
|
||||
{"ListSoftwareForVulnDetection", testListSoftwareForVulnDetection},
|
||||
{"SoftwareByID", testSoftwareByID},
|
||||
{"SoftwareByIDNoDuplicatedVulns", testSoftwareByIDNoDuplicatedVulns},
|
||||
{"SoftwareByIDIncludesCVEPublishedDate", testSoftwareByIDIncludesCVEPublishedDate},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@ -518,24 +519,28 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
|
||||
_, err := ds.InsertSoftwareVulnerabilities(context.Background(), vulns, fleet.NVDSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
cveMeta := []fleet.CVEMeta{
|
||||
{
|
||||
CVE: "CVE-2022-0001",
|
||||
CVSSScore: ptr.Float64(2.0),
|
||||
EPSSProbability: ptr.Float64(0.01),
|
||||
CISAKnownExploit: ptr.Bool(false),
|
||||
Published: ptr.Time(now.Add(-2 * time.Hour)),
|
||||
},
|
||||
{
|
||||
CVE: "CVE-2022-0002",
|
||||
CVSSScore: ptr.Float64(1.0),
|
||||
EPSSProbability: ptr.Float64(0.99),
|
||||
CISAKnownExploit: ptr.Bool(false),
|
||||
Published: ptr.Time(now),
|
||||
},
|
||||
{
|
||||
CVE: "CVE-2022-0003",
|
||||
CVSSScore: ptr.Float64(3.0),
|
||||
EPSSProbability: ptr.Float64(0.98),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(now.Add(-1 * time.Hour)),
|
||||
},
|
||||
}
|
||||
err = ds.InsertCVEMeta(context.Background(), cveMeta)
|
||||
@ -553,6 +558,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
|
||||
CVSSScore: ptr.Float64Ptr(2.0),
|
||||
EPSSProbability: ptr.Float64Ptr(0.01),
|
||||
CISAKnownExploit: ptr.BoolPtr(false),
|
||||
CVEPublished: ptr.TimePtr(now.Add(-2 * time.Hour)),
|
||||
},
|
||||
{
|
||||
CVE: "CVE-2022-0002",
|
||||
@ -560,6 +566,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
|
||||
CVSSScore: ptr.Float64Ptr(1.0),
|
||||
EPSSProbability: ptr.Float64Ptr(0.99),
|
||||
CISAKnownExploit: ptr.BoolPtr(false),
|
||||
CVEPublished: ptr.TimePtr(now),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -578,6 +585,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
|
||||
CVSSScore: ptr.Float64Ptr(3.0),
|
||||
EPSSProbability: ptr.Float64Ptr(0.98),
|
||||
CISAKnownExploit: ptr.BoolPtr(true),
|
||||
CVEPublished: ptr.TimePtr(now.Add(-1 * time.Hour)),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -789,6 +797,20 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
|
||||
assert.Equal(t, baz001.Version, software[0].Version)
|
||||
})
|
||||
|
||||
t.Run("order by cve_published", func(t *testing.T) {
|
||||
opts := fleet.SoftwareListOptions{
|
||||
ListOptions: fleet.ListOptions{
|
||||
OrderKey: "cve_published",
|
||||
OrderDirection: fleet.OrderDescending,
|
||||
},
|
||||
IncludeCVEScores: true,
|
||||
}
|
||||
|
||||
software := listSoftwareCheckCount(t, ds, 5, 5, opts, false)
|
||||
assert.Equal(t, foo001.Name, software[0].Name)
|
||||
assert.Equal(t, foo001.Version, software[0].Version)
|
||||
})
|
||||
|
||||
t.Run("nil cve scores if IncludeCVEScores is false", func(t *testing.T) {
|
||||
opts := fleet.SoftwareListOptions{
|
||||
ListOptions: fleet.ListOptions{
|
||||
@ -1662,10 +1684,9 @@ func testListSoftwareForVulnDetection(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
}
|
||||
|
||||
func testSoftwareByID(t *testing.T, ds *Datastore) {
|
||||
func testSoftwareByIDNoDuplicatedVulns(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"
|
||||
require.NoError(t, ds.UpdateHost(ctx, hostA))
|
||||
@ -1706,3 +1727,131 @@ func testSoftwareByID(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testSoftwareByIDIncludesCVEPublishedDate(t *testing.T, ds *Datastore) {
|
||||
t.Run("software.vulnerabilities includes the published date", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
host := test.NewHost(t, ds, "hostA", "", "hostAkey", "hostAuuid", time.Now())
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
hasVuln bool
|
||||
hasMeta bool
|
||||
hasPublishedDate bool
|
||||
}{
|
||||
{"foo_123", true, true, true},
|
||||
{"bar_123", true, true, false},
|
||||
{"foo_456", true, false, false},
|
||||
{"bar_456", false, true, true},
|
||||
{"foo_789", false, true, false},
|
||||
{"bar_789", false, false, false},
|
||||
}
|
||||
|
||||
// Add software
|
||||
var software []fleet.Software
|
||||
for _, t := range testCases {
|
||||
software = append(software, fleet.Software{
|
||||
Name: t.name,
|
||||
Version: "0.0.1",
|
||||
Source: "apps",
|
||||
})
|
||||
}
|
||||
require.NoError(t, ds.UpdateHostSoftware(ctx, host.ID, software))
|
||||
require.NoError(t, ds.LoadHostSoftware(ctx, host, false))
|
||||
|
||||
// Add vulnerabilities and CVEMeta
|
||||
var vulns []fleet.SoftwareVulnerability
|
||||
var meta []fleet.CVEMeta
|
||||
for _, tC := range testCases {
|
||||
idx := -1
|
||||
for i, s := range host.Software {
|
||||
if s.Name == tC.name {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotEqual(t, -1, idx, "software not found")
|
||||
|
||||
if tC.hasVuln {
|
||||
vulns = append(vulns, fleet.SoftwareVulnerability{
|
||||
SoftwareID: host.Software[idx].ID,
|
||||
CVE: fmt.Sprintf("cve-%s", tC.name),
|
||||
})
|
||||
}
|
||||
|
||||
if tC.hasMeta {
|
||||
var published *time.Time
|
||||
if tC.hasPublishedDate {
|
||||
published = &now
|
||||
}
|
||||
|
||||
meta = append(meta, fleet.CVEMeta{
|
||||
CVE: fmt.Sprintf("cve-%s", tC.name),
|
||||
CVSSScore: ptr.Float64(5.4),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: published,
|
||||
})
|
||||
}
|
||||
}
|
||||
n, err := ds.InsertSoftwareVulnerabilities(ctx, vulns, fleet.UbuntuOVALSource)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, int(n))
|
||||
require.NoError(t, ds.InsertCVEMeta(ctx, meta))
|
||||
|
||||
for _, tC := range testCases {
|
||||
idx := -1
|
||||
for i, s := range host.Software {
|
||||
if s.Name == tC.name {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotEqual(t, -1, idx, "software not found")
|
||||
|
||||
// Test that scores are not included if includeCVEScores = false
|
||||
withoutScores, err := ds.SoftwareByID(ctx, host.Software[idx].ID, false)
|
||||
require.NoError(t, err)
|
||||
if tC.hasVuln {
|
||||
require.Len(t, withoutScores.Vulnerabilities, 1)
|
||||
require.Equal(t, fmt.Sprintf("cve-%s", tC.name), withoutScores.Vulnerabilities[0].CVE)
|
||||
|
||||
require.Nil(t, withoutScores.Vulnerabilities[0].CVSSScore)
|
||||
require.Nil(t, withoutScores.Vulnerabilities[0].EPSSProbability)
|
||||
require.Nil(t, withoutScores.Vulnerabilities[0].CISAKnownExploit)
|
||||
} else {
|
||||
require.Empty(t, withoutScores.Vulnerabilities)
|
||||
}
|
||||
|
||||
withScores, err := ds.SoftwareByID(ctx, host.Software[idx].ID, true)
|
||||
require.NoError(t, err)
|
||||
if tC.hasVuln {
|
||||
require.Len(t, withScores.Vulnerabilities, 1)
|
||||
require.Equal(t, fmt.Sprintf("cve-%s", tC.name), withoutScores.Vulnerabilities[0].CVE)
|
||||
|
||||
if tC.hasMeta {
|
||||
require.NotNil(t, withScores.Vulnerabilities[0].CVSSScore)
|
||||
require.NotNil(t, *withScores.Vulnerabilities[0].CVSSScore)
|
||||
require.Equal(t, **withScores.Vulnerabilities[0].CVSSScore, 5.4)
|
||||
|
||||
require.NotNil(t, withScores.Vulnerabilities[0].EPSSProbability)
|
||||
require.NotNil(t, *withScores.Vulnerabilities[0].EPSSProbability)
|
||||
require.Equal(t, **withScores.Vulnerabilities[0].EPSSProbability, 0.5)
|
||||
|
||||
require.NotNil(t, withScores.Vulnerabilities[0].CISAKnownExploit)
|
||||
require.NotNil(t, *withScores.Vulnerabilities[0].CISAKnownExploit)
|
||||
require.Equal(t, **withScores.Vulnerabilities[0].CISAKnownExploit, true)
|
||||
|
||||
if tC.hasPublishedDate {
|
||||
require.NotNil(t, withScores.Vulnerabilities[0].CVEPublished)
|
||||
require.NotNil(t, *withScores.Vulnerabilities[0].CVEPublished)
|
||||
require.Equal(t, (**withScores.Vulnerabilities[0].CVEPublished), now)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
require.Empty(t, withoutScores.Vulnerabilities)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ type CVE struct {
|
||||
// 1. omitted when using the free tier
|
||||
// 2. null when using the premium tier, but there is no value available. This may be due to an issue with syncing cve scores.
|
||||
// 3. non-null when using the premium tier, and value is available.
|
||||
CVSSScore **float64 `json:"cvss_score,omitempty" db:"cvss_score"`
|
||||
EPSSProbability **float64 `json:"epss_probability,omitempty" db:"epss_probability"`
|
||||
CISAKnownExploit **bool `json:"cisa_known_exploit,omitempty" db:"cisa_known_exploit"`
|
||||
CVSSScore **float64 `json:"cvss_score,omitempty" db:"cvss_score"`
|
||||
EPSSProbability **float64 `json:"epss_probability,omitempty" db:"epss_probability"`
|
||||
CISAKnownExploit **bool `json:"cisa_known_exploit,omitempty" db:"cisa_known_exploit"`
|
||||
CVEPublished **time.Time `json:"cve_published,omitempty" db:"cve_published"`
|
||||
}
|
||||
|
||||
type CVEMeta struct {
|
||||
|
@ -37,6 +37,12 @@ func Time(x time.Time) *time.Time {
|
||||
return &x
|
||||
}
|
||||
|
||||
// TimePtr returns a *time.Time Pointer (**time.Time) for the provided time.
|
||||
func TimePtr(x time.Time) **time.Time {
|
||||
t := Time(x)
|
||||
return &t
|
||||
}
|
||||
|
||||
// RawMessage returns a pointer to the provided json.RawMessage.
|
||||
func RawMessage(x json.RawMessage) *json.RawMessage {
|
||||
return &x
|
||||
|
@ -2434,3 +2434,77 @@ func createHostAndDeviceToken(t *testing.T, ds *mysql.Datastore, token string) *
|
||||
})
|
||||
return host
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestListSoftware() {
|
||||
t := s.T()
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
ctx := context.Background()
|
||||
|
||||
host, err := s.ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: ptr.String(t.Name() + "1"),
|
||||
UUID: t.Name() + "1",
|
||||
Hostname: t.Name() + "foo.local",
|
||||
PrimaryIP: "192.168.1.1",
|
||||
PrimaryMac: "30-65-EC-6F-C4-58",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
software := []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.1", Source: "chrome_extensions"},
|
||||
{Name: "bar", Version: "0.0.3", Source: "apps"},
|
||||
}
|
||||
require.NoError(t, s.ds.UpdateHostSoftware(ctx, host.ID, software))
|
||||
require.NoError(t, s.ds.LoadHostSoftware(ctx, host, false))
|
||||
|
||||
bar := host.Software[0]
|
||||
if bar.Name != "bar" {
|
||||
bar = host.Software[1]
|
||||
}
|
||||
|
||||
n, err := s.ds.InsertSoftwareVulnerabilities(
|
||||
ctx, []fleet.SoftwareVulnerability{
|
||||
{
|
||||
SoftwareID: bar.ID,
|
||||
CVE: "cve-123",
|
||||
},
|
||||
}, fleet.NVDSource,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, int(n))
|
||||
|
||||
require.NoError(t, s.ds.InsertCVEMeta(ctx, []fleet.CVEMeta{{
|
||||
CVE: "cve-123",
|
||||
CVSSScore: ptr.Float64(5.4),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: &now,
|
||||
}}))
|
||||
|
||||
require.NoError(t, s.ds.SyncHostsSoftware(ctx, time.Now().UTC()))
|
||||
|
||||
var resp listSoftwareResponse
|
||||
s.DoJSON("GET", "/api/latest/fleet/software", nil, http.StatusOK, &resp)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
barPayload := resp.Software[0]
|
||||
if barPayload.Name != "bar" {
|
||||
barPayload = resp.Software[1]
|
||||
}
|
||||
|
||||
fooPayload := resp.Software[1]
|
||||
if barPayload.Name != "bar" {
|
||||
barPayload = resp.Software[0]
|
||||
}
|
||||
|
||||
require.Empty(t, fooPayload.Vulnerabilities)
|
||||
require.Len(t, barPayload.Vulnerabilities, 1)
|
||||
require.Equal(t, barPayload.Vulnerabilities[0].CVE, "cve-123")
|
||||
require.NotNil(t, barPayload.Vulnerabilities[0].CVSSScore, ptr.Float64Ptr(5.4))
|
||||
require.NotNil(t, barPayload.Vulnerabilities[0].EPSSProbability, ptr.Float64Ptr(0.5))
|
||||
require.NotNil(t, barPayload.Vulnerabilities[0].CISAKnownExploit, ptr.BoolPtr(true))
|
||||
require.Equal(t, barPayload.Vulnerabilities[0].CVEPublished, ptr.TimePtr(now))
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
@ -23,12 +24,14 @@ type hostPayloadPart struct {
|
||||
}
|
||||
|
||||
type WebhookPayload struct {
|
||||
CVE string `json:"cve"`
|
||||
Link string `json:"details_link"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"` // Premium feature only
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"` // Premium feature only
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"` // Premium feature only
|
||||
Hosts []*hostPayloadPart `json:"hosts_affected"`
|
||||
CVE string `json:"cve"`
|
||||
Link string `json:"details_link"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"` // Premium feature only
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"` // Premium feature only
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"` // Premium feature only
|
||||
CVEPublished *time.Time `json:"cve_published,omitempty"` // Premium feature only
|
||||
|
||||
Hosts []*hostPayloadPart `json:"hosts_affected"`
|
||||
}
|
||||
|
||||
type Mapper struct{}
|
||||
|
@ -3,6 +3,7 @@ package webhooks
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
@ -13,6 +14,7 @@ func TestGetPaylaod(t *testing.T) {
|
||||
serverURL, err := url.Parse("http://mywebsite.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
vuln := fleet.SoftwareVulnerability{
|
||||
CVE: "cve-1",
|
||||
SoftwareID: 1,
|
||||
@ -22,6 +24,7 @@ func TestGetPaylaod(t *testing.T) {
|
||||
CVSSScore: ptr.Float64(1),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(now),
|
||||
}
|
||||
|
||||
sut := Mapper{}
|
||||
@ -30,4 +33,5 @@ func TestGetPaylaod(t *testing.T) {
|
||||
require.Empty(t, result.CISAKnownExploit)
|
||||
require.Empty(t, result.EPSSProbability)
|
||||
require.Empty(t, result.CVSSScore)
|
||||
require.Empty(t, result.CVEPublished)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
jira "github.com/andygrunwald/go-jira"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
@ -46,6 +47,8 @@ var jiraTemplates = struct {
|
||||
{{ end }}
|
||||
{{ if .CVSSScore }}CVSS score (reported by [NVD|https://nvd.nist.gov/]): {{ .CVSSScore }}
|
||||
{{ end }}
|
||||
{{ if .CVEPublished }}Published (reported by [NVD|https://nvd.nist.gov/]): {{ .CVEPublished }}
|
||||
{{ end }}
|
||||
{{ if .CISAKnownExploit }}Known exploits (reported by [CISA|https://www.cisa.gov/known-exploited-vulnerabilities-catalog]): {{ if deref .CISAKnownExploit }}Yes{{ else }}No{{ end }}
|
||||
\\
|
||||
{{ end }}{{ end }}
|
||||
@ -101,6 +104,7 @@ type jiraVulnTplArgs struct {
|
||||
EPSSProbability *float64
|
||||
CVSSScore *float64
|
||||
CISAKnownExploit *bool
|
||||
CVEPublished *time.Time
|
||||
}
|
||||
|
||||
// JiraClient defines the method required for the client that makes API calls
|
||||
@ -281,6 +285,7 @@ func (j *Jira) runVuln(ctx context.Context, cli JiraClient, args jiraArgs) error
|
||||
EPSSProbability: vargs.EPSSProbability,
|
||||
CVSSScore: vargs.CVSSScore,
|
||||
CISAKnownExploit: vargs.CISAKnownExploit,
|
||||
CVEPublished: vargs.CVEPublished,
|
||||
}
|
||||
|
||||
createdIssue, err := j.createTemplatedIssue(ctx, cli, jiraTemplates.VulnSummary, jiraTemplates.VulnDescription, tplArgs)
|
||||
@ -380,6 +385,7 @@ func QueueJiraVulnJobs(
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
args.CISAKnownExploit = meta.CISAKnownExploit
|
||||
args.CVEPublished = meta.Published
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, jiraName, jiraArgs{Vulnerability: &args})
|
||||
if err != nil {
|
||||
|
@ -172,6 +172,14 @@ func TestJiraRun(t *testing.T) {
|
||||
"Probability of exploit",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"vuln with published date",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","cve_published":"2012-04-23T18:25:43.511Z","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Published (reported by [NVD|https://nvd.nist.gov/]): 2012-04-23",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@ -47,10 +48,11 @@ type failingPolicyArgs struct {
|
||||
// vulnArgs are the args common to all integrations that can process
|
||||
// vulnerabilities.
|
||||
type vulnArgs struct {
|
||||
CVE string `json:"cve,omitempty"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"` // Premium feature only
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"` // Premium feature only
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"` // Premium feature only
|
||||
CVE string `json:"cve,omitempty"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"` // Premium feature only
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"` // Premium feature only
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"` // Premium feature only
|
||||
CVEPublished *time.Time `json:"cve_published,omitempty"` // Premium feature only
|
||||
}
|
||||
|
||||
// Worker runs jobs. NOT SAFE FOR CONCURRENT USE.
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
@ -47,6 +48,8 @@ Probability of exploit (reported by [FIRST.org/epss](https://www.first.org/epss/
|
||||
{{ end }}
|
||||
{{ if .CVSSScore }}CVSS score (reported by [NVD](https://nvd.nist.gov/)): {{ .CVSSScore }}
|
||||
{{ end }}
|
||||
{{ if .CVEPublished }}Published (reported by [NVD|https://nvd.nist.gov/]): {{ .CVEPublished }}
|
||||
{{ end }}
|
||||
{{ if .CISAKnownExploit }}Known exploits (reported by [CISA](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)): {{ if deref .CISAKnownExploit }}Yes{{ else }}No{{ end }}
|
||||
|
||||
{{ end }}{{ end }}
|
||||
@ -102,6 +105,7 @@ type zendeskVulnTplArgs struct {
|
||||
EPSSProbability *float64
|
||||
CVSSScore *float64
|
||||
CISAKnownExploit *bool
|
||||
CVEPublished *time.Time
|
||||
}
|
||||
|
||||
// ZendeskClient defines the method required for the client that makes API calls
|
||||
@ -283,6 +287,7 @@ func (z *Zendesk) runVuln(ctx context.Context, cli ZendeskClient, args zendeskAr
|
||||
EPSSProbability: vargs.EPSSProbability,
|
||||
CVSSScore: vargs.CVSSScore,
|
||||
CISAKnownExploit: vargs.CISAKnownExploit,
|
||||
CVEPublished: vargs.CVEPublished,
|
||||
}
|
||||
|
||||
createdTicket, err := z.createTemplatedTicket(ctx, cli, zendeskTemplates.VulnSummary, zendeskTemplates.VulnDescription, tplArgs)
|
||||
@ -375,6 +380,7 @@ func QueueZendeskVulnJobs(
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
args.CISAKnownExploit = meta.CISAKnownExploit
|
||||
args.CVEPublished = meta.Published
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, zendeskName, zendeskArgs{Vulnerability: &args})
|
||||
if err != nil {
|
||||
|
@ -157,6 +157,14 @@ func TestZendeskRun(t *testing.T) {
|
||||
"Probability of exploit",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"vuln with published date",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","cve_published":"2012-04-23T18:25:43.511Z","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Published (reported by [NVD|https://nvd.nist.gov/]): 2012-04-23",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
Loading…
Reference in New Issue
Block a user