feat: add populate software query param (#15661)

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Jahziel Villasana-Espinoza 2023-12-14 16:18:30 -05:00 committed by GitHub
parent ced538c916
commit bcb66e8893
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 1 deletions

View file

@ -0,0 +1,2 @@
- Adds a `populate_software` flag to the `GET /hosts` endpoint, which will include software for each
host returned from that endpoint.

View file

@ -177,6 +177,9 @@ type HostListOptions struct {
// Premium feature, Fleet Free ignores the setting (it forces it to nil to
// disable it).
LowDiskSpaceFilter *int
// PopulateSoftware adds the `Software` field to all Hosts returned.
PopulateSoftware bool
}
// TODO(Sarah): Are we missing any filters here? Should all MDM filters be included?

View file

@ -179,7 +179,20 @@ func (svc *Service) ListHosts(ctx context.Context, opt fleet.HostListOptions) ([
opt.MDMBootstrapPackageFilter = nil
}
return svc.ds.ListHosts(ctx, filter, opt)
hosts, err := svc.ds.ListHosts(ctx, filter, opt)
if err != nil {
return nil, err
}
if opt.PopulateSoftware {
for _, host := range hosts {
if err = svc.ds.LoadHostSoftware(ctx, host, license.IsPremium(ctx)); err != nil {
return nil, err
}
}
}
return hosts, nil
}
/////////////////////////////////////////////////////////////////////////////////

View file

@ -1670,6 +1670,51 @@ func (s *integrationTestSuite) TestListHosts() {
resp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &resp, "os_id", fmt.Sprintf("%d", osID+1337))
require.Len(t, resp.Hosts, 0)
// populate software for hosts
now := time.Now()
inserted, err := s.ds.InsertSoftwareVulnerability(context.Background(), fleet.SoftwareVulnerability{
SoftwareID: host2.Software[0].ID,
CVE: "cve-123-123-123",
}, fleet.NVDSource)
require.NoError(t, err)
require.True(t, inserted)
require.NoError(t, s.ds.InsertCVEMeta(context.Background(), []fleet.CVEMeta{{
CVE: "cve-123-123-123",
CVSSScore: ptr.Float64(5.4),
EPSSProbability: ptr.Float64(0.5),
CISAKnownExploit: ptr.Bool(true),
Published: &now,
Description: "a long description of the cve",
}}))
resp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &resp, "populate_software", "true")
require.Len(t, resp.Hosts, 4)
for _, h := range resp.Hosts {
if h.ID == hosts[2].ID {
require.NotEmpty(t, h.Software)
require.Len(t, h.Software, 1)
require.NotEmpty(t, h.Software[0].Vulnerabilities)
// all these should be nil because this isn't Premium
require.Nil(t, h.Software[0].Vulnerabilities[0].CVSSScore)
require.Nil(t, h.Software[0].Vulnerabilities[0].EPSSProbability)
require.Nil(t, h.Software[0].Vulnerabilities[0].CISAKnownExploit)
require.Nil(t, h.Software[0].Vulnerabilities[0].CVEPublished)
require.Nil(t, h.Software[0].Vulnerabilities[0].Description)
require.Nil(t, h.Software[0].Vulnerabilities[0].ResolvedInVersion)
}
}
resp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &resp, "populate_software", "false")
require.Len(t, resp.Hosts, 4)
for _, h := range resp.Hosts {
require.Empty(t, h.Software)
}
}
func (s *integrationTestSuite) TestInvites() {

View file

@ -3030,6 +3030,59 @@ func (s *integrationEnterpriseTestSuite) TestListHosts() {
s.DoJSON("GET", "/api/latest/fleet/host_summary", nil, http.StatusOK, &summaryResp, "team_id", "1", "platform", "linux")
require.Equal(t, uint(0), summaryResp.TotalsHostsCount)
require.Nil(t, summaryResp.LowDiskSpaceCount)
// populate software for hosts
now := time.Now()
software := []fleet.Software{
{Name: "foo", Version: "0.0.1", Source: "chrome_extensions"},
}
_, err = s.ds.UpdateHostSoftware(context.Background(), host1.ID, software)
require.NoError(t, err)
require.NoError(t, s.ds.LoadHostSoftware(context.Background(), host1, false))
inserted, err := s.ds.InsertSoftwareVulnerability(context.Background(), fleet.SoftwareVulnerability{
SoftwareID: host1.Software[0].ID,
CVE: "cve-123-123-123",
}, fleet.NVDSource)
require.NoError(t, err)
require.True(t, inserted)
vulnMeta := []fleet.CVEMeta{{
CVE: "cve-123-123-123",
CVSSScore: ptr.Float64(5.4),
EPSSProbability: ptr.Float64(0.5),
CISAKnownExploit: ptr.Bool(true),
Published: &now,
Description: "a long description of the cve",
}}
require.NoError(t, s.ds.InsertCVEMeta(context.Background(), vulnMeta))
resp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &resp, "populate_software", "true")
require.Len(t, resp.Hosts, 3)
for _, h := range resp.Hosts {
if h.ID == host1.ID {
require.NotEmpty(t, h.Software)
require.Len(t, h.Software, 1)
require.NotEmpty(t, h.Software[0].Vulnerabilities)
s := &vulnMeta[0].Description
require.Equal(t, &vulnMeta[0].CVSSScore, h.Software[0].Vulnerabilities[0].CVSSScore)
require.Equal(t, &vulnMeta[0].EPSSProbability, h.Software[0].Vulnerabilities[0].EPSSProbability)
require.Equal(t, &vulnMeta[0].CISAKnownExploit, h.Software[0].Vulnerabilities[0].CISAKnownExploit)
require.Equal(t, &s, h.Software[0].Vulnerabilities[0].Description)
}
}
resp = listHostsResponse{}
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &resp, "populate_software", "false")
require.Len(t, resp.Hosts, 3)
for _, h := range resp.Hosts {
require.Empty(t, h.Software)
}
}
func (s *integrationEnterpriseTestSuite) TestMDMNotConfiguredEndpoints() {

View file

@ -483,6 +483,14 @@ func hostListOptionsFromRequest(r *http.Request) (fleet.HostListOptions, error)
}
hopt.LowDiskSpaceFilter = &v
}
populateSoftware := r.URL.Query().Get("populate_software")
if populateSoftware != "" {
ps, err := strconv.ParseBool(populateSoftware)
if err != nil {
return hopt, ctxerr.Wrap(r.Context(), badRequest(fmt.Sprintf("Invalid populate_software: %s", populateSoftware)))
}
hopt.PopulateSoftware = ps
}
// cannot combine software_id, software_version_id, and software_title_id
var softwareErrorLabel []string