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:
Juan Fernandez 2023-03-28 16:11:31 -04:00 committed by GitHub
parent 8a1a700383
commit aecc2fed75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 306 additions and 22 deletions

View 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).

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}
})
}

View File

@ -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 {

View File

@ -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

View File

@ -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))
}

View File

@ -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{}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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.

View File

@ -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 {

View File

@ -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 {