Host Vulnerability Filter (#16889)

This commit is contained in:
Tim Lee 2024-02-15 13:27:18 -07:00 committed by GitHub
parent 0fb2aa5f71
commit eb9a1df045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 109 additions and 1 deletions

View file

@ -1098,6 +1098,7 @@ func (ds *Datastore) applyHostFilters(
sqlStmt, params = filterHostsByMDMBootstrapPackageStatus(sqlStmt, opt, params)
sqlStmt, params = filterHostsByOS(sqlStmt, opt, params)
sqlStmt, params = filterHostsByVulnerability(sqlStmt, opt, params)
sqlStmt, params, _ = hostSearchLike(sqlStmt, params, opt.MatchQuery, append(hostSearchColumns, "display_name")...)
sqlStmt, params = appendListOptionsWithCursorToSQL(sqlStmt, params, &opt.ListOptions)
@ -1500,6 +1501,25 @@ func filterHostsByMDMBootstrapPackageStatus(sql string, opt fleet.HostListOption
return sql + newSQL, params
}
func filterHostsByVulnerability(sqlstmt string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
if opt.VulnerabilityFilter != nil {
sqlstmt += ` AND h.id IN (
SELECT hs.host_id FROM host_software hs
JOIN software_cve sc ON sc.software_id = hs.software_id
WHERE sc.cve = ?
UNION
SELECT hos.host_id FROM host_operating_system hos
JOIN operating_system_vulnerabilities osv ON osv.operating_system_id = hos.os_id
WHERE osv.cve = ?)`
params = append(params, opt.VulnerabilityFilter, opt.VulnerabilityFilter)
}
return sqlstmt, params
}
func (ds *Datastore) CountHosts(ctx context.Context, filter fleet.TeamFilter, opt fleet.HostListOptions) (int, error) {
sql := `SELECT count(*) `

View file

@ -114,6 +114,7 @@ func TestHosts(t *testing.T) {
{"HostsListBySoftwareChangedAt", testHostsListBySoftwareChangedAt},
{"HostsListByOperatingSystemID", testHostsListByOperatingSystemID},
{"HostsListByOSNameAndVersion", testHostsListByOSNameAndVersion},
{"HostsListByVulnerability", testHostsListByVulnerability},
{"HostsListByDiskEncryptionStatus", testHostsListMacOSSettingsDiskEncryptionStatus},
{"HostsListFailingPolicies", printReadsInTest(testHostsListFailingPolicies)},
{"HostsExpiration", testHostsExpiration},
@ -3143,6 +3144,84 @@ func testHostsListByOSNameAndVersion(t *testing.T, ds *Datastore) {
}
}
func testHostsListByVulnerability(t *testing.T, ds *Datastore) {
// seed hosts
var hosts []*fleet.Host
for i := 0; i < 9; i++ {
h, err := ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now().Add(-time.Duration(i) * time.Minute),
OsqueryHostID: ptr.String(strconv.Itoa(i)),
NodeKey: ptr.String(fmt.Sprintf("%d", i)),
UUID: fmt.Sprintf("%d", i),
Hostname: fmt.Sprintf("foo.local%d", i),
})
require.NoError(t, err)
hosts = append(hosts, h)
}
// seed software
software := []fleet.Software{
{Name: "foo", Version: "0.0.2", Source: "chrome_extensions"},
}
// add software to 5 hosts
var swVulnHostIDs []uint
for i := 0; i < 5; i++ {
_, err := ds.UpdateHostSoftware(context.Background(), hosts[i].ID, software)
require.NoError(t, err)
swVulnHostIDs = append(swVulnHostIDs, hosts[i].ID)
}
// seed software vulnerabilities
vuln := fleet.SoftwareVulnerability{
CVE: "CVE-2021-1234",
SoftwareID: 1,
}
_, err := ds.InsertSoftwareVulnerability(context.Background(), vuln, fleet.NVDSource)
require.NoError(t, err)
list, err := ds.ListHosts(context.Background(), fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{VulnerabilityFilter: ptr.String("CVE-2021-1234")})
require.NoError(t, err)
require.Len(t, list, 5)
for _, h := range list {
require.Contains(t, swVulnHostIDs, h.ID)
}
// update 2 host operating system
os := fleet.OperatingSystem{
Name: "Ubuntu",
Version: "20.4.0 LTS",
Arch: "x86_64",
Platform: "ubuntu",
KernelVersion: "5.10.76-linuxkit",
}
err = ds.UpdateHostOperatingSystem(context.Background(), hosts[0].ID, os)
require.NoError(t, err)
err = ds.UpdateHostOperatingSystem(context.Background(), hosts[1].ID, os)
require.NoError(t, err)
// seed os vulnerability
osVulns := []fleet.OSVulnerability{
{
OSID: 1,
CVE: "CVE-2021-1235",
},
}
_, err = ds.InsertOSVulnerabilities(context.Background(), osVulns, fleet.NVDSource)
require.NoError(t, err)
list, err = ds.ListHosts(context.Background(), fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{VulnerabilityFilter: ptr.String("CVE-2021-1235")})
require.NoError(t, err)
require.Len(t, list, 2)
for _, h := range list {
require.Contains(t, []uint{hosts[0].ID, hosts[1].ID}, h.ID)
}
}
func testHostsListMacOSSettingsDiskEncryptionStatus(t *testing.T, ds *Datastore) {
ctx := context.Background()

View file

@ -181,6 +181,9 @@ type HostListOptions struct {
// PopulateSoftware adds the `Software` field to all Hosts returned.
PopulateSoftware bool
// VulnerabilityFilter filters the hosts by the presence of a vulnerability (CVE)
VulnerabilityFilter *string
}
// TODO(Sarah): Are we missing any filters here? Should all MDM filters be included?

View file

@ -324,6 +324,11 @@ func hostListOptionsFromRequest(r *http.Request) (fleet.HostListOptions, error)
hopt.OSVersionFilter = &osVersion
}
cve := r.URL.Query().Get("vulnerability")
if cve != "" {
hopt.VulnerabilityFilter = &cve
}
if hopt.OSNameFilter != nil && hopt.OSVersionFilter == nil {
return hopt, ctxerr.Wrap(
r.Context(), badRequest(

View file

@ -158,7 +158,7 @@ func TestHostListOptionsFromRequest(t *testing.T) {
"&os_name=osName&os_version=osVersion&os_version_id=5&disable_failing_policies=1&macos_settings=verified" +
"&macos_settings_disk_encryption=enforcing&os_settings=pending&os_settings_disk_encryption=failed" +
"&bootstrap_package=installed&mdm_id=6&mdm_name=mdmName&mdm_enrollment_status=automatic" +
"&munki_issue_id=7&low_disk_space=99",
"&munki_issue_id=7&low_disk_space=99&vulnerability=CVE-2023-42887",
hostListOptions: fleet.HostListOptions{
ListOptions: fleet.ListOptions{
OrderKey: "foo",
@ -188,6 +188,7 @@ func TestHostListOptionsFromRequest(t *testing.T) {
MDMEnrollmentStatusFilter: fleet.MDMEnrollStatusAutomatic,
MunkiIssueIDFilter: ptr.Uint(7),
LowDiskSpaceFilter: ptr.Int(99),
VulnerabilityFilter: ptr.String("CVE-2023-42887"),
},
},
"policy_id and policy_response params (for coverage)": {