mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Feature 10196: Add filepath to end-points and third party integrations (#11285)
Adds the software installed path property to the proper end-points and third party integrations (webhook, Zendesk and Jira).
This commit is contained in:
parent
49b04ba4a5
commit
009a87d33e
4
changes/10196-add-file-path
Normal file
4
changes/10196-add-file-path
Normal file
@ -0,0 +1,4 @@
|
||||
- The 'GET /api/v1/fleet/hosts/{id}' and 'GET /api/v1/fleet/hosts/identifier/{identifier}' now
|
||||
include the software installed path on their payload.
|
||||
- Third party vulnerability integrations now include the installed path of the vulnerable software
|
||||
on each host.
|
@ -477,8 +477,8 @@ func TestScanVulnerabilities(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
ds.HostsBySoftwareIDsFunc = func(ctx context.Context, softwareIDs []uint) ([]*fleet.HostShort, error) {
|
||||
return []*fleet.HostShort{
|
||||
ds.HostVulnSummariesBySoftwareIDsFunc = func(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "1",
|
||||
|
@ -43,7 +43,8 @@ POST https://server.com/example
|
||||
{
|
||||
"id": 1,
|
||||
"hostname": "macbook-1",
|
||||
"url": "https://fleet.example.com/hosts/1"
|
||||
"url": "https://fleet.example.com/hosts/1",
|
||||
"software_installed_paths": ["/usr/lib/some-path"],
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
@ -2109,7 +2109,8 @@ Returns the information of the specified host.
|
||||
"version": "4.5.1",
|
||||
"source": "rpm_packages",
|
||||
"generated_cpe": "",
|
||||
"vulnerabilities": null
|
||||
"vulnerabilities": null,
|
||||
"installed_paths": ["/usr/lib/some-path-1"]
|
||||
},
|
||||
{
|
||||
"id": 1146,
|
||||
@ -2127,7 +2128,8 @@ Returns the information of the specified host.
|
||||
"bundle_identifier": "com.some.app",
|
||||
"last_opened_at": "2021-08-18T21:14:00Z",
|
||||
"generated_cpe": "",
|
||||
"vulnerabilities": null
|
||||
"vulnerabilities": null,
|
||||
"installed_paths": ["/usr/lib/some-path-2"]
|
||||
}
|
||||
],
|
||||
"id": 1,
|
||||
@ -2338,7 +2340,8 @@ Returns the information of the host specified using the `uuid`, `osquery_host_id
|
||||
"version": "0.8.0",
|
||||
"source": "python_packages",
|
||||
"generated_cpe": "",
|
||||
"vulnerabilities": null
|
||||
"vulnerabilities": null,
|
||||
"installed_paths": ["/usr/lib/some_path/"]
|
||||
}
|
||||
],
|
||||
"id": 33,
|
||||
|
@ -17,7 +17,7 @@ func NewMapper() fleetwebhooks.VulnMapper {
|
||||
|
||||
func (m *Mapper) GetPayload(
|
||||
hostBaseURL *url.URL,
|
||||
hosts []*fleet.HostShort,
|
||||
hosts []fleet.HostVulnerabilitySummary,
|
||||
cve string,
|
||||
meta fleet.CVEMeta,
|
||||
) fleetwebhooks.WebhookPayload {
|
||||
|
@ -9,6 +9,7 @@ export interface IHostsAffected {
|
||||
id: number;
|
||||
display_name: string;
|
||||
url: string;
|
||||
software_installed_paths?: string[];
|
||||
}
|
||||
export interface IVulnerability {
|
||||
cve: string;
|
||||
|
@ -36,6 +36,7 @@ const PreviewPayloadModal = ({
|
||||
id: 1,
|
||||
display_name: "macbook-1",
|
||||
url: "https://fleet.example.com/hosts/1",
|
||||
software_installed_paths: ["/usr/lib/some-path"],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -112,6 +112,7 @@ func (ds *Datastore) UpdateHostSoftwareInstalledPaths(
|
||||
})
|
||||
}
|
||||
|
||||
// getHostSoftwareInstalledPaths returns all HostSoftwareInstalledPath for the given hostID.
|
||||
func (ds *Datastore) getHostSoftwareInstalledPaths(
|
||||
ctx context.Context,
|
||||
hostID uint,
|
||||
@ -852,7 +853,27 @@ func (ds *Datastore) LoadHostSoftware(ctx context.Context, host *fleet.Host, inc
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Software = software
|
||||
|
||||
installedPaths, err := ds.getHostSoftwareInstalledPaths(
|
||||
ctx,
|
||||
host.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lookup := make(map[uint][]string)
|
||||
for _, ip := range installedPaths {
|
||||
lookup[ip.SoftwareID] = append(lookup[ip.SoftwareID], ip.InstalledPath)
|
||||
}
|
||||
|
||||
host.Software = make([]fleet.HostSoftwareEntry, 0, len(software))
|
||||
for _, s := range software {
|
||||
host.Software = append(host.Software, fleet.HostSoftwareEntry{
|
||||
Software: s,
|
||||
InstalledPaths: lookup[s.ID],
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1266,58 +1287,106 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HostsBySoftwareIDs returns a list of all hosts that have at least one of the specified Software
|
||||
// installed. It returns a minimal represention of matching hosts.
|
||||
func (ds *Datastore) HostsBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]*fleet.HostShort, error) {
|
||||
queryStmt := `
|
||||
SELECT
|
||||
h.id,
|
||||
h.hostname,
|
||||
if(h.computer_name = '', h.hostname, h.computer_name) display_name
|
||||
FROM
|
||||
hosts h
|
||||
INNER JOIN
|
||||
host_software hs
|
||||
ON
|
||||
h.id = hs.host_id
|
||||
WHERE
|
||||
hs.software_id IN (?)
|
||||
GROUP BY h.id, h.hostname
|
||||
ORDER BY
|
||||
h.id`
|
||||
func (ds *Datastore) HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
stmt := `
|
||||
SELECT DISTINCT
|
||||
h.id,
|
||||
h.hostname,
|
||||
if(h.computer_name = '', h.hostname, h.computer_name) display_name,
|
||||
COALESCE(hsip.installed_path, '') AS software_installed_path
|
||||
FROM hosts h
|
||||
INNER JOIN host_software hs ON h.id = hs.host_id AND hs.software_id IN (?)
|
||||
LEFT JOIN host_software_installed_paths hsip ON hs.host_id = hsip.host_id AND hs.software_id = hsip.software_id
|
||||
ORDER BY h.id`
|
||||
|
||||
stmt, args, err := sqlx.In(queryStmt, softwareIDs)
|
||||
stmt, args, err := sqlx.In(stmt, softwareIDs)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "building query args")
|
||||
}
|
||||
var hosts []*fleet.HostShort
|
||||
if err := sqlx.SelectContext(ctx, ds.reader, &hosts, stmt, args...); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "select hosts by cpes")
|
||||
|
||||
var qR []struct {
|
||||
HostID uint `db:"id"`
|
||||
HostName string `db:"hostname"`
|
||||
DisplayName string `db:"display_name"`
|
||||
SPath string `db:"software_installed_path"`
|
||||
}
|
||||
return hosts, nil
|
||||
if err := sqlx.SelectContext(ctx, ds.reader, &qR, stmt, args...); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "selecting hosts by softwareIDs")
|
||||
}
|
||||
|
||||
var result []fleet.HostVulnerabilitySummary
|
||||
lookup := make(map[uint]int)
|
||||
|
||||
for _, r := range qR {
|
||||
i, ok := lookup[r.HostID]
|
||||
|
||||
if ok {
|
||||
result[i].AddSoftwareInstalledPath(r.SPath)
|
||||
continue
|
||||
}
|
||||
|
||||
mapped := fleet.HostVulnerabilitySummary{
|
||||
ID: r.HostID,
|
||||
Hostname: r.HostName,
|
||||
DisplayName: r.DisplayName,
|
||||
}
|
||||
mapped.AddSoftwareInstalledPath(r.SPath)
|
||||
result = append(result, mapped)
|
||||
|
||||
lookup[r.HostID] = len(result) - 1
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) HostsByCVE(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
query := `
|
||||
SELECT DISTINCT
|
||||
(h.id),
|
||||
h.hostname,
|
||||
if(h.computer_name = '', h.hostname, h.computer_name) display_name
|
||||
FROM
|
||||
hosts h
|
||||
INNER JOIN host_software hs ON h.id = hs.host_id
|
||||
INNER JOIN software_cve scv ON scv.software_id = hs.software_id
|
||||
WHERE
|
||||
scv.cve = ?
|
||||
ORDER BY
|
||||
h.id
|
||||
`
|
||||
// ** DEPRECATED **
|
||||
func (ds *Datastore) HostsByCVE(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
stmt := `
|
||||
SELECT DISTINCT
|
||||
(h.id),
|
||||
h.hostname,
|
||||
if(h.computer_name = '', h.hostname, h.computer_name) display_name,
|
||||
COALESCE(hsip.installed_path, '') AS software_installed_path
|
||||
FROM hosts h
|
||||
INNER JOIN host_software hs ON h.id = hs.host_id
|
||||
INNER JOIN software_cve scv ON scv.software_id = hs.software_id
|
||||
LEFT JOIN host_software_installed_paths hsip ON hs.host_id = hsip.host_id AND hs.software_id = hsip.software_id
|
||||
WHERE scv.cve = ?
|
||||
ORDER BY h.id`
|
||||
|
||||
var hosts []*fleet.HostShort
|
||||
if err := sqlx.SelectContext(ctx, ds.reader, &hosts, query, cve); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "select hosts by cves")
|
||||
var qR []struct {
|
||||
HostID uint `db:"id"`
|
||||
HostName string `db:"hostname"`
|
||||
DisplayName string `db:"display_name"`
|
||||
SPath string `db:"software_installed_path"`
|
||||
}
|
||||
return hosts, nil
|
||||
if err := sqlx.SelectContext(ctx, ds.reader, &qR, stmt, cve); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "selecting hosts by softwareIDs")
|
||||
}
|
||||
|
||||
var result []fleet.HostVulnerabilitySummary
|
||||
lookup := make(map[uint]int)
|
||||
|
||||
for _, r := range qR {
|
||||
i, ok := lookup[r.HostID]
|
||||
|
||||
if ok {
|
||||
result[i].AddSoftwareInstalledPath(r.SPath)
|
||||
continue
|
||||
}
|
||||
|
||||
mapped := fleet.HostVulnerabilitySummary{
|
||||
ID: r.HostID,
|
||||
Hostname: r.HostName,
|
||||
DisplayName: r.DisplayName,
|
||||
}
|
||||
mapped.AddSoftwareInstalledPath(r.SPath)
|
||||
result = append(result, mapped)
|
||||
|
||||
lookup[r.HostID] = len(result) - 1
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) InsertCVEMeta(ctx context.Context, cveMeta []fleet.CVEMeta) error {
|
||||
|
@ -37,7 +37,7 @@ func TestSoftware(t *testing.T) {
|
||||
{"SyncHostsSoftware", testSoftwareSyncHostsSoftware},
|
||||
{"DeleteSoftwareVulnerabilities", testDeleteSoftwareVulnerabilities},
|
||||
{"HostsByCVE", testHostsByCVE},
|
||||
{"HostsBySoftwareIDs", testHostsBySoftwareIDs},
|
||||
{"HostVulnSummariesBySoftwareIDs", testHostVulnSummariesBySoftwareIDs},
|
||||
{"UpdateHostSoftware", testUpdateHostSoftware},
|
||||
{"UpdateHostSoftwareUpdatesSoftware", testUpdateHostSoftwareUpdatesSoftware},
|
||||
{"ListSoftwareByHostIDShort", testListSoftwareByHostIDShort},
|
||||
@ -79,21 +79,31 @@ func testSoftwareSaveHost(t *testing.T, ds *Datastore) {
|
||||
{Name: "zoo", Version: "0.0.5", Source: "deb_packages", BundleIdentifier: ""},
|
||||
}
|
||||
|
||||
getHostSoftware := func(h *fleet.Host) []fleet.Software {
|
||||
var software []fleet.Software
|
||||
for _, s := range h.Software {
|
||||
software = append(software, s.Software)
|
||||
}
|
||||
return software
|
||||
}
|
||||
|
||||
_, err := ds.UpdateHostSoftware(context.Background(), host1.ID, software1)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host1, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1.HostSoftware.Software)
|
||||
host1Software := getHostSoftware(host1)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1Software)
|
||||
|
||||
soft1ByID, err := ds.SoftwareByID(context.Background(), host1.HostSoftware.Software[0].ID, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, soft1ByID)
|
||||
assert.Equal(t, host1.HostSoftware.Software[0], *soft1ByID)
|
||||
assert.Equal(t, host1Software[0], *soft1ByID)
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host2, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2.HostSoftware.Software)
|
||||
host2Software := getHostSoftware(host2)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2Software)
|
||||
|
||||
software1 = []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.1", Source: "chrome_extensions"},
|
||||
@ -108,10 +118,12 @@ func testSoftwareSaveHost(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host1, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1.HostSoftware.Software)
|
||||
host1Software = getHostSoftware(host1)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1Software)
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host2, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2.HostSoftware.Software)
|
||||
host2Software = getHostSoftware(host2)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2Software)
|
||||
|
||||
software1 = []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.3", Source: "chrome_extensions"},
|
||||
@ -120,9 +132,9 @@ func testSoftwareSaveHost(t *testing.T, ds *Datastore) {
|
||||
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), host1.ID, software1)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host1, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1.HostSoftware.Software)
|
||||
host1Software = getHostSoftware(host1)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software1, host1Software)
|
||||
|
||||
software2 = []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.2", Source: "chrome_extensions"},
|
||||
@ -133,7 +145,8 @@ func testSoftwareSaveHost(t *testing.T, ds *Datastore) {
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host2, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2.HostSoftware.Software)
|
||||
host2Software = getHostSoftware(host2)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2Software)
|
||||
|
||||
software2 = []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.2", Source: "chrome_extensions"},
|
||||
@ -144,7 +157,8 @@ func testSoftwareSaveHost(t *testing.T, ds *Datastore) {
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host2, false))
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2.HostSoftware.Software)
|
||||
host2Software = getHostSoftware(host2)
|
||||
test.ElementsMatchSkipIDAndHostCount(t, software2, host2Software)
|
||||
}
|
||||
|
||||
func testSoftwareCPE(t *testing.T, ds *Datastore) {
|
||||
@ -1132,11 +1146,28 @@ func insertVulnSoftwareForTest(t *testing.T, ds *Datastore) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ds.UpdateHostSoftware(context.Background(), host1.ID, software1)
|
||||
mutationResults, err := ds.UpdateHostSoftware(context.Background(), host1.ID, software1)
|
||||
require.NoError(t, err)
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2)
|
||||
|
||||
// Insert paths for software1
|
||||
s1Paths := map[string]struct{}{}
|
||||
for _, s := range software1 {
|
||||
key := fmt.Sprintf("%s%s%s", fmt.Sprintf("/some/path/%s", s.Name), fleet.SoftwareFieldSeparator, s.ToUniqueStr())
|
||||
s1Paths[key] = struct{}{}
|
||||
}
|
||||
require.NoError(t, ds.UpdateHostSoftwareInstalledPaths(context.Background(), host1.ID, s1Paths, mutationResults))
|
||||
|
||||
mutationResults, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert paths for software2
|
||||
s2Paths := map[string]struct{}{}
|
||||
for _, s := range software2 {
|
||||
key := fmt.Sprintf("%s%s%s", fmt.Sprintf("/some/path/%s", s.Name), fleet.SoftwareFieldSeparator, s.ToUniqueStr())
|
||||
s2Paths[key] = struct{}{}
|
||||
}
|
||||
require.NoError(t, ds.UpdateHostSoftwareInstalledPaths(context.Background(), host2.ID, s2Paths, mutationResults))
|
||||
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host1, false))
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host2, false))
|
||||
sort.Slice(host1.Software, func(i, j int) bool {
|
||||
@ -1279,15 +1310,21 @@ func testHostsByCVE(t *testing.T, ds *Datastore) {
|
||||
hosts, err = ds.HostsByCVE(ctx, "CVE-2022-0001")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 2)
|
||||
require.ElementsMatch(t, hosts, []*fleet.HostShort{
|
||||
require.ElementsMatch(t, hosts, []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "host1",
|
||||
DisplayName: "computer1",
|
||||
SoftwareInstalledPaths: []string{
|
||||
"/some/path/foo.chrome",
|
||||
},
|
||||
}, {
|
||||
ID: 2,
|
||||
Hostname: "host2",
|
||||
DisplayName: "host2",
|
||||
SoftwareInstalledPaths: []string{
|
||||
"/some/path/foo.chrome",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -1298,10 +1335,11 @@ func testHostsByCVE(t *testing.T, ds *Datastore) {
|
||||
require.Equal(t, hosts[0].Hostname, "host2")
|
||||
}
|
||||
|
||||
func testHostsBySoftwareIDs(t *testing.T, ds *Datastore) {
|
||||
func testHostVulnSummariesBySoftwareIDs(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
hosts, err := ds.HostsBySoftwareIDs(ctx, []uint{0})
|
||||
// Invalid non-existing host id
|
||||
hosts, err := ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{0})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 0)
|
||||
|
||||
@ -1310,48 +1348,57 @@ func testHostsBySoftwareIDs(t *testing.T, ds *Datastore) {
|
||||
allSoftware, err := ds.ListSoftware(ctx, fleet.SoftwareListOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var fooRpm fleet.Software
|
||||
var chrome3 fleet.Software
|
||||
var barRpm fleet.Software
|
||||
|
||||
for _, s := range allSoftware {
|
||||
if s.GenerateCPE == "cpe_foo_chrome_3" {
|
||||
switch s.GenerateCPE {
|
||||
case "cpe_foo_rpm":
|
||||
fooRpm = s
|
||||
case "cpe_foo_chrome_3":
|
||||
chrome3 = s
|
||||
}
|
||||
|
||||
if s.GenerateCPE == "cpe_bar_rpm" {
|
||||
case "cpe_bar_rpm":
|
||||
barRpm = s
|
||||
}
|
||||
}
|
||||
|
||||
require.NotZero(t, chrome3.ID)
|
||||
require.NotZero(t, barRpm.ID)
|
||||
|
||||
hosts, err = ds.HostsBySoftwareIDs(ctx, []uint{chrome3.ID})
|
||||
hosts, err = ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{chrome3.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 2)
|
||||
require.ElementsMatch(t, hosts, []*fleet.HostShort{
|
||||
require.ElementsMatch(t, hosts, []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "host1",
|
||||
DisplayName: "computer1",
|
||||
ID: 1,
|
||||
Hostname: "host1",
|
||||
DisplayName: "computer1",
|
||||
SoftwareInstalledPaths: []string{"/some/path/foo.chrome"},
|
||||
}, {
|
||||
ID: 2,
|
||||
Hostname: "host2",
|
||||
DisplayName: "host2",
|
||||
ID: 2,
|
||||
Hostname: "host2",
|
||||
DisplayName: "host2",
|
||||
SoftwareInstalledPaths: []string{"/some/path/foo.chrome"},
|
||||
},
|
||||
})
|
||||
|
||||
hosts, err = ds.HostsBySoftwareIDs(ctx, []uint{barRpm.ID})
|
||||
hosts, err = ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{barRpm.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 1)
|
||||
require.Equal(t, hosts[0].Hostname, "host2")
|
||||
require.ElementsMatch(t, hosts, []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 2,
|
||||
Hostname: "host2",
|
||||
DisplayName: "host2",
|
||||
SoftwareInstalledPaths: []string{"/some/path/bar.rpm"},
|
||||
},
|
||||
})
|
||||
|
||||
// Duplicates should not be returned if cpes are found on the same host ie host2 should only appear once
|
||||
hosts, err = ds.HostsBySoftwareIDs(ctx, []uint{chrome3.ID, barRpm.ID})
|
||||
hosts, err = ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{chrome3.ID, barRpm.ID, fooRpm.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 2)
|
||||
require.Equal(t, hosts[0].Hostname, "host1")
|
||||
require.Equal(t, hosts[1].Hostname, "host2")
|
||||
require.ElementsMatch(t, hosts[0].SoftwareInstalledPaths, []string{"/some/path/foo.rpm", "/some/path/foo.chrome"})
|
||||
require.ElementsMatch(t, hosts[1].SoftwareInstalledPaths, []string{"/some/path/bar.rpm", "/some/path/foo.chrome"})
|
||||
}
|
||||
|
||||
// testUpdateHostSoftwareUpdatesSoftware tests that uninstalling applications
|
||||
@ -1454,15 +1501,15 @@ func testUpdateHostSoftwareUpdatesSoftware(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
cmpNameVersionCount(expectedSoftware, software)
|
||||
|
||||
hosts, err := ds.HostsBySoftwareIDs(ctx, []uint{bazSoftwareID})
|
||||
hosts, err := ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{bazSoftwareID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 1)
|
||||
require.Equal(t, hosts[0].ID, h1.ID)
|
||||
|
||||
hosts, err = ds.HostsBySoftwareIDs(ctx, []uint{barSoftwareID})
|
||||
hosts, err = ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{barSoftwareID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, hosts)
|
||||
hosts, err = ds.HostsBySoftwareIDs(ctx, []uint{baz2SoftwareID})
|
||||
hosts, err = ds.HostVulnSummariesBySoftwareIDs(ctx, []uint{baz2SoftwareID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, hosts)
|
||||
|
||||
@ -1486,7 +1533,7 @@ func testUpdateHostSoftware(t *testing.T, ds *Datastore) {
|
||||
lastYear := now.Add(-365 * 24 * time.Hour)
|
||||
|
||||
// sort software slice by last opened at timestamp
|
||||
genSortFn := func(sl []fleet.Software) func(l, r int) bool {
|
||||
genSortFn := func(sl []fleet.HostSoftwareEntry) func(l, r int) bool {
|
||||
return func(l, r int) bool {
|
||||
lsw, rsw := sl[l], sl[r]
|
||||
lts, rts := lsw.LastOpenedAt, rsw.LastOpenedAt
|
||||
@ -2001,11 +2048,13 @@ func testAllSoftwareIterator(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host, false))
|
||||
|
||||
foo_ce_v1 := slices.IndexFunc(host.Software, func(c fleet.Software) bool {
|
||||
foo_ce_v1 := slices.IndexFunc(host.Software, func(c fleet.HostSoftwareEntry) bool {
|
||||
return c.Name == "foo" && c.Version == "0.0.1" && c.Source == "chrome_extensions"
|
||||
})
|
||||
foo_app_v2 := slices.IndexFunc(host.Software, func(c fleet.Software) bool { return c.Name == "foo" && c.Version == "v0.0.2" && c.Source == "apps" })
|
||||
bar_v3 := slices.IndexFunc(host.Software, func(c fleet.Software) bool {
|
||||
foo_app_v2 := slices.IndexFunc(host.Software, func(c fleet.HostSoftwareEntry) bool {
|
||||
return c.Name == "foo" && c.Version == "v0.0.2" && c.Source == "apps"
|
||||
})
|
||||
bar_v3 := slices.IndexFunc(host.Software, func(c fleet.HostSoftwareEntry) bool {
|
||||
return c.Name == "bar" && c.Version == "0.0.3" && c.Source == "deb_packages"
|
||||
})
|
||||
|
||||
|
@ -430,8 +430,13 @@ type Datastore interface {
|
||||
// After aggregation, it cleans up unused software (e.g. software installed
|
||||
// on removed hosts, software uninstalled on hosts, etc.)
|
||||
SyncHostsSoftware(ctx context.Context, updatedAt time.Time) error
|
||||
HostsBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]*HostShort, error)
|
||||
HostsByCVE(ctx context.Context, cve string) ([]*HostShort, error)
|
||||
// HostVulnSummariesBySoftwareIDs returns a list of all hosts that have at least one of the
|
||||
// specified Software installed. Includes the path were the software was installed.
|
||||
HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]HostVulnerabilitySummary, error)
|
||||
// *DEPRECATED use HostVulnSummariesBySoftwareIDs instead* HostsByCVE
|
||||
// returns a list of all hosts that have at least one software suceptible to the provided CVE.
|
||||
// Includes the path were the software was installed.
|
||||
HostsByCVE(ctx context.Context, cve string) ([]HostVulnerabilitySummary, error)
|
||||
InsertCVEMeta(ctx context.Context, cveMeta []CVEMeta) error
|
||||
ListCVEs(ctx context.Context, maxAge time.Duration) ([]CVEMeta, error)
|
||||
|
||||
|
@ -903,11 +903,23 @@ type AggregatedMacadminsData struct {
|
||||
MDMSolutions []AggregatedMDMSolutions `json:"mobile_device_management_solution"`
|
||||
}
|
||||
|
||||
// HostShort is a minimal host representation returned when querying hosts.
|
||||
type HostShort struct {
|
||||
ID uint `json:"id" db:"id"`
|
||||
Hostname string `json:"hostname" db:"hostname"`
|
||||
// HostVulnerabilitySummary type used with webhooks and third-party vulnerability automations.
|
||||
// Contains all pertinent host info plus the installed paths of all affected software.
|
||||
type HostVulnerabilitySummary struct {
|
||||
// ID Is the ID of the host
|
||||
ID uint `json:"id" db:"id"`
|
||||
// Hostname the host's hostname
|
||||
Hostname string `json:"hostname" db:"hostname"`
|
||||
// DisplayName either the 'computer_name' or the 'host_name' (whatever is not empty)
|
||||
DisplayName string `json:"display_name" db:"display_name"`
|
||||
// SoftwareInstalledPaths paths of vulnerable software installed on the host.
|
||||
SoftwareInstalledPaths []string `json:"software_installed_paths,omitempty" db:"software_installed_paths"`
|
||||
}
|
||||
|
||||
func (hvs *HostVulnerabilitySummary) AddSoftwareInstalledPath(p string) {
|
||||
if p != "" {
|
||||
hvs.SoftwareInstalledPaths = append(hvs.SoftwareInstalledPaths, p)
|
||||
}
|
||||
}
|
||||
|
||||
type OSVersions struct {
|
||||
|
@ -43,7 +43,7 @@ type Software struct {
|
||||
// GenerateCPE is the CPE23 string that corresponds to the current software
|
||||
GenerateCPE string `json:"generated_cpe" db:"generated_cpe"`
|
||||
|
||||
// Vulnerabilities lists all the found CVEs for the CPE
|
||||
// Vulnerabilities lists all found vulnerablities
|
||||
Vulnerabilities Vulnerabilities `json:"vulnerabilities"`
|
||||
// HostsCount indicates the number of hosts with that software, filled only
|
||||
// if explicitly requested.
|
||||
@ -83,10 +83,18 @@ func (s *AuthzSoftwareInventory) AuthzType() string {
|
||||
return "software_inventory"
|
||||
}
|
||||
|
||||
type HostSoftwareEntry struct {
|
||||
// Software details
|
||||
Software
|
||||
// Where this software was installed on the host, value is derived from the
|
||||
// host_software_installed_paths table.
|
||||
InstalledPaths []string `json:"installed_paths,omitempty"`
|
||||
}
|
||||
|
||||
// HostSoftware is the set of software installed on a specific host
|
||||
type HostSoftware struct {
|
||||
// Software is the software information.
|
||||
Software []Software `json:"software,omitempty" csv:"-"`
|
||||
Software []HostSoftwareEntry `json:"software,omitempty" csv:"-"`
|
||||
|
||||
// SoftwareUpdatedAt is the time that the host software was last updated
|
||||
SoftwareUpdatedAt time.Time `json:"software_updated_at" db:"software_updated_at" csv:"software_updated_at"`
|
||||
@ -141,6 +149,10 @@ type UpdateHostSoftwareDBResult struct {
|
||||
func (uhsdbr *UpdateHostSoftwareDBResult) CurrInstalled() []Software {
|
||||
var r []Software
|
||||
|
||||
if uhsdbr == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
deleteMap := map[uint]struct{}{}
|
||||
for _, d := range uhsdbr.Deleted {
|
||||
deleteMap[d.ID] = struct{}{}
|
||||
|
@ -332,9 +332,9 @@ type ListSoftwareByHostIDShortFunc func(ctx context.Context, hostID uint) ([]fle
|
||||
|
||||
type SyncHostsSoftwareFunc func(ctx context.Context, updatedAt time.Time) error
|
||||
|
||||
type HostsBySoftwareIDsFunc func(ctx context.Context, softwareIDs []uint) ([]*fleet.HostShort, error)
|
||||
type HostVulnSummariesBySoftwareIDsFunc func(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error)
|
||||
|
||||
type HostsByCVEFunc func(ctx context.Context, cve string) ([]*fleet.HostShort, error)
|
||||
type HostsByCVEFunc func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error)
|
||||
|
||||
type InsertCVEMetaFunc func(ctx context.Context, cveMeta []fleet.CVEMeta) error
|
||||
|
||||
@ -1105,8 +1105,8 @@ type DataStore struct {
|
||||
SyncHostsSoftwareFunc SyncHostsSoftwareFunc
|
||||
SyncHostsSoftwareFuncInvoked bool
|
||||
|
||||
HostsBySoftwareIDsFunc HostsBySoftwareIDsFunc
|
||||
HostsBySoftwareIDsFuncInvoked bool
|
||||
HostVulnSummariesBySoftwareIDsFunc HostVulnSummariesBySoftwareIDsFunc
|
||||
HostVulnSummariesBySoftwareIDsFuncInvoked bool
|
||||
|
||||
HostsByCVEFunc HostsByCVEFunc
|
||||
HostsByCVEFuncInvoked bool
|
||||
@ -2661,14 +2661,14 @@ func (s *DataStore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time)
|
||||
return s.SyncHostsSoftwareFunc(ctx, updatedAt)
|
||||
}
|
||||
|
||||
func (s *DataStore) HostsBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]*fleet.HostShort, error) {
|
||||
func (s *DataStore) HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
s.mu.Lock()
|
||||
s.HostsBySoftwareIDsFuncInvoked = true
|
||||
s.HostVulnSummariesBySoftwareIDsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.HostsBySoftwareIDsFunc(ctx, softwareIDs)
|
||||
return s.HostVulnSummariesBySoftwareIDsFunc(ctx, softwareIDs)
|
||||
}
|
||||
|
||||
func (s *DataStore) HostsByCVE(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
func (s *DataStore) HostsByCVE(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
s.mu.Lock()
|
||||
s.HostsByCVEFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
|
@ -4992,7 +4992,10 @@ func (s *integrationTestSuite) TestPaginateListSoftware() {
|
||||
|
||||
if i == 0 {
|
||||
// this host has all software, refresh the list so we have the software.ID filled
|
||||
sws = h.Software
|
||||
sws = make([]fleet.Software, 0, len(h.Software))
|
||||
for _, s := range h.Software {
|
||||
sws = append(sws, s.Software)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,8 +177,8 @@ func TestIntegrationAnalyzer(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.LoadHostSoftware(context.Background(), host, false))
|
||||
|
||||
var powerpoint fleet.Software
|
||||
var word fleet.Software
|
||||
var powerpoint fleet.HostSoftwareEntry
|
||||
var word fleet.HostSoftwareEntry
|
||||
for _, s := range host.HostSoftware.Software {
|
||||
if s.Name == "Microsoft PowerPoint.app" {
|
||||
powerpoint = s
|
||||
|
@ -13,14 +13,15 @@ import (
|
||||
// VulnMapper used for mapping vulnerabilities and their associated data into the payload that
|
||||
// will be sent via thrid party webhooks.
|
||||
type VulnMapper interface {
|
||||
GetPayload(*url.URL, []*fleet.HostShort, string, fleet.CVEMeta) WebhookPayload
|
||||
GetPayload(*url.URL, []fleet.HostVulnerabilitySummary, string, fleet.CVEMeta) WebhookPayload
|
||||
}
|
||||
|
||||
type hostPayloadPart struct {
|
||||
ID uint `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
DisplayName string `json:"display_name"`
|
||||
URL string `json:"url"`
|
||||
ID uint `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
DisplayName string `json:"display_name"`
|
||||
URL string `json:"url"`
|
||||
SoftwareInstalledPaths []string `json:"software_installed_paths,omitempty"`
|
||||
}
|
||||
|
||||
type WebhookPayload struct {
|
||||
@ -42,25 +43,35 @@ func NewMapper() VulnMapper {
|
||||
|
||||
func (m *Mapper) getHostPayloadPart(
|
||||
hostBaseURL *url.URL,
|
||||
hosts []*fleet.HostShort,
|
||||
hosts []fleet.HostVulnerabilitySummary,
|
||||
) []*hostPayloadPart {
|
||||
shortHosts := make([]*hostPayloadPart, len(hosts))
|
||||
for i, h := range hosts {
|
||||
hostURL := *hostBaseURL
|
||||
hostURL.Path = path.Join(hostURL.Path, "hosts", strconv.Itoa(int(h.ID)))
|
||||
shortHosts[i] = &hostPayloadPart{
|
||||
hostPayload := hostPayloadPart{
|
||||
ID: h.ID,
|
||||
Hostname: h.Hostname,
|
||||
DisplayName: h.DisplayName,
|
||||
URL: hostURL.String(),
|
||||
}
|
||||
|
||||
for _, p := range h.SoftwareInstalledPaths {
|
||||
if p != "" {
|
||||
hostPayload.SoftwareInstalledPaths = append(
|
||||
hostPayload.SoftwareInstalledPaths,
|
||||
p,
|
||||
)
|
||||
}
|
||||
}
|
||||
shortHosts[i] = &hostPayload
|
||||
}
|
||||
return shortHosts
|
||||
}
|
||||
|
||||
func (m *Mapper) GetPayload(
|
||||
hostBaseURL *url.URL,
|
||||
hosts []*fleet.HostShort,
|
||||
hosts []fleet.HostVulnerabilitySummary,
|
||||
cve string,
|
||||
meta fleet.CVEMeta,
|
||||
) WebhookPayload {
|
||||
|
@ -29,9 +29,48 @@ func TestGetPaylaod(t *testing.T) {
|
||||
|
||||
sut := Mapper{}
|
||||
|
||||
result := sut.GetPayload(serverURL, nil, vuln.CVE, meta)
|
||||
require.Empty(t, result.CISAKnownExploit)
|
||||
require.Empty(t, result.EPSSProbability)
|
||||
require.Empty(t, result.CVSSScore)
|
||||
require.Empty(t, result.CVEPublished)
|
||||
t.Run("does not include EE features", func(t *testing.T) {
|
||||
result := sut.GetPayload(serverURL, nil, vuln.CVE, meta)
|
||||
require.Empty(t, result.CISAKnownExploit)
|
||||
require.Empty(t, result.EPSSProbability)
|
||||
require.Empty(t, result.CVSSScore)
|
||||
require.Empty(t, result.CVEPublished)
|
||||
})
|
||||
|
||||
t.Run("host payload only includes valid software paths", func(t *testing.T) {
|
||||
hosts := []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "host1",
|
||||
DisplayName: "d-host1",
|
||||
SoftwareInstalledPaths: []string{
|
||||
"",
|
||||
"/some/path",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Hostname: "host2",
|
||||
DisplayName: "d-host2",
|
||||
SoftwareInstalledPaths: nil,
|
||||
},
|
||||
}
|
||||
result := sut.GetPayload(serverURL, hosts, vuln.CVE, meta)
|
||||
require.ElementsMatch(t, result.Hosts, []*hostPayloadPart{
|
||||
{
|
||||
ID: uint(1),
|
||||
Hostname: "host1",
|
||||
DisplayName: "d-host1",
|
||||
URL: "http://mywebsite.com/hosts/1",
|
||||
SoftwareInstalledPaths: []string{"/some/path"},
|
||||
},
|
||||
{
|
||||
ID: uint(2),
|
||||
Hostname: "host2",
|
||||
DisplayName: "d-host2",
|
||||
URL: "http://mywebsite.com/hosts/2",
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -36,14 +36,13 @@ func TriggerVulnerabilitiesWebhook(
|
||||
targetURL := vulnConfig.DestinationURL
|
||||
batchSize := vulnConfig.HostBatchSize
|
||||
|
||||
// TODO JUAN: Handle OS Vulns
|
||||
groups := make(map[string][]uint)
|
||||
cveGrouped := make(map[string][]uint)
|
||||
for _, v := range args.Vulnerablities {
|
||||
groups[v.GetCVE()] = append(groups[v.GetCVE()], v.Affected())
|
||||
cveGrouped[v.GetCVE()] = append(cveGrouped[v.GetCVE()], v.Affected())
|
||||
}
|
||||
|
||||
for cve, sIDs := range groups {
|
||||
hosts, err := ds.HostsBySoftwareIDs(ctx, sIDs)
|
||||
for cve, sIDs := range cveGrouped {
|
||||
hosts, err := ds.HostVulnSummariesBySoftwareIDs(ctx, sIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get hosts by software ids")
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func TestTriggerVulnerabilitiesWebhook(t *testing.T) {
|
||||
t.Run("trigger requests", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
hosts := []*fleet.HostShort{
|
||||
hosts := []fleet.HostVulnerabilitySummary{
|
||||
{ID: 1, Hostname: "h1", DisplayName: "d1"},
|
||||
{ID: 2, Hostname: "h2", DisplayName: "d2"},
|
||||
{ID: 3, Hostname: "h3", DisplayName: "d3"},
|
||||
@ -105,7 +105,7 @@ func TestTriggerVulnerabilitiesWebhook(t *testing.T) {
|
||||
name string
|
||||
vulns []fleet.SoftwareVulnerability
|
||||
meta map[string]fleet.CVEMeta
|
||||
hosts []*fleet.HostShort
|
||||
hosts []fleet.HostVulnerabilitySummary
|
||||
want string
|
||||
}{
|
||||
{
|
||||
@ -178,7 +178,7 @@ func TestTriggerVulnerabilitiesWebhook(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
ds.HostsBySoftwareIDsFunc = func(ctx context.Context, softwareIDs []uint) ([]*fleet.HostShort, error) {
|
||||
ds.HostVulnSummariesBySoftwareIDsFunc = func(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return c.hosts, nil
|
||||
}
|
||||
|
||||
@ -194,8 +194,8 @@ func TestTriggerVulnerabilitiesWebhook(t *testing.T) {
|
||||
err := TriggerVulnerabilitiesWebhook(ctx, ds, logger, args, &mapper)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, ds.HostsBySoftwareIDsFuncInvoked)
|
||||
ds.HostsBySoftwareIDsFuncInvoked = false
|
||||
assert.True(t, ds.HostVulnSummariesBySoftwareIDsFuncInvoked)
|
||||
ds.HostVulnSummariesBySoftwareIDsFuncInvoked = false
|
||||
|
||||
want := strings.Split(c.want, "\n")
|
||||
assert.ElementsMatch(t, want, requests)
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
|
||||
func TestJiraFailer(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
return []*fleet.HostShort{{ID: 1, Hostname: "test"}}, nil
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return []fleet.HostVulnerabilitySummary{{ID: 1, Hostname: "test"}}, nil
|
||||
}
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return &fleet.AppConfig{Integrations: fleet.Integrations{
|
||||
@ -73,7 +73,7 @@ func TestJiraFailer(t *testing.T) {
|
||||
cves := []string{"CVE-2018-1234", "CVE-2019-1234", "CVE-2020-1234", "CVE-2021-1234"}
|
||||
for i := 0; i < 10; i++ {
|
||||
cve := cves[i%len(cves)]
|
||||
err := jira.Run(license.NewContext(context.Background(), &fleet.LicenseInfo{Tier: fleet.TierFree}), json.RawMessage(fmt.Sprintf(`{"cve":%q}`, cve)))
|
||||
err := jira.Run(license.NewContext(context.Background(), &fleet.LicenseInfo{Tier: fleet.TierFree}), json.RawMessage(fmt.Sprintf(`{"vulnerability":{"cve":%q}}`, cve)))
|
||||
if err != nil {
|
||||
failedIndices = append(failedIndices, i)
|
||||
}
|
||||
@ -89,8 +89,8 @@ func TestJiraFailer(t *testing.T) {
|
||||
|
||||
func TestZendeskFailer(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
return []*fleet.HostShort{{ID: 1, Hostname: "test"}}, nil
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return []fleet.HostVulnerabilitySummary{{ID: 1, Hostname: "test"}}, nil
|
||||
}
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return &fleet.AppConfig{Integrations: fleet.Integrations{
|
||||
@ -132,7 +132,7 @@ func TestZendeskFailer(t *testing.T) {
|
||||
cves := []string{"CVE-2018-1234", "CVE-2019-1234", "CVE-2020-1234", "CVE-2021-1234"}
|
||||
for i := 0; i < 10; i++ {
|
||||
cve := cves[i%len(cves)]
|
||||
err := zendesk.Run(license.NewContext(context.Background(), &fleet.LicenseInfo{Tier: fleet.TierFree}), json.RawMessage(fmt.Sprintf(`{"cve":%q}`, cve)))
|
||||
err := zendesk.Run(license.NewContext(context.Background(), &fleet.LicenseInfo{Tier: fleet.TierFree}), json.RawMessage(fmt.Sprintf(`{"vulnerability":{"cve":%q}}`, cve)))
|
||||
if err != nil {
|
||||
failedIndices = append(failedIndices, i)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
@ -58,6 +59,9 @@ Affected hosts:
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
{{ range slice .Hosts 0 $end }}
|
||||
* [{{ .DisplayName }}|{{ $.FleetURL }}/hosts/{{ .ID }}]
|
||||
{{ range $path := .SoftwareInstalledPaths }}
|
||||
** {{ $path }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
View the affected software and more affected hosts:
|
||||
@ -96,7 +100,7 @@ type jiraVulnTplArgs struct {
|
||||
NVDURL string
|
||||
FleetURL string
|
||||
CVE string
|
||||
Hosts []*fleet.HostShort
|
||||
Hosts []fleet.HostVulnerabilitySummary
|
||||
|
||||
IsPremium bool
|
||||
|
||||
@ -220,9 +224,6 @@ 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"`
|
||||
}
|
||||
@ -265,15 +266,22 @@ func (j *Jira) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
||||
func (j *Jira) runVuln(ctx context.Context, cli JiraClient, args jiraArgs) error {
|
||||
vargs := args.Vulnerability
|
||||
if vargs == nil {
|
||||
// support the old format of vulnerability args, where only the CVE
|
||||
// is provided.
|
||||
vargs = &vulnArgs{
|
||||
CVE: args.CVE,
|
||||
}
|
||||
return errors.New("invalid job args")
|
||||
}
|
||||
|
||||
var hosts []fleet.HostVulnerabilitySummary
|
||||
var err error
|
||||
|
||||
// Default to deprecated method in case we are processing an 'old' job payload
|
||||
// we are deprecating this because of performance reasons - querying by software_id should be
|
||||
// way more efficient than by CVE.
|
||||
if len(vargs.AffectedSoftwareIDs) == 0 {
|
||||
hosts, err = j.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
} else {
|
||||
hosts, err = j.Datastore.HostVulnSummariesBySoftwareIDs(ctx, vargs.AffectedSoftwareIDs)
|
||||
}
|
||||
hosts, err := j.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "find hosts by cve")
|
||||
return ctxerr.Wrap(ctx, err, "fetching hosts")
|
||||
}
|
||||
|
||||
tplArgs := &jiraVulnTplArgs{
|
||||
@ -374,13 +382,13 @@ func QueueJiraVulnJobs(
|
||||
sort.Strings(cves)
|
||||
level.Debug(logger).Log("recent_cves", fmt.Sprintf("%v", cves))
|
||||
|
||||
uniqCVEs := make(map[string]bool)
|
||||
cveGrouped := make(map[string][]uint)
|
||||
for _, v := range recentVulns {
|
||||
uniqCVEs[v.GetCVE()] = true
|
||||
cveGrouped[v.GetCVE()] = append(cveGrouped[v.GetCVE()], v.Affected())
|
||||
}
|
||||
|
||||
for cve := range uniqCVEs {
|
||||
args := vulnArgs{CVE: cve}
|
||||
for cve, sIDs := range cveGrouped {
|
||||
args := vulnArgs{CVE: cve, AffectedSoftwareIDs: sIDs}
|
||||
if meta, ok := cveMeta[cve]; ok {
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
|
@ -23,11 +23,15 @@ import (
|
||||
|
||||
func TestJiraRun(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
return []*fleet.HostShort{
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "test",
|
||||
SoftwareInstalledPaths: []string{
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -54,7 +58,8 @@ func TestJiraRun(t *testing.T) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
var expectedSummary, expectedDescription, expectedNotInDescription string
|
||||
var expectedSummary, expectedNotInDescription string
|
||||
var expectedDescription []string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(501)
|
||||
@ -71,8 +76,10 @@ func TestJiraRun(t *testing.T) {
|
||||
if expectedSummary != "" {
|
||||
require.Contains(t, string(body), expectedSummary)
|
||||
}
|
||||
if expectedDescription != "" {
|
||||
require.Contains(t, string(body), expectedDescription)
|
||||
if len(expectedDescription) != 0 {
|
||||
for _, s := range expectedDescription {
|
||||
require.Contains(t, string(body), s)
|
||||
}
|
||||
}
|
||||
if expectedNotInDescription != "" {
|
||||
fmt.Println(string(body))
|
||||
@ -105,23 +112,20 @@ func TestJiraRun(t *testing.T) {
|
||||
licenseTier string
|
||||
payload string
|
||||
expectedSummary string
|
||||
expectedDescription 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:",
|
||||
[]string{
|
||||
"Affected hosts:",
|
||||
"https://fleetdm.com/hosts/1",
|
||||
"** /some/path/1",
|
||||
"** /some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -129,7 +133,12 @@ func TestJiraRun(t *testing.T) {
|
||||
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:",
|
||||
[]string{
|
||||
"Affected hosts:",
|
||||
"https://fleetdm.com/hosts/1",
|
||||
"** /some/path/1",
|
||||
"** /some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -137,7 +146,7 @@ func TestJiraRun(t *testing.T) {
|
||||
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",
|
||||
[]string{"\\u0026policy_id=1\\u0026policy_response=failing"},
|
||||
"\\u0026team_id=",
|
||||
},
|
||||
{
|
||||
@ -145,23 +154,20 @@ func TestJiraRun(t *testing.T) {
|
||||
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",
|
||||
[]string{"\\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:",
|
||||
[]string{
|
||||
"Affected hosts:",
|
||||
"https://fleetdm.com/hosts/1",
|
||||
"** /some/path/1",
|
||||
"** /some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -169,7 +175,12 @@ func TestJiraRun(t *testing.T) {
|
||||
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",
|
||||
[]string{
|
||||
"Affected hosts:",
|
||||
"https://fleetdm.com/hosts/1",
|
||||
"** /some/path/1",
|
||||
"** /some/path/2",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
@ -177,7 +188,13 @@ func TestJiraRun(t *testing.T) {
|
||||
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",
|
||||
[]string{
|
||||
"Affected hosts:",
|
||||
"https://fleetdm.com/hosts/1",
|
||||
"** /some/path/1",
|
||||
"** /some/path/2",
|
||||
"Published (reported by [NVD|https://nvd.nist.gov/]): 2012-04-23",
|
||||
},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
@ -48,11 +48,12 @@ 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
|
||||
CVEPublished *time.Time `json:"cve_published,omitempty"` // Premium feature only
|
||||
CVE string `json:"cve,omitempty"`
|
||||
AffectedSoftwareIDs []uint `json:"affected_software,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.
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
@ -59,6 +60,9 @@ Affected hosts:
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
{{ range slice .Hosts 0 $end }}
|
||||
* [{{ .DisplayName }}]({{ $.FleetURL }}/hosts/{{ .ID }})
|
||||
{{ range $path := .SoftwareInstalledPaths }}
|
||||
* {{ $path }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
View the affected software and more affected hosts:
|
||||
@ -97,7 +101,7 @@ type zendeskVulnTplArgs struct {
|
||||
NVDURL string
|
||||
FleetURL string
|
||||
CVE string
|
||||
Hosts []*fleet.HostShort
|
||||
Hosts []fleet.HostVulnerabilitySummary
|
||||
|
||||
IsPremium bool
|
||||
|
||||
@ -222,9 +226,6 @@ 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"`
|
||||
}
|
||||
@ -267,15 +268,23 @@ func (z *Zendesk) Run(ctx context.Context, argsJSON json.RawMessage) error {
|
||||
func (z *Zendesk) runVuln(ctx context.Context, cli ZendeskClient, args zendeskArgs) error {
|
||||
vargs := args.Vulnerability
|
||||
if vargs == nil {
|
||||
// support the old format of vulnerability args, where only the CVE
|
||||
// is provided.
|
||||
vargs = &vulnArgs{
|
||||
CVE: args.CVE,
|
||||
}
|
||||
return errors.New("invalid job args")
|
||||
}
|
||||
hosts, err := z.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
|
||||
var hosts []fleet.HostVulnerabilitySummary
|
||||
var err error
|
||||
|
||||
// Default to deprecated method in case we are processing an 'old' job payload
|
||||
// we are deprecating this because of performance reasons - querying by software_id should be
|
||||
// way more efficient than by CVE.
|
||||
if len(vargs.AffectedSoftwareIDs) == 0 {
|
||||
hosts, err = z.Datastore.HostsByCVE(ctx, vargs.CVE)
|
||||
} else {
|
||||
hosts, err = z.Datastore.HostVulnSummariesBySoftwareIDs(ctx, vargs.AffectedSoftwareIDs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "find hosts by cve")
|
||||
return ctxerr.Wrap(ctx, err, "fetching hosts")
|
||||
}
|
||||
|
||||
tplArgs := &zendeskVulnTplArgs{
|
||||
@ -369,13 +378,13 @@ func QueueZendeskVulnJobs(
|
||||
sort.Strings(cves)
|
||||
level.Debug(logger).Log("recent_cves", fmt.Sprintf("%v", cves))
|
||||
|
||||
uniqCVEs := make(map[string]bool)
|
||||
cveGrouped := make(map[string][]uint)
|
||||
for _, v := range recentVulns {
|
||||
uniqCVEs[v.GetCVE()] = true
|
||||
cveGrouped[v.GetCVE()] = append(cveGrouped[v.GetCVE()], v.Affected())
|
||||
}
|
||||
|
||||
for cve := range uniqCVEs {
|
||||
args := vulnArgs{CVE: cve}
|
||||
for cve, sIDs := range cveGrouped {
|
||||
args := vulnArgs{CVE: cve, AffectedSoftwareIDs: sIDs}
|
||||
if meta, ok := cveMeta[cve]; ok {
|
||||
args.EPSSProbability = meta.EPSSProbability
|
||||
args.CVSSScore = meta.CVSSScore
|
||||
|
@ -22,11 +22,15 @@ import (
|
||||
|
||||
func TestZendeskRun(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
return []*fleet.HostShort{
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
return []fleet.HostVulnerabilitySummary{
|
||||
{
|
||||
ID: 1,
|
||||
Hostname: "test",
|
||||
SoftwareInstalledPaths: []string{
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -53,7 +57,8 @@ func TestZendeskRun(t *testing.T) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
var expectedSubject, expectedDescription, expectedNotInDescription string
|
||||
var expectedSubject, expectedNotInDescription string
|
||||
var expectedDescription []string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(501)
|
||||
@ -69,8 +74,10 @@ func TestZendeskRun(t *testing.T) {
|
||||
if expectedSubject != "" {
|
||||
require.Contains(t, string(body), expectedSubject)
|
||||
}
|
||||
if expectedDescription != "" {
|
||||
require.Contains(t, string(body), expectedDescription)
|
||||
if len(expectedDescription) != 0 {
|
||||
for _, s := range expectedDescription {
|
||||
require.Contains(t, string(body), s)
|
||||
}
|
||||
}
|
||||
if expectedNotInDescription != "" {
|
||||
require.NotContains(t, string(body), expectedNotInDescription)
|
||||
@ -90,23 +97,19 @@ func TestZendeskRun(t *testing.T) {
|
||||
licenseTier string
|
||||
payload string
|
||||
expectedSubject string
|
||||
expectedDescription 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`,
|
||||
[]string{
|
||||
`"group_id":123`,
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -114,7 +117,11 @@ func TestZendeskRun(t *testing.T) {
|
||||
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`,
|
||||
[]string{
|
||||
`"group_id":123`,
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -122,7 +129,7 @@ func TestZendeskRun(t *testing.T) {
|
||||
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",
|
||||
[]string{"\\u0026policy_id=1\\u0026policy_response=failing"},
|
||||
"\\u0026team_id=",
|
||||
},
|
||||
{
|
||||
@ -130,23 +137,19 @@ func TestZendeskRun(t *testing.T) {
|
||||
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",
|
||||
[]string{"\\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`,
|
||||
[]string{
|
||||
`"group_id":123`,
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
"Probability of exploit",
|
||||
},
|
||||
{
|
||||
@ -154,7 +157,11 @@ func TestZendeskRun(t *testing.T) {
|
||||
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",
|
||||
[]string{
|
||||
"Probability of exploit",
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
@ -162,7 +169,11 @@ func TestZendeskRun(t *testing.T) {
|
||||
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",
|
||||
[]string{
|
||||
"Published (reported by [NVD|https://nvd.nist.gov/]): 2012-04-23",
|
||||
"/some/path/1",
|
||||
"/some/path/2",
|
||||
},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
@ -80,10 +80,10 @@ func main() {
|
||||
logger := kitlog.NewLogfmtLogger(os.Stdout)
|
||||
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
hosts := make([]*fleet.HostShort, *hostsCount)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
hosts := make([]fleet.HostVulnerabilitySummary, *hostsCount)
|
||||
for i := 0; i < *hostsCount; i++ {
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
hosts[i] = fleet.HostVulnerabilitySummary{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
@ -80,10 +80,10 @@ func main() {
|
||||
logger := kitlog.NewLogfmtLogger(os.Stdout)
|
||||
|
||||
ds := new(mock.Store)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]*fleet.HostShort, error) {
|
||||
hosts := make([]*fleet.HostShort, *hostsCount)
|
||||
ds.HostsByCVEFunc = func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error) {
|
||||
hosts := make([]fleet.HostVulnerabilitySummary, *hostsCount)
|
||||
for i := 0; i < *hostsCount; i++ {
|
||||
hosts[i] = &fleet.HostShort{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
hosts[i] = fleet.HostVulnerabilitySummary{ID: uint(i + 1), Hostname: fmt.Sprintf("host-test-%d", i+1), DisplayName: fmt.Sprintf("host-test-%d", i+1)}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user