mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Add vulnerability scores to Jira/Zendesk tickets for premium users (#8346)
This commit is contained in:
parent
acfd154150
commit
49e910270b
11 changed files with 435 additions and 86 deletions
1
changes/issue-7675-add-vuln-scores-to-integrations
Normal file
1
changes/issue-7675-add-vuln-scores-to-integrations
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Added vulnerability scores to Jira and Zendesk integrations for Fleet Premium users.
|
||||
|
|
@ -202,6 +202,7 @@ func scanVulnerabilities(
|
|||
ds,
|
||||
kitlog.With(logger, "jira", "vulnerabilities"),
|
||||
vulns,
|
||||
meta,
|
||||
); err != nil {
|
||||
errHandler(ctx, logger, "queueing vulnerabilities to jira", err)
|
||||
}
|
||||
|
|
@ -213,6 +214,7 @@ func scanVulnerabilities(
|
|||
ds,
|
||||
kitlog.With(logger, "zendesk", "vulnerabilities"),
|
||||
vulns,
|
||||
meta,
|
||||
); err != nil {
|
||||
errHandler(ctx, logger, "queueing vulnerabilities to Zendesk", err)
|
||||
}
|
||||
|
|
@ -468,6 +470,7 @@ func startIntegrationsSchedule(
|
|||
instanceID string,
|
||||
ds fleet.Datastore,
|
||||
logger kitlog.Logger,
|
||||
license *fleet.LicenseInfo,
|
||||
) (*schedule.Schedule, error) {
|
||||
const (
|
||||
name = "integrations"
|
||||
|
|
@ -484,11 +487,13 @@ func startIntegrationsSchedule(
|
|||
Datastore: ds,
|
||||
Log: logger,
|
||||
NewClientFunc: newJiraClient,
|
||||
License: license,
|
||||
}
|
||||
zendesk := &worker.Zendesk{
|
||||
Datastore: ds,
|
||||
Log: logger,
|
||||
NewClientFunc: newZendeskClient,
|
||||
License: license,
|
||||
}
|
||||
// leave the url empty for now, will be filled when the lock is acquired with
|
||||
// the up-to-date config.
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ the way that the Fleet server works.
|
|||
if _, err := startAutomationsSchedule(ctx, instanceID, ds, logger, 5*time.Minute, failingPolicySet); err != nil {
|
||||
initFatal(err, "failed to register automations schedule")
|
||||
}
|
||||
if _, err := startIntegrationsSchedule(ctx, instanceID, ds, logger); err != nil {
|
||||
if _, err := startIntegrationsSchedule(ctx, instanceID, ds, logger, license); err != nil {
|
||||
initFatal(err, "failed to register integrations schedule")
|
||||
}
|
||||
if config.MDMApple.Enable {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ func TestJiraFailer(t *testing.T) {
|
|||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: fleet.TierFree},
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (JiraClient, error) {
|
||||
return failer, nil
|
||||
},
|
||||
|
|
@ -120,6 +121,7 @@ func TestZendeskFailer(t *testing.T) {
|
|||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: fleet.TierFree},
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (ZendeskClient, error) {
|
||||
return failer, nil
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,10 +30,25 @@ var jiraTemplates = struct {
|
|||
`Vulnerability {{ .CVE }} detected on {{ len .Hosts }} host(s)`,
|
||||
)),
|
||||
|
||||
// Jira uses wiki markup in the v2 api.
|
||||
VulnDescription: template.Must(template.New("").Parse(
|
||||
// Jira uses wiki markup in the v2 api. See
|
||||
// https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all
|
||||
// for some reference. The `\\` marks force a newline to have the desired spacing
|
||||
// around the scores, when present.
|
||||
VulnDescription: template.Must(template.New("").Funcs(template.FuncMap{
|
||||
// CISAKnownExploit is *bool, so any condition check on it in the template
|
||||
// will test if nil or not, and not its actual boolean value. Hence, "deref".
|
||||
"deref": func(b *bool) bool { return *b },
|
||||
}).Parse(
|
||||
`See vulnerability (CVE) details in National Vulnerability Database (NVD) here: [{{ .CVE }}|{{ .NVDURL }}{{ .CVE }}].
|
||||
|
||||
{{ if .IsPremium }}{{ if .EPSSProbability }}\\Probability of exploit (reported by [FIRST.org/epss|https://www.first.org/epss/]): {{ .EPSSProbability }}
|
||||
{{ end }}
|
||||
{{ if .CVSSScore }}CVSS score (reported by [NVD|https://nvd.nist.gov/]): {{ .CVSSScore }}
|
||||
{{ 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 }}
|
||||
|
||||
Affected hosts:
|
||||
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
|
|
@ -76,6 +91,13 @@ type jiraVulnTplArgs struct {
|
|||
FleetURL string
|
||||
CVE string
|
||||
Hosts []*fleet.HostShort
|
||||
|
||||
IsPremium bool
|
||||
|
||||
// the following fields are only included in the ticket for premium licenses.
|
||||
EPSSProbability *float64
|
||||
CVSSScore *float64
|
||||
CISAKnownExploit *bool
|
||||
}
|
||||
|
||||
type jiraFailingPoliciesTplArgs struct {
|
||||
|
|
@ -98,6 +120,7 @@ type Jira struct {
|
|||
FleetURL string
|
||||
Datastore fleet.Datastore
|
||||
Log kitlog.Logger
|
||||
License *fleet.LicenseInfo
|
||||
NewClientFunc func(*externalsvc.JiraOptions) (JiraClient, error)
|
||||
|
||||
// mu protects concurrent access to clientsCache, so that the job processor
|
||||
|
|
@ -199,7 +222,10 @@ func (j *Jira) getClient(ctx context.Context, args jiraArgs) (JiraClient, error)
|
|||
|
||||
// jiraArgs are the arguments for the Jira integration job.
|
||||
type jiraArgs struct {
|
||||
// CVE is deprecated but kept for backwards compatibility (there may be jobs
|
||||
// enqueued in that format to process).
|
||||
CVE string `json:"cve,omitempty"`
|
||||
Vulnerability *vulnArgs `json:"vulnerability,omitempty"`
|
||||
FailingPolicy *failingPolicyArgs `json:"failing_policy,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -239,16 +265,28 @@ func (j *Jira) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
|||
}
|
||||
|
||||
func (j *Jira) runVuln(ctx context.Context, cli JiraClient, args jiraArgs) error {
|
||||
hosts, err := j.Datastore.HostsByCVE(ctx, args.CVE)
|
||||
vargs := args.Vulnerability
|
||||
if vargs == nil {
|
||||
// support the old format of vulnerability args, where only the CVE
|
||||
// is provided.
|
||||
vargs = &vulnArgs{
|
||||
CVE: args.CVE,
|
||||
}
|
||||
}
|
||||
hosts, err := j.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "find hosts by cve")
|
||||
}
|
||||
|
||||
tplArgs := &jiraVulnTplArgs{
|
||||
NVDURL: nvdCVEURL,
|
||||
FleetURL: j.FleetURL,
|
||||
CVE: args.CVE,
|
||||
Hosts: hosts,
|
||||
NVDURL: nvdCVEURL,
|
||||
FleetURL: j.FleetURL,
|
||||
CVE: vargs.CVE,
|
||||
Hosts: hosts,
|
||||
IsPremium: j.License.IsPremium(),
|
||||
EPSSProbability: vargs.EPSSProbability,
|
||||
CVSSScore: vargs.CVSSScore,
|
||||
CISAKnownExploit: vargs.CISAKnownExploit,
|
||||
}
|
||||
|
||||
createdIssue, err := j.createTemplatedIssue(ctx, cli, jiraTemplates.VulnSummary, jiraTemplates.VulnDescription, tplArgs)
|
||||
|
|
@ -257,7 +295,7 @@ func (j *Jira) runVuln(ctx context.Context, cli JiraClient, args jiraArgs) error
|
|||
}
|
||||
level.Debug(j.Log).Log(
|
||||
"msg", "created jira issue for cve",
|
||||
"cve", args.CVE,
|
||||
"cve", vargs.CVE,
|
||||
"issue_id", createdIssue.ID,
|
||||
"issue_key", createdIssue.Key,
|
||||
)
|
||||
|
|
@ -324,7 +362,13 @@ func (j *Jira) createTemplatedIssue(ctx context.Context, cli JiraClient, summary
|
|||
|
||||
// QueueJiraVulnJobs queues the Jira vulnerability jobs to process asynchronously
|
||||
// via the worker.
|
||||
func QueueJiraVulnJobs(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, recentVulns []fleet.SoftwareVulnerability) error {
|
||||
func QueueJiraVulnJobs(
|
||||
ctx context.Context,
|
||||
ds fleet.Datastore,
|
||||
logger kitlog.Logger,
|
||||
recentVulns []fleet.SoftwareVulnerability,
|
||||
cveMeta map[string]fleet.CVEMeta,
|
||||
) error {
|
||||
level.Info(logger).Log("enabled", "true", "recentVulns", len(recentVulns))
|
||||
|
||||
// for troubleshooting, log in debug level the CVEs that we will process
|
||||
|
|
@ -343,7 +387,13 @@ func QueueJiraVulnJobs(ctx context.Context, ds fleet.Datastore, logger kitlog.Lo
|
|||
}
|
||||
|
||||
for cve := range uniqCVEs {
|
||||
job, err := QueueJob(ctx, ds, jiraName, jiraArgs{CVE: cve})
|
||||
args := vulnArgs{CVE: cve}
|
||||
if meta, ok := cveMeta[cve]; ok {
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
args.CISAKnownExploit = meta.CISAKnownExploit
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, jiraName, jiraArgs{Vulnerability: &args})
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queueing job")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
|
@ -73,6 +74,7 @@ func TestJiraRun(t *testing.T) {
|
|||
require.Contains(t, string(body), expectedDescription)
|
||||
}
|
||||
if expectedNotInDescription != "" {
|
||||
fmt.Println(string(body))
|
||||
require.NotContains(t, string(body), expectedNotInDescription)
|
||||
}
|
||||
|
||||
|
|
@ -96,37 +98,100 @@ func TestJiraRun(t *testing.T) {
|
|||
client, err := externalsvc.NewJiraClient(&externalsvc.JiraOptions{BaseURL: srv.URL})
|
||||
require.NoError(t, err)
|
||||
|
||||
jira := &Jira{
|
||||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (JiraClient, error) {
|
||||
return client, nil
|
||||
cases := []struct {
|
||||
desc string
|
||||
licenseTier string
|
||||
payload string
|
||||
expectedSummary string
|
||||
expectedDescription string
|
||||
expectedNotInDescription string
|
||||
}{
|
||||
{
|
||||
"old vuln format free",
|
||||
fleet.TierFree,
|
||||
`{"cve":"CVE-1234-5678"}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Affected hosts:",
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln free",
|
||||
fleet.TierFree,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678"}}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Affected hosts:",
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln with scores free",
|
||||
fleet.TierFree,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Affected hosts:",
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"failing global policy",
|
||||
fleet.TierFree,
|
||||
`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": []}}`,
|
||||
`"summary":"test-policy policy failed on 0 host(s)"`,
|
||||
"\\u0026policy_id=1\\u0026policy_response=failing",
|
||||
"\\u0026team_id=",
|
||||
},
|
||||
{
|
||||
"failing team policy",
|
||||
fleet.TierPremium,
|
||||
`{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "test-1"}, {"id": 2, "hostname": "test-2"}]}}`,
|
||||
`"summary":"test-policy-2 policy failed on 2 host(s)"`,
|
||||
"\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"old vuln format premium",
|
||||
fleet.TierPremium,
|
||||
`{"cve":"CVE-1234-5678"}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Affected hosts:",
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln premium",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678"}}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Affected hosts:",
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln with scores premium",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Probability of exploit",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("vuln", func(t *testing.T) {
|
||||
expectedSummary = `"summary":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`
|
||||
expectedDescription, expectedNotInDescription = "", ""
|
||||
err = jira.Run(context.Background(), json.RawMessage(`{"cve":"CVE-1234-5678"}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
jira := &Jira{
|
||||
FleetURL: "https://fleetdm.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: c.licenseTier},
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (JiraClient, error) {
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("failing global policy", func(t *testing.T) {
|
||||
expectedSummary = `"summary":"test-policy policy failed on 0 host(s)"`
|
||||
expectedDescription = "\\u0026policy_id=1\\u0026policy_response=failing" // ampersand gets rendered as \u0026 in json string
|
||||
expectedNotInDescription = "\\u0026team_id="
|
||||
err = jira.Run(context.Background(), json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": []}}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
expectedSummary = c.expectedSummary
|
||||
expectedDescription = c.expectedDescription
|
||||
expectedNotInDescription = c.expectedNotInDescription
|
||||
err = jira.Run(context.Background(), json.RawMessage(c.payload))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("failing team policy", func(t *testing.T) {
|
||||
expectedSummary = `"summary":"test-policy-2 policy failed on 2 host(s)"`
|
||||
expectedDescription = "\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing" // ampersand gets rendered as \u0026 in json string
|
||||
expectedNotInDescription = ""
|
||||
err = jira.Run(context.Background(), json.RawMessage(`{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "test-1"}, {"id": 2, "hostname": "test-2"}]}}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestJiraQueueVulnJobs(t *testing.T) {
|
||||
|
|
@ -153,8 +218,12 @@ func TestJiraQueueVulnJobs(t *testing.T) {
|
|||
CVE: "CVE-1234-5678",
|
||||
SoftwareID: 3,
|
||||
}}
|
||||
meta := make(map[string]fleet.CVEMeta, len(vulns))
|
||||
for _, v := range vulns {
|
||||
meta[v.CVE] = fleet.CVEMeta{CVE: v.CVE}
|
||||
}
|
||||
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, vulns)
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, vulns, meta)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
require.Equal(t, 1, count)
|
||||
|
|
@ -164,7 +233,11 @@ func TestJiraQueueVulnJobs(t *testing.T) {
|
|||
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
|
||||
return job, nil
|
||||
}
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: "CVE-1234-5678"}})
|
||||
theCVE := "CVE-1234-5678"
|
||||
meta := map[string]fleet.CVEMeta{
|
||||
theCVE: {CVE: theCVE},
|
||||
}
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: theCVE}}, meta)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
})
|
||||
|
|
@ -173,7 +246,11 @@ func TestJiraQueueVulnJobs(t *testing.T) {
|
|||
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: "CVE-1234-5678"}})
|
||||
theCVE := "CVE-1234-5678"
|
||||
meta := map[string]fleet.CVEMeta{
|
||||
theCVE: {CVE: theCVE},
|
||||
}
|
||||
err := QueueJiraVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: theCVE}}, meta)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, io.EOF)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
|
|
@ -304,6 +381,7 @@ func TestJiraRunClientUpdate(t *testing.T) {
|
|||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: fleet.TierFree},
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (JiraClient, error) {
|
||||
// keep track of project keys received in calls to NewClientFunc
|
||||
projectKeys = append(projectKeys, opts.ProjectKey)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ type failingPolicyArgs struct {
|
|||
TeamID *uint `json:"team_id,omitempty"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Worker runs jobs. NOT SAFE FOR CONCURRENT USE.
|
||||
type Worker struct {
|
||||
ds fleet.Datastore
|
||||
|
|
|
|||
|
|
@ -30,9 +30,26 @@ var zendeskTemplates = struct {
|
|||
`Vulnerability {{ .CVE }} detected on {{ len .Hosts }} host(s)`,
|
||||
)),
|
||||
|
||||
VulnDescription: template.Must(template.New("").Parse(
|
||||
// Zendesk uses markdown for formatting. Some reference documentation about
|
||||
// it can be found here:
|
||||
// https://support.zendesk.com/hc/en-us/articles/4408846544922-Formatting-text-with-Markdown
|
||||
VulnDescription: template.Must(template.New("").Funcs(template.FuncMap{
|
||||
// CISAKnownExploit is *bool, so any condition check on it in the template
|
||||
// will test if nil or not, and not its actual boolean value. Hence, "deref".
|
||||
"deref": func(b *bool) bool { return *b },
|
||||
}).Parse(
|
||||
`See vulnerability (CVE) details in National Vulnerability Database (NVD) here: [{{ .CVE }}]({{ .NVDURL }}{{ .CVE }}).
|
||||
|
||||
{{ if .IsPremium }}{{ if .EPSSProbability }}
|
||||
|
||||
Probability of exploit (reported by [FIRST.org/epss](https://www.first.org/epss/)): {{ .EPSSProbability }}
|
||||
{{ end }}
|
||||
{{ if .CVSSScore }}CVSS score (reported by [NVD](https://nvd.nist.gov/)): {{ .CVSSScore }}
|
||||
{{ 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 }}
|
||||
|
||||
Affected hosts:
|
||||
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
|
|
@ -43,8 +60,8 @@ Affected hosts:
|
|||
View the affected software and more affected hosts:
|
||||
|
||||
1. Go to the [Software]({{ .FleetURL }}/software/manage) page in Fleet.
|
||||
2. Above the list of software, in the *Search software* box, enter "{{ .CVE }}".
|
||||
3. Hover over the affected software and select *View all hosts*.
|
||||
2. Above the list of software, in the **Search software** box, enter "{{ .CVE }}".
|
||||
3. Hover over the affected software and select **View all hosts**.
|
||||
|
||||
----
|
||||
|
||||
|
|
@ -75,6 +92,13 @@ type zendeskVulnTplArgs struct {
|
|||
FleetURL string
|
||||
CVE string
|
||||
Hosts []*fleet.HostShort
|
||||
|
||||
IsPremium bool
|
||||
|
||||
// the following fields are only included in the ticket for premium licenses.
|
||||
EPSSProbability *float64
|
||||
CVSSScore *float64
|
||||
CISAKnownExploit *bool
|
||||
}
|
||||
|
||||
type zendeskFailingPoliciesTplArgs struct {
|
||||
|
|
@ -97,6 +121,7 @@ type Zendesk struct {
|
|||
FleetURL string
|
||||
Datastore fleet.Datastore
|
||||
Log kitlog.Logger
|
||||
License *fleet.LicenseInfo
|
||||
NewClientFunc func(*externalsvc.ZendeskOptions) (ZendeskClient, error)
|
||||
|
||||
// mu protects concurrent access to clientsCache, so that the job processor
|
||||
|
|
@ -199,7 +224,10 @@ func (z *Zendesk) Name() string {
|
|||
|
||||
// zendeskArgs are the arguments for the Zendesk integration job.
|
||||
type zendeskArgs struct {
|
||||
// CVE is deprecated but kept for backwards compatibility (there may be jobs
|
||||
// enqueued in that format to process).
|
||||
CVE string `json:"cve,omitempty"`
|
||||
Vulnerability *vulnArgs `json:"vulnerability,omitempty"`
|
||||
FailingPolicy *failingPolicyArgs `json:"failing_policy,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -239,16 +267,28 @@ func (z *Zendesk) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
|||
}
|
||||
|
||||
func (z *Zendesk) runVuln(ctx context.Context, cli ZendeskClient, args zendeskArgs) error {
|
||||
hosts, err := z.Datastore.HostsByCVE(ctx, args.CVE)
|
||||
vargs := args.Vulnerability
|
||||
if vargs == nil {
|
||||
// support the old format of vulnerability args, where only the CVE
|
||||
// is provided.
|
||||
vargs = &vulnArgs{
|
||||
CVE: args.CVE,
|
||||
}
|
||||
}
|
||||
hosts, err := z.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "find hosts by cve")
|
||||
}
|
||||
|
||||
tplArgs := &zendeskVulnTplArgs{
|
||||
NVDURL: nvdCVEURL,
|
||||
FleetURL: z.FleetURL,
|
||||
CVE: args.CVE,
|
||||
Hosts: hosts,
|
||||
NVDURL: nvdCVEURL,
|
||||
FleetURL: z.FleetURL,
|
||||
CVE: vargs.CVE,
|
||||
Hosts: hosts,
|
||||
IsPremium: z.License.IsPremium(),
|
||||
EPSSProbability: vargs.EPSSProbability,
|
||||
CVSSScore: vargs.CVSSScore,
|
||||
CISAKnownExploit: vargs.CISAKnownExploit,
|
||||
}
|
||||
|
||||
createdTicket, err := z.createTemplatedTicket(ctx, cli, zendeskTemplates.VulnSummary, zendeskTemplates.VulnDescription, tplArgs)
|
||||
|
|
@ -257,7 +297,7 @@ func (z *Zendesk) runVuln(ctx context.Context, cli ZendeskClient, args zendeskAr
|
|||
}
|
||||
level.Debug(z.Log).Log(
|
||||
"msg", "created zendesk ticket for cve",
|
||||
"cve", args.CVE,
|
||||
"cve", vargs.CVE,
|
||||
"ticket_id", createdTicket.ID,
|
||||
)
|
||||
return nil
|
||||
|
|
@ -317,7 +357,13 @@ func (z *Zendesk) createTemplatedTicket(ctx context.Context, cli ZendeskClient,
|
|||
|
||||
// QueueZendeskVulnJobs queues the Zendesk vulnerability jobs to process asynchronously
|
||||
// via the worker.
|
||||
func QueueZendeskVulnJobs(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, recentVulns []fleet.SoftwareVulnerability) error {
|
||||
func QueueZendeskVulnJobs(
|
||||
ctx context.Context,
|
||||
ds fleet.Datastore,
|
||||
logger kitlog.Logger,
|
||||
recentVulns []fleet.SoftwareVulnerability,
|
||||
cveMeta map[string]fleet.CVEMeta,
|
||||
) error {
|
||||
level.Info(logger).Log("enabled", "true", "recentVulns", len(recentVulns))
|
||||
|
||||
// for troubleshooting, log in debug level the CVEs that we will process
|
||||
|
|
@ -336,7 +382,13 @@ func QueueZendeskVulnJobs(ctx context.Context, ds fleet.Datastore, logger kitlog
|
|||
}
|
||||
|
||||
for cve := range uniqCVEs {
|
||||
job, err := QueueJob(ctx, ds, zendeskName, zendeskArgs{CVE: cve})
|
||||
args := vulnArgs{CVE: cve}
|
||||
if meta, ok := cveMeta[cve]; ok {
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
args.CISAKnownExploit = meta.CISAKnownExploit
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, zendeskName, zendeskArgs{Vulnerability: &args})
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queueing job")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,38 +83,99 @@ func TestZendeskRun(t *testing.T) {
|
|||
client, err := externalsvc.NewZendeskTestClient(&externalsvc.ZendeskOptions{URL: srv.URL, GroupID: int64(123)})
|
||||
require.NoError(t, err)
|
||||
|
||||
zendesk := &Zendesk{
|
||||
FleetURL: "https://fleetdm.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (ZendeskClient, error) {
|
||||
return client, nil
|
||||
cases := []struct {
|
||||
desc string
|
||||
licenseTier string
|
||||
payload string
|
||||
expectedSubject string
|
||||
expectedDescription string
|
||||
expectedNotInDescription string
|
||||
}{
|
||||
{
|
||||
"old vuln format free",
|
||||
fleet.TierFree,
|
||||
`{"cve":"CVE-1234-5678"}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
`"group_id":123`,
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln free",
|
||||
fleet.TierFree,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678"}}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
`"group_id":123`,
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln with scores free",
|
||||
fleet.TierFree,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
`"group_id":123`,
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"failing global policy",
|
||||
fleet.TierFree,
|
||||
`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": [{"id": 123, "hostname": "host-123"}]}}`,
|
||||
`"subject":"test-policy policy failed on 1 host(s)"`,
|
||||
"\\u0026policy_id=1\\u0026policy_response=failing",
|
||||
"\\u0026team_id=",
|
||||
},
|
||||
{
|
||||
"failing team policy",
|
||||
fleet.TierPremium,
|
||||
`{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "host-1"}, {"id": 2, "hostname": "host-2"}]}}`,
|
||||
`"subject":"test-policy-2 policy failed on 2 host(s)"`,
|
||||
"\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"old vuln format premium",
|
||||
fleet.TierPremium,
|
||||
`{"cve":"CVE-1234-5678"}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
`"group_id":123`,
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln premium",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678"}}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
`"group_id":123`,
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
"vuln with scores premium",
|
||||
fleet.TierPremium,
|
||||
`{"vulnerability":{"cve":"CVE-1234-5678","epss_probability":3.4,"cvss_score":50,"cisa_known_exploit":true}}`,
|
||||
`"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`,
|
||||
"Probability of exploit",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("vuln", func(t *testing.T) {
|
||||
expectedSubject = `"subject":"Vulnerability CVE-1234-5678 detected on 1 host(s)"`
|
||||
expectedDescription = `"group_id":123`
|
||||
expectedNotInDescription = ""
|
||||
err = zendesk.Run(context.Background(), json.RawMessage(`{"cve":"CVE-1234-5678"}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
zendesk := &Zendesk{
|
||||
FleetURL: "https://fleetdm.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: c.licenseTier},
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (ZendeskClient, error) {
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("failing global policy", func(t *testing.T) {
|
||||
expectedSubject = `"subject":"test-policy policy failed on 1 host(s)"`
|
||||
expectedDescription = "\\u0026policy_id=1\\u0026policy_response=failing" // ampersand gets rendered as \u0026 in json string
|
||||
expectedNotInDescription = "\\u0026team_id="
|
||||
err = zendesk.Run(context.Background(), json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": [{"id": 123, "hostname": "host-123"}]}}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("failing team policy", func(t *testing.T) {
|
||||
expectedSubject = `"subject":"test-policy-2 policy failed on 2 host(s)"`
|
||||
expectedDescription = "\\u0026team_id=123\\u0026policy_id=2\\u0026policy_response=failing" // ampersand gets rendered as \u0026 in json string
|
||||
expectedNotInDescription = ""
|
||||
err = zendesk.Run(context.Background(), json.RawMessage(`{"failing_policy":{"policy_id": 2, "policy_name": "test-policy-2", "team_id": 123, "hosts": [{"id": 1, "hostname": "host-1"}, {"id": 2, "hostname": "host-2"}]}}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
expectedSubject = c.expectedSubject
|
||||
expectedDescription = c.expectedDescription
|
||||
expectedNotInDescription = c.expectedNotInDescription
|
||||
err = zendesk.Run(context.Background(), json.RawMessage(c.payload))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestZendeskQueueVulnJobs(t *testing.T) {
|
||||
|
|
@ -141,8 +202,12 @@ func TestZendeskQueueVulnJobs(t *testing.T) {
|
|||
CVE: "CVE-1234-5678",
|
||||
SoftwareID: 3,
|
||||
}}
|
||||
meta := make(map[string]fleet.CVEMeta, len(vulns))
|
||||
for _, v := range vulns {
|
||||
meta[v.CVE] = fleet.CVEMeta{CVE: v.CVE}
|
||||
}
|
||||
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, vulns)
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, vulns, meta)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
require.Equal(t, 1, count)
|
||||
|
|
@ -152,7 +217,11 @@ func TestZendeskQueueVulnJobs(t *testing.T) {
|
|||
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
|
||||
return job, nil
|
||||
}
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: "CVE-1234-5678"}})
|
||||
theCVE := "CVE-1234-5678"
|
||||
meta := map[string]fleet.CVEMeta{
|
||||
theCVE: {CVE: theCVE},
|
||||
}
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: theCVE}}, meta)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
})
|
||||
|
|
@ -161,7 +230,11 @@ func TestZendeskQueueVulnJobs(t *testing.T) {
|
|||
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: "CVE-1234-5678"}})
|
||||
theCVE := "CVE-1234-5678"
|
||||
meta := map[string]fleet.CVEMeta{
|
||||
theCVE: {CVE: theCVE},
|
||||
}
|
||||
err := QueueZendeskVulnJobs(ctx, ds, logger, []fleet.SoftwareVulnerability{{CVE: theCVE}}, meta)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, io.EOF)
|
||||
require.True(t, ds.NewJobFuncInvoked)
|
||||
|
|
@ -292,6 +365,7 @@ func TestZendeskRunClientUpdate(t *testing.T) {
|
|||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
Log: kitlog.NewNopLogger(),
|
||||
License: &fleet.LicenseInfo{Tier: fleet.TierFree},
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (ZendeskClient, error) {
|
||||
// keep track of group IDs received in calls to NewClientFunc
|
||||
groupIDs = append(groupIDs, opts.GroupID)
|
||||
|
|
|
|||
|
|
@ -28,13 +28,23 @@ func main() {
|
|||
jiraProjectKey = flag.String("jira-project-key", "", "The Jira project key")
|
||||
fleetURL = flag.String("fleet-url", "https://localhost:8080", "The Fleet server URL")
|
||||
cve = flag.String("cve", "", "The CVE to create a Jira issue for")
|
||||
epssProbability = flag.Float64("epss-probability", 0, "The EPSS Probability score of the CVE")
|
||||
cvssScore = flag.Float64("cvss-score", 0, "The CVSS score of the CVE")
|
||||
cisaKnownExploit = flag.Bool("cisa-known-exploit", false, "Whether CISA reported it as a known exploit")
|
||||
hostsCount = flag.Int("hosts-count", 1, "The number of hosts to match the CVE or failing policy")
|
||||
failingPolicyID = flag.Int("failing-policy-id", 0, "The failing policy ID")
|
||||
failingPolicyTeamID = flag.Int("failing-policy-team-id", 0, "The Team ID of the failing policy")
|
||||
premiumLicense = flag.Bool("premium", false, "Whether to simulate a premium user or not")
|
||||
)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// keep set of flags that were provided, to handle those that can be absent
|
||||
setFlags := make(map[string]bool)
|
||||
flag.CommandLine.Visit(func(f *flag.Flag) {
|
||||
setFlags[f.Name] = true
|
||||
})
|
||||
|
||||
if *jiraURL == "" {
|
||||
fmt.Fprintf(os.Stderr, "-jira-url is required")
|
||||
os.Exit(1)
|
||||
|
|
@ -72,7 +82,7 @@ func main() {
|
|||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
hosts := make([]*fleet.HostShort, *hostsCount)
|
||||
for i := 0; i < *hostsCount; i++ {
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1)}
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
|
@ -110,10 +120,15 @@ func main() {
|
|||
}, nil
|
||||
}
|
||||
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierFree}
|
||||
if *premiumLicense {
|
||||
license.Tier = fleet.TierPremium
|
||||
}
|
||||
jira := &worker.Jira{
|
||||
FleetURL: *fleetURL,
|
||||
Datastore: ds,
|
||||
Log: logger,
|
||||
License: license,
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (worker.JiraClient, error) {
|
||||
return externalsvc.NewJiraClient(opts)
|
||||
},
|
||||
|
|
@ -121,7 +136,31 @@ func main() {
|
|||
|
||||
var argsJSON json.RawMessage
|
||||
if *cve != "" {
|
||||
argsJSON = json.RawMessage(fmt.Sprintf(`{"cve":%q}`, *cve))
|
||||
vulnArgs := struct {
|
||||
CVE string `json:"cve,omitempty"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"`
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"`
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"`
|
||||
}{
|
||||
CVE: *cve,
|
||||
}
|
||||
if setFlags["epss-probability"] {
|
||||
vulnArgs.EPSSProbability = epssProbability
|
||||
}
|
||||
if setFlags["cvss-score"] {
|
||||
vulnArgs.CVSSScore = cvssScore
|
||||
}
|
||||
if setFlags["cisa-known-exploit"] {
|
||||
vulnArgs.CISAKnownExploit = cisaKnownExploit
|
||||
}
|
||||
|
||||
b, err := json.Marshal(vulnArgs)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to marshal vulnerability args: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
argsJSON = json.RawMessage(fmt.Sprintf(`{"vulnerability":%s}`, string(b)))
|
||||
|
||||
} else if *failingPolicyID > 0 {
|
||||
jsonStr := fmt.Sprintf(`{"failing_policy":{"policy_id": %d, "policy_name": "test-policy-%[1]d", `, *failingPolicyID)
|
||||
if *failingPolicyTeamID > 0 {
|
||||
|
|
|
|||
|
|
@ -28,13 +28,23 @@ func main() {
|
|||
zendeskGroupID = flag.Int64("zendesk-group-id", 0, "The Zendesk group id")
|
||||
fleetURL = flag.String("fleet-url", "https://localhost:8080", "The Fleet server URL")
|
||||
cve = flag.String("cve", "", "The CVE to create a Zendesk issue for")
|
||||
epssProbability = flag.Float64("epss-probability", 0, "The EPSS Probability score of the CVE")
|
||||
cvssScore = flag.Float64("cvss-score", 0, "The CVSS score of the CVE")
|
||||
cisaKnownExploit = flag.Bool("cisa-known-exploit", false, "Whether CISA reported it as a known exploit")
|
||||
hostsCount = flag.Int("hosts-count", 1, "The number of hosts to match the CVE or failing policy")
|
||||
failingPolicyID = flag.Int("failing-policy-id", 0, "The failing policy ID")
|
||||
failingPolicyTeamID = flag.Int("failing-policy-team-id", 0, "The Team ID of the failing policy")
|
||||
premiumLicense = flag.Bool("premium", false, "Whether to simulate a premium user or not")
|
||||
)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// keep set of flags that were provided, to handle those that can be absent
|
||||
setFlags := make(map[string]bool)
|
||||
flag.CommandLine.Visit(func(f *flag.Flag) {
|
||||
setFlags[f.Name] = true
|
||||
})
|
||||
|
||||
if *zendeskURL == "" {
|
||||
fmt.Fprintf(os.Stderr, "-zendesk-url is required")
|
||||
os.Exit(1)
|
||||
|
|
@ -72,7 +82,7 @@ func main() {
|
|||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
hosts := make([]*fleet.HostShort, *hostsCount)
|
||||
for i := 0; i < *hostsCount; i++ {
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1)}
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
|
@ -110,10 +120,15 @@ func main() {
|
|||
}, nil
|
||||
}
|
||||
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierFree}
|
||||
if *premiumLicense {
|
||||
license.Tier = fleet.TierPremium
|
||||
}
|
||||
zendesk := &worker.Zendesk{
|
||||
FleetURL: *fleetURL,
|
||||
Datastore: ds,
|
||||
Log: logger,
|
||||
License: license,
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (worker.ZendeskClient, error) {
|
||||
return externalsvc.NewZendeskClient(opts)
|
||||
},
|
||||
|
|
@ -121,7 +136,31 @@ func main() {
|
|||
|
||||
var argsJSON json.RawMessage
|
||||
if *cve != "" {
|
||||
argsJSON = json.RawMessage(fmt.Sprintf(`{"cve":%q}`, *cve))
|
||||
vulnArgs := struct {
|
||||
CVE string `json:"cve,omitempty"`
|
||||
EPSSProbability *float64 `json:"epss_probability,omitempty"`
|
||||
CVSSScore *float64 `json:"cvss_score,omitempty"`
|
||||
CISAKnownExploit *bool `json:"cisa_known_exploit,omitempty"`
|
||||
}{
|
||||
CVE: *cve,
|
||||
}
|
||||
if setFlags["epss-probability"] {
|
||||
vulnArgs.EPSSProbability = epssProbability
|
||||
}
|
||||
if setFlags["cvss-score"] {
|
||||
vulnArgs.CVSSScore = cvssScore
|
||||
}
|
||||
if setFlags["cisa-known-exploit"] {
|
||||
vulnArgs.CISAKnownExploit = cisaKnownExploit
|
||||
}
|
||||
|
||||
b, err := json.Marshal(vulnArgs)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to marshal vulnerability args: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
argsJSON = json.RawMessage(fmt.Sprintf(`{"vulnerability":%s}`, string(b)))
|
||||
|
||||
} else if *failingPolicyID > 0 {
|
||||
jsonStr := fmt.Sprintf(`{"failing_policy":{"policy_id": %d, "policy_name": "test-policy-%[1]d", `, *failingPolicyID)
|
||||
if *failingPolicyTeamID > 0 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue