mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Added known_vulnerability to vulnerabilities endpoint. (#21136)
#19857 For `GET /api/v1/fleet/vulnerabilities` endpoint, added `known_vulnerability` field to the response. This field is present when query is a valid CVE format and returns no results. It indicates whether the vulnerability is in Fleet's DB. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
1b4e4f44c5
commit
b67017398b
7 changed files with 105 additions and 9 deletions
1
changes/19857-known_vulnerability
Normal file
1
changes/19857-known_vulnerability
Normal file
|
|
@ -0,0 +1 @@
|
|||
For GET /api/v1/fleet/vulnerabilities endpoint, added `known_vulnerability` field to the response. This field is present when query is a valid CVE format and returns no results. It indicates whether the vulnerability is in Fleet's DB.
|
||||
|
|
@ -3,6 +3,7 @@ package mysql
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -496,3 +497,12 @@ func (ds *Datastore) batchInsertHostCounts(ctx context.Context, counts []hostCou
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) IsCVEKnownToFleet(ctx context.Context, cve string) (bool, error) {
|
||||
var count uint
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &count, "SELECT 1 FROM cve_meta WHERE cve = ?", cve)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -989,6 +989,8 @@ type Datastore interface {
|
|||
CountVulnerabilities(ctx context.Context, opt VulnListOptions) (uint, error)
|
||||
// UpdateVulnerabilityHostCounts updates hosts counts for all vulnerabilities.
|
||||
UpdateVulnerabilityHostCounts(ctx context.Context) error
|
||||
// IsCVEKnownToFleet checks if the provided CVE is known to Fleet.
|
||||
IsCVEKnownToFleet(ctx context.Context, cve string) (bool, error)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Apple MDM
|
||||
|
|
|
|||
|
|
@ -668,6 +668,8 @@ type Service interface {
|
|||
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)
|
||||
// IsCVEKnownToFleet returns whether the provided CVE is known to Fleet.
|
||||
IsCVEKnownToFleet(ctx context.Context, cve string) (bool, error)
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Team Policies
|
||||
|
|
|
|||
|
|
@ -684,6 +684,8 @@ type CountVulnerabilitiesFunc func(ctx context.Context, opt fleet.VulnListOption
|
|||
|
||||
type UpdateVulnerabilityHostCountsFunc func(ctx context.Context) error
|
||||
|
||||
type IsCVEKnownToFleetFunc func(ctx context.Context, cve string) (bool, error)
|
||||
|
||||
type NewMDMAppleConfigProfileFunc func(ctx context.Context, p fleet.MDMAppleConfigProfile) (*fleet.MDMAppleConfigProfile, error)
|
||||
|
||||
type BulkUpsertMDMAppleConfigProfilesFunc func(ctx context.Context, payload []*fleet.MDMAppleConfigProfile) error
|
||||
|
|
@ -2007,6 +2009,9 @@ type DataStore struct {
|
|||
UpdateVulnerabilityHostCountsFunc UpdateVulnerabilityHostCountsFunc
|
||||
UpdateVulnerabilityHostCountsFuncInvoked bool
|
||||
|
||||
IsCVEKnownToFleetFunc IsCVEKnownToFleetFunc
|
||||
IsCVEKnownToFleetFuncInvoked bool
|
||||
|
||||
NewMDMAppleConfigProfileFunc NewMDMAppleConfigProfileFunc
|
||||
NewMDMAppleConfigProfileFuncInvoked bool
|
||||
|
||||
|
|
@ -4823,6 +4828,13 @@ func (s *DataStore) UpdateVulnerabilityHostCounts(ctx context.Context) error {
|
|||
return s.UpdateVulnerabilityHostCountsFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) IsCVEKnownToFleet(ctx context.Context, cve string) (bool, error) {
|
||||
s.mu.Lock()
|
||||
s.IsCVEKnownToFleetFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.IsCVEKnownToFleetFunc(ctx, cve)
|
||||
}
|
||||
|
||||
func (s *DataStore) NewMDMAppleConfigProfile(ctx context.Context, p fleet.MDMAppleConfigProfile) (*fleet.MDMAppleConfigProfile, error) {
|
||||
s.mu.Lock()
|
||||
s.NewMDMAppleConfigProfileFuncInvoked = true
|
||||
|
|
|
|||
|
|
@ -8662,6 +8662,8 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
|||
require.NoError(t, err)
|
||||
|
||||
// insert CVEMeta
|
||||
knownCVEWoPrefix := "2021-1299"
|
||||
knownCVE := "cve-" + knownCVEWoPrefix
|
||||
mockTime := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
err = s.ds.InsertCVEMeta(context.Background(), []fleet.CVEMeta{
|
||||
{
|
||||
|
|
@ -8688,6 +8690,14 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
|||
Published: ptr.Time(mockTime),
|
||||
Description: "Test CVE 2021-1246",
|
||||
},
|
||||
{
|
||||
CVE: knownCVE,
|
||||
CVSSScore: ptr.Float64(6.4),
|
||||
EPSSProbability: ptr.Float64(0.61),
|
||||
CISAKnownExploit: ptr.Bool(true),
|
||||
Published: ptr.Time(mockTime),
|
||||
Description: fmt.Sprintf("Test %s", knownCVE),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
@ -8701,6 +8711,7 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
|||
require.Equal(t, resp.Count, uint(3))
|
||||
require.False(t, resp.Meta.HasPreviousResults)
|
||||
require.False(t, resp.Meta.HasNextResults)
|
||||
assert.Nil(t, resp.KnownVulnerability)
|
||||
|
||||
expected := map[string]struct {
|
||||
fleet.CVEMeta
|
||||
|
|
@ -8738,6 +8749,7 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
|||
require.Equal(t, resp.Count, uint(2))
|
||||
require.False(t, resp.Meta.HasPreviousResults)
|
||||
require.False(t, resp.Meta.HasNextResults)
|
||||
assert.Nil(t, resp.KnownVulnerability)
|
||||
|
||||
expected = map[string]struct {
|
||||
fleet.CVEMeta
|
||||
|
|
@ -8771,6 +8783,34 @@ func (s *integrationTestSuite) TestListVulnerabilities() {
|
|||
require.Equal(t, resp.Count, uint(0))
|
||||
require.False(t, resp.Meta.HasPreviousResults)
|
||||
require.False(t, resp.Meta.HasNextResults)
|
||||
assert.Nil(t, resp.KnownVulnerability)
|
||||
|
||||
// test with a known CVE that does not match on software/OS
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities", nil, http.StatusOK, &resp, "query", knownCVE)
|
||||
require.Empty(t, resp.Err)
|
||||
assert.Len(s.T(), resp.Vulnerabilities, 0)
|
||||
assert.Equal(t, resp.Count, uint(0))
|
||||
assert.False(t, resp.Meta.HasPreviousResults)
|
||||
assert.False(t, resp.Meta.HasNextResults)
|
||||
assert.Equal(t, ptr.Bool(true), resp.KnownVulnerability)
|
||||
|
||||
// test with a known CVE that does not match on software/OS, but without CVE- prefix
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities", nil, http.StatusOK, &resp, "query", knownCVEWoPrefix)
|
||||
require.Empty(t, resp.Err)
|
||||
assert.Len(s.T(), resp.Vulnerabilities, 0)
|
||||
assert.Equal(t, resp.Count, uint(0))
|
||||
assert.False(t, resp.Meta.HasPreviousResults)
|
||||
assert.False(t, resp.Meta.HasNextResults)
|
||||
assert.Equal(t, ptr.Bool(true), resp.KnownVulnerability)
|
||||
|
||||
// test with a unknown CVE that does not match on software/OS
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities", nil, http.StatusOK, &resp, "query", knownCVE+"1")
|
||||
require.Empty(t, resp.Err)
|
||||
assert.Len(s.T(), resp.Vulnerabilities, 0)
|
||||
assert.Equal(t, resp.Count, uint(0))
|
||||
assert.False(t, resp.Meta.HasPreviousResults)
|
||||
assert.False(t, resp.Meta.HasNextResults)
|
||||
assert.Equal(t, ptr.Bool(false), resp.KnownVulnerability)
|
||||
|
||||
// Team 1 Filter
|
||||
s.DoJSON("GET", "/api/latest/fleet/vulnerabilities", nil, http.StatusOK, &resp, "team_id", "1")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
|
|
@ -22,13 +23,17 @@ type listVulnerabilitiesRequest struct {
|
|||
}
|
||||
|
||||
type listVulnerabilitiesResponse struct {
|
||||
Vulnerabilities []fleet.VulnerabilityWithMetadata `json:"vulnerabilities"`
|
||||
Count uint `json:"count"`
|
||||
CountsUpdatedAt time.Time `json:"counts_updated_at"`
|
||||
Meta *fleet.PaginationMetadata `json:"meta,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
Vulnerabilities []fleet.VulnerabilityWithMetadata `json:"vulnerabilities"`
|
||||
Count uint `json:"count"`
|
||||
CountsUpdatedAt time.Time `json:"counts_updated_at"`
|
||||
Meta *fleet.PaginationMetadata `json:"meta,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
KnownVulnerability *bool `json:"known_vulnerability,omitempty"`
|
||||
}
|
||||
|
||||
// Allow formats like: CVE-2017-12345, cve-2017-12345 or 2017-12345
|
||||
var cveRegex = regexp.MustCompile(`(?i)^(CVE-)?\d{4}-\d{4}\d*$`)
|
||||
|
||||
func (r listVulnerabilitiesResponse) error() error { return r.Err }
|
||||
|
||||
func listVulnerabilitiesEndpoint(ctx context.Context, req interface{}, svc fleet.Service) (errorer, error) {
|
||||
|
|
@ -50,11 +55,31 @@ func listVulnerabilitiesEndpoint(ctx context.Context, req interface{}, svc fleet
|
|||
}
|
||||
}
|
||||
|
||||
var knownVulnerability *bool
|
||||
if len(vulns) == 0 && len(request.ListOptions.MatchQuery) > 0 {
|
||||
// If no vulnerabilities are returned, we need to check if the query was for a vulnerability known to fleet
|
||||
query := request.ListOptions.MatchQuery
|
||||
matches := cveRegex.FindStringSubmatch(query)
|
||||
if matches != nil {
|
||||
const cvePrefix = "CVE-"
|
||||
if len(matches) > 1 && matches[1] == "" {
|
||||
// If CVE prefix was missing, we add it
|
||||
query = cvePrefix + query
|
||||
}
|
||||
known, err := svc.IsCVEKnownToFleet(ctx, query)
|
||||
if err != nil {
|
||||
return listVulnerabilitiesResponse{Err: err}, nil
|
||||
}
|
||||
knownVulnerability = &known
|
||||
}
|
||||
}
|
||||
|
||||
return listVulnerabilitiesResponse{
|
||||
Vulnerabilities: vulns,
|
||||
Meta: meta,
|
||||
Count: count,
|
||||
CountsUpdatedAt: updatedAt,
|
||||
Vulnerabilities: vulns,
|
||||
Meta: meta,
|
||||
Count: count,
|
||||
CountsUpdatedAt: updatedAt,
|
||||
KnownVulnerability: knownVulnerability,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +124,10 @@ func (svc *Service) CountVulnerabilities(ctx context.Context, opts fleet.VulnLis
|
|||
return svc.ds.CountVulnerabilities(ctx, opts)
|
||||
}
|
||||
|
||||
func (svc *Service) IsCVEKnownToFleet(ctx context.Context, cve string) (bool, error) {
|
||||
return svc.ds.IsCVEKnownToFleet(ctx, cve)
|
||||
}
|
||||
|
||||
type getVulnerabilityRequest struct {
|
||||
CVE string `url:"cve"`
|
||||
TeamID *uint `query:"team_id,optional"`
|
||||
|
|
|
|||
Loading…
Reference in a new issue