mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
16475 vuln detail api (#16828)
This commit is contained in:
parent
de40fa6988
commit
97cc6b844f
@ -20,3 +20,7 @@ func (svc *Service) ListVulnerabilities(ctx context.Context, opt fleet.VulnListO
|
||||
opt.IsEE = true
|
||||
return svc.Service.ListVulnerabilities(ctx, opt)
|
||||
}
|
||||
|
||||
func (svc *Service) Vulnerability(ctx context.Context, cve string, teamID *uint, useCVSScores bool) (*fleet.VulnerabilityWithMetadata, error) {
|
||||
return svc.Service.Vulnerability(ctx, cve, teamID, true)
|
||||
}
|
||||
|
@ -2,14 +2,167 @@ package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint, includeCVEScores bool) (*fleet.VulnerabilityWithMetadata, error) {
|
||||
var vuln fleet.VulnerabilityWithMetadata
|
||||
|
||||
eeSelectStmt := `
|
||||
SELECT
|
||||
vhc.cve,
|
||||
MIN(COALESCE(osv.created_at, sc.created_at, NOW())) AS created_at,
|
||||
COALESCE(osv.source, sc.source, 0) AS source,
|
||||
cm.cvss_score,
|
||||
cm.epss_probability,
|
||||
cm.cisa_known_exploit,
|
||||
cm.published,
|
||||
COALESCE(cm.description, '') AS description,
|
||||
vhc.host_count,
|
||||
vhc.updated_at as host_count_updated_at
|
||||
FROM
|
||||
vulnerability_host_counts vhc
|
||||
LEFT JOIN cve_meta cm ON cm.cve = vhc.cve
|
||||
LEFT JOIN operating_system_vulnerabilities osv ON osv.cve = vhc.cve
|
||||
LEFT JOIN software_cve sc ON sc.cve = vhc.cve
|
||||
WHERE vhc.cve = ?
|
||||
`
|
||||
eeGroupBy := " GROUP BY vhc.cve, source, cm.cvss_score, cm.epss_probability, cm.cisa_known_exploit, cm.published, description, vhc.host_count, host_count_updated_at"
|
||||
|
||||
freeSelectStmt := `
|
||||
SELECT
|
||||
vhc.cve,
|
||||
MIN(COALESCE(osv.created_at, sc.created_at, NOW())) AS created_at,
|
||||
COALESCE(osv.source, sc.source, 0) AS source,
|
||||
vhc.host_count,
|
||||
vhc.updated_at as host_count_updated_at
|
||||
FROM
|
||||
vulnerability_host_counts vhc
|
||||
LEFT JOIN operating_system_vulnerabilities osv ON osv.cve = vhc.cve
|
||||
LEFT JOIN software_cve sc ON sc.cve = vhc.cve
|
||||
WHERE vhc.cve = ?
|
||||
`
|
||||
|
||||
freeGroupBy := " GROUP BY vhc.cve, source, vhc.host_count, host_count_updated_at"
|
||||
|
||||
var args []interface{}
|
||||
args = append(args, cve)
|
||||
|
||||
if teamID != nil {
|
||||
eeSelectStmt += " AND vhc.team_id = ?"
|
||||
freeSelectStmt += " AND vhc.team_id = ?"
|
||||
args = append(args, *teamID)
|
||||
} else {
|
||||
eeSelectStmt += " AND vhc.team_id = 0"
|
||||
freeSelectStmt += " AND vhc.team_id = 0"
|
||||
}
|
||||
|
||||
eeSelectStmt += eeGroupBy
|
||||
freeSelectStmt += freeGroupBy
|
||||
|
||||
var selectStmt string
|
||||
if includeCVEScores {
|
||||
selectStmt = eeSelectStmt
|
||||
} else {
|
||||
selectStmt = freeSelectStmt
|
||||
}
|
||||
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &vuln, selectStmt, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("Vulnerability").WithName(cve))
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "fetching vulnerability")
|
||||
}
|
||||
return &vuln, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) (vos []*fleet.VulnerableOS, updatedAt time.Time, err error) {
|
||||
osvs, err := ds.OSVersions(ctx, teamID, nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, updatedAt, ctxerr.Wrap(ctx, err, "fetching OS versions by CVE")
|
||||
}
|
||||
|
||||
updatedAt = osvs.CountsUpdatedAt
|
||||
|
||||
var osVersionWithResolved []struct {
|
||||
OSVersionID uint `db:"os_version_id"`
|
||||
ResolvedVersion *string `db:"resolved_in_version"`
|
||||
}
|
||||
|
||||
selectStmt := `
|
||||
SELECT os.os_version_id, osv.resolved_in_version
|
||||
FROM operating_system_vulnerabilities osv
|
||||
JOIN operating_systems os ON os.id = osv.operating_system_id
|
||||
WHERE osv.cve = ?
|
||||
`
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &osVersionWithResolved, selectStmt, cve)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, updatedAt, ctxerr.Wrap(ctx, notFound("Vulnerability").WithName(cve))
|
||||
}
|
||||
return vos, updatedAt, ctxerr.Wrap(ctx, err, "fetching OS versions by CVE")
|
||||
}
|
||||
|
||||
for _, osv := range osvs.OSVersions {
|
||||
for _, id := range osVersionWithResolved {
|
||||
if osv.OSVersionID == id.OSVersionID {
|
||||
vos = append(vos, &fleet.VulnerableOS{
|
||||
OSVersion: osv,
|
||||
ResolvedInVersion: id.ResolvedVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ds *Datastore) SoftwareByCVE(ctx context.Context, cve string, teamID *uint) (vs []*fleet.VulnerableSoftware, updatedAt time.Time, err error) {
|
||||
var args []interface{}
|
||||
selectStmt := `
|
||||
SELECT
|
||||
s.id,
|
||||
s.name,
|
||||
s.version,
|
||||
s.source,
|
||||
s.browser,
|
||||
COALESCE(scpe.cpe, '') as generated_cpe,
|
||||
COALESCE(shc.hosts_count, 0) as hosts_count,
|
||||
COALESCE(sc.resolved_in_version, '') as resolved_in_version
|
||||
FROM software s
|
||||
JOIN software_cve sc ON sc.software_id = s.id
|
||||
LEFT JOIN software_cpe scpe ON scpe.software_id = s.id
|
||||
LEFT JOIN software_host_counts shc ON shc.software_id = s.id
|
||||
WHERE sc.cve = ?
|
||||
`
|
||||
args = append(args, cve)
|
||||
|
||||
if teamID != nil {
|
||||
selectStmt += " AND shc.team_id = ?"
|
||||
args = append(args, *teamID)
|
||||
} else {
|
||||
selectStmt += " AND shc.team_id = 0"
|
||||
}
|
||||
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &vs, selectStmt, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, updatedAt, ctxerr.Wrap(ctx, notFound("Vulnerability").WithName(cve))
|
||||
}
|
||||
return vs, updatedAt, ctxerr.Wrap(ctx, err, "fetching software by CVE")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ds *Datastore) ListVulnerabilities(ctx context.Context, opt fleet.VulnListOptions) ([]fleet.VulnerabilityWithMetadata, *fleet.PaginationMetadata, error) {
|
||||
// Define base select statements for EE and Free versions
|
||||
eeSelectStmt := `
|
||||
@ -62,9 +215,10 @@ func (ds *Datastore) ListVulnerabilities(ctx context.Context, opt fleet.VulnList
|
||||
cm.cisa_known_exploit,
|
||||
cm.published,
|
||||
description,
|
||||
vhc.host_count
|
||||
vhc.host_count,
|
||||
host_count_updated_at
|
||||
`
|
||||
freeGroupBy := " GROUP BY vhc.cve, source, vhc.host_count"
|
||||
freeGroupBy := " GROUP BY vhc.cve, source, vhc.host_count, host_count_updated_at"
|
||||
|
||||
// Choose the appropriate group by statement based on EE or Free
|
||||
var groupBy string
|
||||
@ -91,14 +245,6 @@ func (ds *Datastore) ListVulnerabilities(ctx context.Context, opt fleet.VulnList
|
||||
selectStmt, args = searchLike(selectStmt, args, match, "vhc.cve")
|
||||
}
|
||||
|
||||
if opt.KnownExploit {
|
||||
selectStmt = selectStmt + " AND cm.cisa_known_exploit = 1"
|
||||
}
|
||||
|
||||
if match := opt.MatchQuery; match != "" {
|
||||
selectStmt, args = searchLike(selectStmt, args, match, "vhc.cve")
|
||||
}
|
||||
|
||||
// Append group by statement
|
||||
selectStmt += groupBy
|
||||
|
||||
|
@ -20,6 +20,10 @@ func TestVulnerabilities(t *testing.T) {
|
||||
fn func(t *testing.T, ds *Datastore)
|
||||
}{
|
||||
{"TestListVulnerabilities", testListVulnerabilities},
|
||||
{"TestVulnerabilityWithOS", testVulnerabilityWithOS},
|
||||
{"TestVulnerabilityWithSoftware", testVulnerabilityWithSoftware},
|
||||
{"TestOSVersionsByCVE", testOSVersionsByCVE},
|
||||
{"TestSoftwareByCVE", testSoftwareByCVE},
|
||||
{"TestVulnerabilitiesPagination", testVulnerabilitiesPagination},
|
||||
{"TestVulnerabilitiesTeamFilter", testVulnerabilitiesTeamFilter},
|
||||
{"TestListVulnerabilitiesSort", testListVulnerabilitiesSort},
|
||||
@ -157,6 +161,169 @@ func testListVulnerabilities(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
}
|
||||
|
||||
func testVulnerabilityWithOS(t *testing.T, ds *Datastore) {
|
||||
mockTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
ctx := context.Background()
|
||||
|
||||
v, err := ds.Vulnerability(ctx, "CVE-2020-1234", nil, false)
|
||||
require.Nil(t, v)
|
||||
require.Error(t, err)
|
||||
var nfe *notFoundError
|
||||
require.ErrorAs(t, err, &nfe)
|
||||
|
||||
// Insert Host Count
|
||||
insertStmt := `
|
||||
INSERT INTO vulnerability_host_counts (cve, team_id, host_count)
|
||||
VALUES (?, ?, ?), (?, ?, ?)
|
||||
`
|
||||
_, err = ds.writer(context.Background()).Exec(insertStmt,
|
||||
"CVE-2020-1234", 0, 10,
|
||||
"CVE-2020-1234", 1, 4,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// // insert OS Vuln
|
||||
_, err = ds.InsertOSVulnerabilities(context.Background(), []fleet.OSVulnerability{
|
||||
{
|
||||
OSID: 1,
|
||||
CVE: "CVE-2020-1234",
|
||||
ResolvedInVersion: ptr.String("1.0.0"),
|
||||
},
|
||||
}, fleet.MSRCSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
// // insert CVEMeta
|
||||
err = ds.InsertCVEMeta(context.Background(), []fleet.CVEMeta{
|
||||
{
|
||||
CVE: "CVE-2020-1234",
|
||||
CVSSScore: ptr.Float64(7.5),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(mockTime),
|
||||
Description: "Test CVE 2020-1234",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := fleet.VulnerabilityWithMetadata{
|
||||
CVEMeta: fleet.CVEMeta{
|
||||
CVE: "CVE-2020-1234",
|
||||
},
|
||||
HostCount: 10,
|
||||
Source: fleet.MSRCSource,
|
||||
}
|
||||
|
||||
// No CVSSScores
|
||||
v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.CVEMeta, v.CVEMeta)
|
||||
require.Equal(t, expected.HostCount, v.HostCount)
|
||||
require.Equal(t, expected.Source, v.Source)
|
||||
|
||||
// Team 1
|
||||
expected.HostCount = 4
|
||||
v, err = ds.Vulnerability(ctx, "CVE-2020-1234", ptr.Uint(1), false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.CVEMeta, v.CVEMeta)
|
||||
require.Equal(t, expected.HostCount, v.HostCount)
|
||||
require.Equal(t, expected.Source, v.Source)
|
||||
|
||||
expected = fleet.VulnerabilityWithMetadata{
|
||||
CVEMeta: fleet.CVEMeta{
|
||||
CVE: "CVE-2020-1234",
|
||||
CVSSScore: ptr.Float64(7.5),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(mockTime),
|
||||
Description: "Test CVE 2020-1234",
|
||||
},
|
||||
HostCount: 10,
|
||||
Source: fleet.MSRCSource,
|
||||
}
|
||||
|
||||
// With CVSSScores
|
||||
v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.CVEMeta, v.CVEMeta)
|
||||
require.Equal(t, expected.HostCount, v.HostCount)
|
||||
require.Equal(t, expected.Source, v.Source)
|
||||
}
|
||||
|
||||
func testVulnerabilityWithSoftware(t *testing.T, ds *Datastore) {
|
||||
mockTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
ctx := context.Background()
|
||||
|
||||
v, err := ds.Vulnerability(ctx, "CVE-2020-1234", nil, false)
|
||||
require.Nil(t, v)
|
||||
require.Error(t, err)
|
||||
var nfe *notFoundError
|
||||
require.ErrorAs(t, err, &nfe)
|
||||
|
||||
// Insert Host Count
|
||||
insertStmt := `
|
||||
INSERT INTO vulnerability_host_counts (cve, team_id, host_count)
|
||||
VALUES (?, ?, ?)
|
||||
`
|
||||
|
||||
_, err = ds.writer(context.Background()).Exec(insertStmt, "CVE-2020-1234", 0, 10)
|
||||
require.NoError(t, err)
|
||||
|
||||
// insert Software Vuln
|
||||
_, err = ds.InsertSoftwareVulnerability(context.Background(), fleet.SoftwareVulnerability{
|
||||
SoftwareID: 1,
|
||||
CVE: "CVE-2020-1234",
|
||||
}, fleet.NVDSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
// insert CVEMeta
|
||||
err = ds.InsertCVEMeta(context.Background(), []fleet.CVEMeta{
|
||||
{
|
||||
CVE: "CVE-2020-1234",
|
||||
CVSSScore: ptr.Float64(7.5),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(mockTime),
|
||||
Description: "Test CVE 2020-1234",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// No CVSSScores
|
||||
expected := fleet.VulnerabilityWithMetadata{
|
||||
CVEMeta: fleet.CVEMeta{
|
||||
CVE: "CVE-2020-1234",
|
||||
},
|
||||
HostCount: 10,
|
||||
Source: fleet.NVDSource,
|
||||
}
|
||||
|
||||
v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.CVEMeta, v.CVEMeta)
|
||||
require.Equal(t, expected.HostCount, v.HostCount)
|
||||
require.Equal(t, expected.Source, v.Source)
|
||||
|
||||
// With CVSSScores
|
||||
expected = fleet.VulnerabilityWithMetadata{
|
||||
CVEMeta: fleet.CVEMeta{
|
||||
CVE: "CVE-2020-1234",
|
||||
CVSSScore: ptr.Float64(7.5),
|
||||
EPSSProbability: ptr.Float64(0.5),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(mockTime),
|
||||
Description: "Test CVE 2020-1234",
|
||||
},
|
||||
HostCount: 10,
|
||||
Source: fleet.NVDSource,
|
||||
}
|
||||
|
||||
v, err = ds.Vulnerability(ctx, "CVE-2020-1234", nil, true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.CVEMeta, v.CVEMeta)
|
||||
require.Equal(t, expected.HostCount, v.HostCount)
|
||||
require.Equal(t, expected.Source, v.Source)
|
||||
}
|
||||
|
||||
func testVulnerabilitiesPagination(t *testing.T, ds *Datastore) {
|
||||
seedVulnerabilities(t, ds)
|
||||
|
||||
@ -629,6 +796,80 @@ func testVulnerabilityHostCountBatchInserts(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
}
|
||||
|
||||
func testOSVersionsByCVE(t *testing.T, ds *Datastore) {
|
||||
seedVulnerabilities(t, ds)
|
||||
|
||||
// global
|
||||
osv, _, err := ds.OSVersionsByCVE(context.Background(), "CVE-2020-1238", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []fleet.VulnerableOS{
|
||||
{
|
||||
OSVersion: fleet.OSVersion{
|
||||
Name: "Microsoft Windows 11 Enterprise 22H2 10.0.22621.2715",
|
||||
NameOnly: "Microsoft Windows 11 Enterprise 22H2",
|
||||
OSVersionID: 1,
|
||||
Version: "10.0.22621.2715",
|
||||
Platform: "windows",
|
||||
HostsCount: 10,
|
||||
},
|
||||
ResolvedInVersion: ptr.String("1.0.0"),
|
||||
},
|
||||
}
|
||||
|
||||
require.Len(t, osv, 1)
|
||||
require.Equal(t, osv[0].OSVersion, expected[0].OSVersion)
|
||||
|
||||
// team 1
|
||||
expected[0].OSVersion.HostsCount = 4
|
||||
osv, _, err = ds.OSVersionsByCVE(context.Background(), "CVE-2020-1238", ptr.Uint(1))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, osv, 1)
|
||||
require.Equal(t, osv[0].OSVersion, expected[0].OSVersion)
|
||||
|
||||
// team 2
|
||||
expected[0].OSVersion.HostsCount = 3
|
||||
osv, _, err = ds.OSVersionsByCVE(context.Background(), "CVE-2020-1238", ptr.Uint(2))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, osv, 1)
|
||||
require.Equal(t, osv[0].OSVersion, expected[0].OSVersion)
|
||||
}
|
||||
|
||||
func testSoftwareByCVE(t *testing.T, ds *Datastore) {
|
||||
seedVulnerabilities(t, ds)
|
||||
|
||||
// global
|
||||
software, _, err := ds.SoftwareByCVE(context.Background(), "CVE-2020-1234", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &fleet.VulnerableSoftware{
|
||||
ID: 1,
|
||||
Name: "Chrome",
|
||||
Version: "1.0.0",
|
||||
Source: "programs",
|
||||
HostsCount: 5,
|
||||
GenerateCPE: "cpe:2.3:a:google:chrome:1.0.0:*:*:*:*:*:*:*:*",
|
||||
ResolvedInVersion: ptr.String("1.0.0"),
|
||||
}
|
||||
|
||||
require.Len(t, software, 1)
|
||||
require.Equal(t, expected, software[0])
|
||||
|
||||
// team 1
|
||||
expected.HostsCount = 4
|
||||
software, _, err = ds.SoftwareByCVE(context.Background(), "CVE-2020-1234", ptr.Uint(1))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, software, 1)
|
||||
require.Equal(t, expected, software[0])
|
||||
|
||||
// team 2
|
||||
expected.HostsCount = 1
|
||||
software, _, err = ds.SoftwareByCVE(context.Background(), "CVE-2020-1234", ptr.Uint(2))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, software, 1)
|
||||
require.Equal(t, expected, software[0])
|
||||
}
|
||||
|
||||
func assertHostCounts(t *testing.T, expected []hostCount, actual []fleet.VulnerabilityWithMetadata) {
|
||||
t.Helper()
|
||||
require.Len(t, actual, len(expected))
|
||||
@ -639,6 +880,88 @@ func assertHostCounts(t *testing.T, expected []hostCount, actual []fleet.Vulnera
|
||||
}
|
||||
|
||||
func seedVulnerabilities(t *testing.T, ds *Datastore) {
|
||||
// insert 20 hosts
|
||||
var hostids []uint
|
||||
for i := 0; i < 20; i++ {
|
||||
host := test.NewHost(t, ds, fmt.Sprintf("host%d", i), fmt.Sprintf("192.168.0.%d", i), fmt.Sprintf("%d", i+1000), fmt.Sprintf("%d", i+1000), time.Now())
|
||||
hostids = append(hostids, host.ID)
|
||||
}
|
||||
|
||||
// update 15 hosts to windows
|
||||
for i := 0; i < 10; i++ {
|
||||
err := ds.UpdateHostOperatingSystem(context.Background(), hostids[i], fleet.OperatingSystem{
|
||||
Name: "Microsoft Windows 11 Enterprise 22H2",
|
||||
Version: "10.0.22621.2715",
|
||||
Arch: "x86_64",
|
||||
Platform: "windows",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// update 5 hosts to macOS
|
||||
for i := 10; i < 15; i++ {
|
||||
err := ds.UpdateHostOperatingSystem(context.Background(), hostids[i], fleet.OperatingSystem{
|
||||
Name: "macOS",
|
||||
Version: "14.1.2",
|
||||
Arch: "arm64",
|
||||
Platform: "darwin",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// move 4 windows hosts to team 1
|
||||
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
|
||||
require.NoError(t, err)
|
||||
err = ds.AddHostsToTeam(context.Background(), &team1.ID, hostids[:4])
|
||||
require.NoError(t, err)
|
||||
|
||||
// move 3 windows hosts to team 2
|
||||
team2, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team2"})
|
||||
require.NoError(t, err)
|
||||
err = ds.AddHostsToTeam(context.Background(), &team2.ID, hostids[4:7])
|
||||
require.NoError(t, err)
|
||||
|
||||
// move 1 macOS host to team 2
|
||||
err = ds.AddHostsToTeam(context.Background(), &team2.ID, []uint{hostids[10]})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ds.UpdateOSVersions(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// State:
|
||||
// 10 global windows hosts
|
||||
// 5 global macOS hosts
|
||||
// 4 windows hosts in team 1
|
||||
// 3 windows hosts in team 2
|
||||
// 1 macOS host in team 2
|
||||
|
||||
// add software to 5 windows hosts
|
||||
// affects:
|
||||
// 5 global windows hosts
|
||||
// 4 windows hosts in team 1
|
||||
// 1 windows host in team 2
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err = ds.UpdateHostSoftware(context.Background(), hostids[i], []fleet.Software{
|
||||
{
|
||||
Name: "Chrome",
|
||||
Version: "1.0.0",
|
||||
Source: "programs",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = ds.UpsertSoftwareCPEs(context.Background(), []fleet.SoftwareCPE{
|
||||
{
|
||||
SoftwareID: 1,
|
||||
CPE: "cpe:2.3:a:google:chrome:1.0.0:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ds.SyncHostsSoftware(context.Background(), time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
softwareVulns := []fleet.SoftwareVulnerability{
|
||||
{
|
||||
SoftwareID: 1,
|
||||
@ -817,7 +1140,7 @@ func seedVulnerabilities(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
|
||||
// Insert OS Vuln
|
||||
_, err := ds.InsertOSVulnerabilities(context.Background(), osVulns, fleet.NVDSource)
|
||||
_, err = ds.InsertOSVulnerabilities(context.Background(), osVulns, fleet.NVDSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert Software Vuln
|
||||
|
@ -310,6 +310,8 @@ type Datastore interface {
|
||||
GetMDMSolution(ctx context.Context, mdmID uint) (*MDMSolution, error)
|
||||
|
||||
OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*OSVersions, error)
|
||||
OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) ([]*VulnerableOS, time.Time, error)
|
||||
SoftwareByCVE(ctx context.Context, cve string, teamID *uint) ([]*VulnerableSoftware, time.Time, error)
|
||||
OSVersion(ctx context.Context, osVersionID uint, teamID *uint) (*OSVersion, *time.Time, error)
|
||||
UpdateOSVersions(ctx context.Context) error
|
||||
|
||||
@ -857,6 +859,8 @@ type Datastore interface {
|
||||
|
||||
// ListVulnerabilities returns a list of unique vulnerabilities based on the provided options.
|
||||
ListVulnerabilities(ctx context.Context, opt VulnListOptions) ([]VulnerabilityWithMetadata, *PaginationMetadata, error)
|
||||
// Vulnerability returns the vulnerability corresponding to the specified CVE ID
|
||||
Vulnerability(ctx context.Context, cve string, teamID *uint, includeCVEScores bool) (*VulnerabilityWithMetadata, error)
|
||||
// CountVulnerabilities returns the number of unique vulnerabilities based on the provided
|
||||
// options.
|
||||
CountVulnerabilities(ctx context.Context, opt VulnListOptions) (uint, error)
|
||||
|
@ -1125,6 +1125,11 @@ type OSVersions struct {
|
||||
OSVersions []OSVersion `json:"os_versions"`
|
||||
}
|
||||
|
||||
type VulnerableOS struct {
|
||||
OSVersion
|
||||
ResolvedInVersion *string `json:"resolved_in_version"`
|
||||
}
|
||||
|
||||
type OSVersion struct {
|
||||
// ID is the unique id of the operating system.
|
||||
ID uint `json:"id,omitempty"`
|
||||
|
@ -610,8 +610,14 @@ type Service interface {
|
||||
|
||||
// ListVulnerabilities returns a list of vulnerabilities based on the provided options.
|
||||
ListVulnerabilities(ctx context.Context, opt VulnListOptions) ([]VulnerabilityWithMetadata, *PaginationMetadata, error)
|
||||
// ListVulnerability returns a vulnerability based on the provided CVE.
|
||||
Vulnerability(ctx context.Context, cve string, teamID *uint, useCVSScores bool) (*VulnerabilityWithMetadata, error)
|
||||
// CountVulnerabilities returns the number of vulnerabilities based on the provided options.
|
||||
CountVulnerabilities(ctx context.Context, opt VulnListOptions) (uint, error)
|
||||
// ListOSVersionsByCVE returns a list of OS versions affected by the provided CVE.
|
||||
ListOSVersionsByCVE(ctx context.Context, cve string, teamID *uint) (result []*VulnerableOS, updatedAt time.Time, err error)
|
||||
// ListSoftwareByCVE returns a list of software affected by the provided CVE.
|
||||
ListSoftwareByCVE(ctx context.Context, cve string, teamID *uint) (result []*VulnerableSoftware, updatedAt time.Time, err error)
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Team Policies
|
||||
|
@ -103,6 +103,17 @@ func (s Software) ToUniqueStr() string {
|
||||
return strings.Join(ss, SoftwareFieldSeparator)
|
||||
}
|
||||
|
||||
type VulnerableSoftware struct {
|
||||
ID uint `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Version string `json:"version" db:"version"`
|
||||
Source string `json:"source" db:"source"`
|
||||
Browser string `json:"browser" db:"browser"`
|
||||
GenerateCPE string `json:"generated_cpe" db:"generated_cpe"`
|
||||
HostsCount int `json:"hosts_count,omitempty" db:"hosts_count"`
|
||||
ResolvedInVersion *string `json:"resolved_in_version" db:"resolved_in_version"`
|
||||
}
|
||||
|
||||
type SliceString []string
|
||||
|
||||
func (c *SliceString) Scan(v interface{}) error {
|
||||
|
@ -244,6 +244,10 @@ type GetMDMSolutionFunc func(ctx context.Context, mdmID uint) (*fleet.MDMSolutio
|
||||
|
||||
type OSVersionsFunc func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error)
|
||||
|
||||
type OSVersionsByCVEFunc func(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableOS, time.Time, error)
|
||||
|
||||
type SoftwareByCVEFunc func(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableSoftware, time.Time, error)
|
||||
|
||||
type OSVersionFunc func(ctx context.Context, osVersionID uint, teamID *uint) (*fleet.OSVersion, *time.Time, error)
|
||||
|
||||
type UpdateOSVersionsFunc func(ctx context.Context) error
|
||||
@ -594,6 +598,8 @@ type DeleteOutOfDateOSVulnerabilitiesFunc func(ctx context.Context, source fleet
|
||||
|
||||
type ListVulnerabilitiesFunc func(ctx context.Context, opt fleet.VulnListOptions) ([]fleet.VulnerabilityWithMetadata, *fleet.PaginationMetadata, error)
|
||||
|
||||
type VulnerabilityFunc func(ctx context.Context, cve string, teamID *uint, includeCVEScores bool) (*fleet.VulnerabilityWithMetadata, error)
|
||||
|
||||
type CountVulnerabilitiesFunc func(ctx context.Context, opt fleet.VulnListOptions) (uint, error)
|
||||
|
||||
type UpdateVulnerabilityHostCountsFunc func(ctx context.Context) error
|
||||
@ -1166,6 +1172,12 @@ type DataStore struct {
|
||||
OSVersionsFunc OSVersionsFunc
|
||||
OSVersionsFuncInvoked bool
|
||||
|
||||
OSVersionsByCVEFunc OSVersionsByCVEFunc
|
||||
OSVersionsByCVEFuncInvoked bool
|
||||
|
||||
SoftwareByCVEFunc SoftwareByCVEFunc
|
||||
SoftwareByCVEFuncInvoked bool
|
||||
|
||||
OSVersionFunc OSVersionFunc
|
||||
OSVersionFuncInvoked bool
|
||||
|
||||
@ -1691,6 +1703,9 @@ type DataStore struct {
|
||||
ListVulnerabilitiesFunc ListVulnerabilitiesFunc
|
||||
ListVulnerabilitiesFuncInvoked bool
|
||||
|
||||
VulnerabilityFunc VulnerabilityFunc
|
||||
VulnerabilityFuncInvoked bool
|
||||
|
||||
CountVulnerabilitiesFunc CountVulnerabilitiesFunc
|
||||
CountVulnerabilitiesFuncInvoked bool
|
||||
|
||||
@ -2833,6 +2848,20 @@ func (s *DataStore) OSVersions(ctx context.Context, teamID *uint, platform *stri
|
||||
return s.OSVersionsFunc(ctx, teamID, platform, name, version)
|
||||
}
|
||||
|
||||
func (s *DataStore) OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableOS, time.Time, error) {
|
||||
s.mu.Lock()
|
||||
s.OSVersionsByCVEFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.OSVersionsByCVEFunc(ctx, cve, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) SoftwareByCVE(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableSoftware, time.Time, error) {
|
||||
s.mu.Lock()
|
||||
s.SoftwareByCVEFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SoftwareByCVEFunc(ctx, cve, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) OSVersion(ctx context.Context, osVersionID uint, teamID *uint) (*fleet.OSVersion, *time.Time, error) {
|
||||
s.mu.Lock()
|
||||
s.OSVersionFuncInvoked = true
|
||||
@ -4058,6 +4087,13 @@ func (s *DataStore) ListVulnerabilities(ctx context.Context, opt fleet.VulnListO
|
||||
return s.ListVulnerabilitiesFunc(ctx, opt)
|
||||
}
|
||||
|
||||
func (s *DataStore) Vulnerability(ctx context.Context, cve string, teamID *uint, includeCVEScores bool) (*fleet.VulnerabilityWithMetadata, error) {
|
||||
s.mu.Lock()
|
||||
s.VulnerabilityFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.VulnerabilityFunc(ctx, cve, teamID, includeCVEScores)
|
||||
}
|
||||
|
||||
func (s *DataStore) CountVulnerabilities(ctx context.Context, opt fleet.VulnListOptions) (uint, error) {
|
||||
s.mu.Lock()
|
||||
s.CountVulnerabilitiesFuncInvoked = true
|
||||
|
@ -373,6 +373,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
|
||||
// Vulnerabilities
|
||||
ue.GET("/api/_version_/fleet/vulnerabilities", listVulnerabilitiesEndpoint, listVulnerabilitiesRequest{})
|
||||
ue.GET("/api/_version_/fleet/vulnerabilities/{cve}", getVulnerabilityEndpoint, getVulnerabilityRequest{})
|
||||
|
||||
// Hosts
|
||||
ue.GET("/api/_version_/fleet/host_summary", getHostSummaryEndpoint, getHostSummaryRequest{})
|
||||
|
@ -7400,9 +7400,8 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.ds.UpdateHostOperatingSystem(context.Background(), host.ID, fleet.OperatingSystem{
|
||||
Name: "windows",
|
||||
Name: "Windows 11 Enterprise 22H2",
|
||||
Version: "10.0.19042.1234",
|
||||
Arch: "64bit",
|
||||
Platform: "windows",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@ -7415,18 +7414,33 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
||||
}
|
||||
}
|
||||
|
||||
err = s.ds.UpdateOSVersions(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.ds.InsertOSVulnerability(context.Background(), fleet.OSVulnerability{
|
||||
OSID: os.ID,
|
||||
CVE: "CVE-2021-1234",
|
||||
OSID: os.ID,
|
||||
CVE: "CVE-2021-1234",
|
||||
ResolvedInVersion: *ptr.StringPtr("10.0.19043.2013"),
|
||||
}, fleet.MSRCSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := s.ds.UpdateHostSoftware(context.Background(), host.ID, []fleet.Software{
|
||||
{Name: "foo", Version: "0.0.1", Source: "chrome_extensions"},
|
||||
{Name: "Google Chrome", Version: "0.0.1", Source: "programs"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sw := res.Inserted[0]
|
||||
|
||||
_, err = s.ds.UpsertSoftwareCPEs(context.Background(), []fleet.SoftwareCPE{
|
||||
{
|
||||
SoftwareID: sw.ID,
|
||||
CPE: "cpe:2.3:a:google:chrome:1.0.0:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.ds.SyncHostsSoftware(context.Background(), time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.ds.InsertSoftwareVulnerability(context.Background(), fleet.SoftwareVulnerability{
|
||||
SoftwareID: sw.ID,
|
||||
CVE: "CVE-2021-1235",
|
||||
@ -7515,6 +7529,42 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
||||
require.Equal(t, expectedVuln.DetailsLink, vuln.DetailsLink)
|
||||
require.Empty(t, vuln.CVSSScore)
|
||||
}
|
||||
|
||||
var gResp getVulnerabilityResponse
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities/CVE-2021-1234", nil, http.StatusOK, &gResp)
|
||||
require.Empty(t, gResp.Err)
|
||||
require.Equal(t, "CVE-2021-1234", gResp.Vulnerability.CVE)
|
||||
require.Equal(t, uint(1), gResp.Vulnerability.HostCount)
|
||||
require.Equal(t, "https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-1234", gResp.Vulnerability.DetailsLink)
|
||||
require.Empty(t, gResp.Vulnerability.Description)
|
||||
require.Empty(t, gResp.Vulnerability.CVSSScore)
|
||||
require.Empty(t, gResp.Vulnerability.CISAKnownExploit)
|
||||
require.Empty(t, gResp.Vulnerability.EPSSProbability)
|
||||
require.Empty(t, gResp.Vulnerability.Published)
|
||||
require.Len(t, gResp.OSVersions, 1)
|
||||
require.Equal(t, "Windows 11 Enterprise 22H2 10.0.19042.1234", gResp.OSVersions[0].Name)
|
||||
require.Equal(t, "Windows 11 Enterprise 22H2", gResp.OSVersions[0].NameOnly)
|
||||
require.Equal(t, "windows", gResp.OSVersions[0].Platform)
|
||||
require.Equal(t, "10.0.19042.1234", gResp.OSVersions[0].Version)
|
||||
require.Equal(t, 1, gResp.OSVersions[0].HostsCount)
|
||||
require.Equal(t, "10.0.19043.2013", *gResp.OSVersions[0].ResolvedInVersion)
|
||||
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities/CVE-2021-1235", nil, http.StatusOK, &gResp)
|
||||
require.Empty(t, gResp.Err)
|
||||
require.Equal(t, "CVE-2021-1235", gResp.Vulnerability.CVE)
|
||||
require.Equal(t, uint(1), gResp.Vulnerability.HostCount)
|
||||
require.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2021-1235", gResp.Vulnerability.DetailsLink)
|
||||
require.Empty(t, gResp.Vulnerability.Description)
|
||||
require.Empty(t, gResp.Vulnerability.CVSSScore)
|
||||
require.Empty(t, gResp.Vulnerability.CISAKnownExploit)
|
||||
require.Empty(t, gResp.Vulnerability.EPSSProbability)
|
||||
require.Empty(t, gResp.Vulnerability.Published)
|
||||
require.Len(t, gResp.Software, 1)
|
||||
require.Equal(t, "Google Chrome", gResp.Software[0].Name)
|
||||
require.Equal(t, "0.0.1", gResp.Software[0].Version)
|
||||
require.Equal(t, "programs", gResp.Software[0].Source)
|
||||
require.Equal(t, "cpe:2.3:a:google:chrome:1.0.0:*:*:*:*:*:*:*:*", gResp.Software[0].GenerateCPE)
|
||||
require.Equal(t, 1, gResp.Software[0].HostsCount)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestOSVersions() {
|
||||
|
@ -3163,9 +3163,8 @@ func (s *integrationEnterpriseTestSuite) TestListVulnerabilities() {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.ds.UpdateHostOperatingSystem(context.Background(), host.ID, fleet.OperatingSystem{
|
||||
Name: "windows",
|
||||
Name: "Windows 11 Enterprise 22H2",
|
||||
Version: "10.0.19042.1234",
|
||||
Arch: "64bit",
|
||||
Platform: "windows",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@ -3178,9 +3177,13 @@ func (s *integrationEnterpriseTestSuite) TestListVulnerabilities() {
|
||||
}
|
||||
}
|
||||
|
||||
err = s.ds.UpdateOSVersions(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.ds.InsertOSVulnerability(context.Background(), fleet.OSVulnerability{
|
||||
OSID: os.ID,
|
||||
CVE: "CVE-2021-1234",
|
||||
OSID: os.ID,
|
||||
CVE: "CVE-2021-1234",
|
||||
ResolvedInVersion: ptr.String("10.0.19043.2013"),
|
||||
}, fleet.MSRCSource)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -3299,6 +3302,25 @@ func (s *integrationEnterpriseTestSuite) TestListVulnerabilities() {
|
||||
require.Equal(t, expectedVuln.DetailsLink, vuln.DetailsLink)
|
||||
require.Equal(t, expectedVuln.CVEMeta, vuln.CVEMeta)
|
||||
}
|
||||
|
||||
var gResp getVulnerabilityResponse
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities/CVE-2021-1234", nil, http.StatusOK, &gResp)
|
||||
require.Empty(t, gResp.Err)
|
||||
require.Equal(t, "CVE-2021-1234", gResp.Vulnerability.CVE)
|
||||
require.Equal(t, uint(1), gResp.Vulnerability.HostCount)
|
||||
require.Equal(t, "https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-1234", gResp.Vulnerability.DetailsLink)
|
||||
require.Equal(t, "Test CVE 2021-1234", gResp.Vulnerability.Description)
|
||||
require.Equal(t, ptr.Float64(7.5), gResp.Vulnerability.CVSSScore)
|
||||
require.Equal(t, ptr.Bool(true), gResp.Vulnerability.CISAKnownExploit)
|
||||
require.Equal(t, ptr.Float64(0.5), gResp.Vulnerability.EPSSProbability)
|
||||
require.Equal(t, ptr.Time(mockTime), gResp.Vulnerability.Published)
|
||||
require.Len(t, gResp.OSVersions, 1)
|
||||
require.Equal(t, "Windows 11 Enterprise 22H2 10.0.19042.1234", gResp.OSVersions[0].Name)
|
||||
require.Equal(t, "Windows 11 Enterprise 22H2", gResp.OSVersions[0].NameOnly)
|
||||
require.Equal(t, "windows", gResp.OSVersions[0].Platform)
|
||||
require.Equal(t, "10.0.19042.1234", gResp.OSVersions[0].Version)
|
||||
require.Equal(t, 1, gResp.OSVersions[0].HostsCount)
|
||||
require.Equal(t, "10.0.19043.2013", *gResp.OSVersions[0].ResolvedInVersion)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestOSVersions() {
|
||||
|
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
@ -90,3 +91,79 @@ func (svc *Service) CountVulnerabilities(ctx context.Context, opts fleet.VulnLis
|
||||
|
||||
return svc.ds.CountVulnerabilities(ctx, opts)
|
||||
}
|
||||
|
||||
type getVulnerabilityRequest struct {
|
||||
CVE string `url:"cve"`
|
||||
TeamID *uint `query:"team_id,optional"`
|
||||
}
|
||||
|
||||
type getVulnerabilityResponse struct {
|
||||
Vulnerability *fleet.VulnerabilityWithMetadata `json:"vulnerability"`
|
||||
OSVersions []*fleet.VulnerableOS `json:"os_versions"`
|
||||
Software []*fleet.VulnerableSoftware `json:"software"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r getVulnerabilityResponse) error() error { return r.Err }
|
||||
|
||||
func getVulnerabilityEndpoint(ctx context.Context, req interface{}, svc fleet.Service) (errorer, error) {
|
||||
request := req.(*getVulnerabilityRequest)
|
||||
|
||||
vuln, err := svc.Vulnerability(ctx, request.CVE, request.TeamID, false)
|
||||
if err != nil {
|
||||
return getVulnerabilityResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
if vuln.Source == fleet.MSRCSource {
|
||||
vuln.DetailsLink = fmt.Sprintf("https://msrc.microsoft.com/update-guide/en-US/vulnerability/%s", vuln.CVE)
|
||||
} else {
|
||||
vuln.DetailsLink = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vuln.CVE)
|
||||
}
|
||||
|
||||
osVersions, _, err := svc.ListOSVersionsByCVE(ctx, vuln.CVE, request.TeamID)
|
||||
if err != nil {
|
||||
return getVulnerabilityResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
software, _, err := svc.ListSoftwareByCVE(ctx, vuln.CVE, request.TeamID)
|
||||
if err != nil {
|
||||
return getVulnerabilityResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return getVulnerabilityResponse{
|
||||
Vulnerability: vuln,
|
||||
OSVersions: osVersions,
|
||||
Software: software,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) Vulnerability(ctx context.Context, cve string, teamID *uint, useCVSScores bool) (*fleet.VulnerabilityWithMetadata, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vuln, err := svc.ds.Vulnerability(ctx, cve, teamID, useCVSScores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vuln, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ListOSVersionsByCVE(ctx context.Context, cve string, teamID *uint) (result []*fleet.VulnerableOS, updatedAt time.Time, err error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, updatedAt, err
|
||||
}
|
||||
return svc.ds.OSVersionsByCVE(ctx, cve, teamID)
|
||||
}
|
||||
|
||||
func (svc *Service) ListSoftwareByCVE(ctx context.Context, cve string, teamID *uint) (result []*fleet.VulnerableSoftware, updatedAt time.Time, err error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, updatedAt, err
|
||||
}
|
||||
return svc.ds.SoftwareByCVE(ctx, cve, teamID)
|
||||
}
|
||||
|
@ -51,3 +51,129 @@ func TestListVulnerabilities(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVulnerabilitesAuth(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
|
||||
ds.ListVulnerabilitiesFunc = func(cxt context.Context, opt fleet.VulnListOptions) ([]fleet.VulnerabilityWithMetadata, *fleet.PaginationMetadata, error) {
|
||||
return []fleet.VulnerabilityWithMetadata{}, &fleet.PaginationMetadata{}, nil
|
||||
}
|
||||
|
||||
ds.VulnerabilityFunc = func(cxt context.Context, cve string, teamID *uint, includeCVEScores bool) (*fleet.VulnerabilityWithMetadata, error) {
|
||||
return &fleet.VulnerabilityWithMetadata{}, nil
|
||||
}
|
||||
|
||||
ds.CountVulnerabilitiesFunc = func(cxt context.Context, opt fleet.VulnListOptions) (uint, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
user *fleet.User
|
||||
shouldFailGlobalRead bool
|
||||
shouldFailTeamRead bool
|
||||
}{
|
||||
{
|
||||
name: "global-admin",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||
},
|
||||
shouldFailGlobalRead: false,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "global-maintainer",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
GlobalRole: ptr.String(fleet.RoleMaintainer),
|
||||
},
|
||||
shouldFailGlobalRead: false,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "global-observer",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
},
|
||||
shouldFailGlobalRead: false,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "team-admin-belongs-to-team",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
Teams: []fleet.UserTeam{{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleAdmin,
|
||||
}},
|
||||
},
|
||||
shouldFailGlobalRead: true,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "team-maintainer-belongs-to-team",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
Teams: []fleet.UserTeam{{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleMaintainer,
|
||||
}},
|
||||
},
|
||||
shouldFailGlobalRead: true,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "team-observer-belongs-to-team",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
Teams: []fleet.UserTeam{{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleObserver,
|
||||
}},
|
||||
},
|
||||
shouldFailGlobalRead: true,
|
||||
shouldFailTeamRead: false,
|
||||
},
|
||||
{
|
||||
name: "team-admin-does-not-belong-to-team",
|
||||
user: &fleet.User{
|
||||
ID: 1,
|
||||
Teams: []fleet.UserTeam{{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleAdmin,
|
||||
}},
|
||||
},
|
||||
shouldFailGlobalRead: true,
|
||||
shouldFailTeamRead: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: tc.user})
|
||||
_, _, err := svc.ListVulnerabilities(ctx, fleet.VulnListOptions{})
|
||||
checkAuthErr(t, tc.shouldFailGlobalRead, err)
|
||||
|
||||
_, _, err = svc.ListVulnerabilities(ctx, fleet.VulnListOptions{
|
||||
TeamID: 1,
|
||||
})
|
||||
checkAuthErr(t, tc.shouldFailTeamRead, err)
|
||||
|
||||
_, err = svc.CountVulnerabilities(ctx, fleet.VulnListOptions{})
|
||||
checkAuthErr(t, tc.shouldFailGlobalRead, err)
|
||||
|
||||
_, err = svc.CountVulnerabilities(ctx, fleet.VulnListOptions{
|
||||
TeamID: 1,
|
||||
})
|
||||
checkAuthErr(t, tc.shouldFailTeamRead, err)
|
||||
|
||||
_, err = svc.Vulnerability(ctx, "CVE-2019-1234", nil, false)
|
||||
checkAuthErr(t, tc.shouldFailGlobalRead, err)
|
||||
|
||||
_, err = svc.Vulnerability(ctx, "CVE-2019-1234", ptr.Uint(1), false)
|
||||
checkAuthErr(t, tc.shouldFailTeamRead, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user