fleet/server/service/vulnerabilities.go
Scott Gress 9da7008765
Update backend error messages (#40364)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** For #40348

# Details

This PR updates a number of error message on the server to use `fleet`
and `report` instead of `team` or `query` where applicable.

# Checklist for submitter

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

- [ ] 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/guides/committing-changes.md#changes-files)
for more information.
this is all internal, i don't think it warrants a changelog

## Testing

- [X] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually
I did not go trying to trigger all these errors.  It's text changes.
2026-02-25 13:54:45 -06:00

236 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"fmt"
"net/http"
"regexp"
"time"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)
var freeValidVulnSortColumns = []string{
"cve",
"hosts_count",
"host_count_updated_at",
"created_at",
}
type cveNotFoundError struct{}
var _ fleet.NotFoundError = (*cveNotFoundError)(nil)
func (p cveNotFoundError) Error() string {
return "This is not a known CVE. None of Fleets vulnerability sources are aware of this CVE."
}
func (p cveNotFoundError) IsNotFound() bool {
return true
}
func (p cveNotFoundError) IsClientError() bool {
return true
}
type listVulnerabilitiesRequest struct {
fleet.VulnListOptions
}
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"`
}
// Allow formats like: CVE-2017-12345, cve-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) (fleet.Errorer, error) {
request := req.(*listVulnerabilitiesRequest)
vulns, meta, err := svc.ListVulnerabilities(ctx, request.VulnListOptions)
if err != nil {
return listVulnerabilitiesResponse{Err: err}, nil
}
count, err := svc.CountVulnerabilities(ctx, request.VulnListOptions)
if err != nil {
return listVulnerabilitiesResponse{Err: err}, nil
}
updatedAt := time.Now()
for _, vuln := range vulns {
if vuln.HostsCountUpdatedAt.Before(updatedAt) {
updatedAt = vuln.HostsCountUpdatedAt
}
}
return listVulnerabilitiesResponse{
Vulnerabilities: vulns,
Meta: meta,
Count: count,
CountsUpdatedAt: updatedAt,
}, nil
}
func (svc *Service) ListVulnerabilities(ctx context.Context, opt fleet.VulnListOptions) ([]fleet.VulnerabilityWithMetadata, *fleet.PaginationMetadata, error) {
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{
TeamID: opt.TeamID,
}, fleet.ActionRead); err != nil {
return nil, nil, err
}
if len(opt.ValidSortColumns) == 0 {
opt.ValidSortColumns = freeValidVulnSortColumns
}
if !opt.HasValidSortColumn() {
return nil, nil, badRequest("invalid order key")
}
if opt.KnownExploit && !opt.IsEE {
return nil, nil, fleet.ErrMissingLicense
}
vulns, meta, err := svc.ds.ListVulnerabilities(ctx, opt)
if err != nil {
return nil, nil, err
}
for i, vuln := range vulns {
vulns[i].DetailsLink = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vuln.CVE.CVE)
}
return vulns, meta, nil
}
func (svc *Service) CountVulnerabilities(ctx context.Context, opts fleet.VulnListOptions) (uint, error) {
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{
TeamID: opts.TeamID,
}, fleet.ActionRead); err != nil {
return 0, err
}
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" renameto:"fleet_id"`
}
type getVulnerabilityResponse struct {
Vulnerability *fleet.VulnerabilityWithMetadata `json:"vulnerability"`
OSVersions []*fleet.VulnerableOS `json:"os_versions"`
Software []*fleet.VulnerableSoftware `json:"software"`
Err error `json:"error,omitempty"`
statusCode int
}
func (r getVulnerabilityResponse) Error() error { return r.Err }
func (r getVulnerabilityResponse) Status() int {
if r.statusCode == 0 {
return http.StatusOK
}
return r.statusCode
}
func getVulnerabilityEndpoint(ctx context.Context, req interface{}, svc fleet.Service) (fleet.Errorer, error) {
request := req.(*getVulnerabilityRequest)
vuln, known, err := svc.Vulnerability(ctx, request.CVE, request.TeamID, false)
if err != nil {
return getVulnerabilityResponse{Err: err}, nil
}
if vuln == nil && known {
// Return 204 status code if the vulnerability is known to Fleet but does not match any host software/OS
return getVulnerabilityResponse{statusCode: http.StatusNoContent}, nil
}
vuln.DetailsLink = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vuln.CVE.CVE)
osVersions, _, err := svc.ListOSVersionsByCVE(ctx, vuln.CVE.CVE, request.TeamID)
if err != nil {
return getVulnerabilityResponse{Err: err}, nil
}
software, _, err := svc.ListSoftwareByCVE(ctx, vuln.CVE.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) (vuln *fleet.VulnerabilityWithMetadata,
known bool, err error,
) {
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil {
return nil, false, err
}
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionRead); err != nil {
return nil, false, err
}
if !cveRegex.Match([]byte(cve)) {
return nil, false, badRequest("That vulnerability (CVE) is not valid. Try updating your search to use CVE format: \"CVE-YYYY-<4 or more digits>\"")
}
if teamID != nil && *teamID != 0 {
exists, err := svc.ds.TeamExists(ctx, *teamID)
if err != nil {
return nil, false, ctxerr.Wrap(ctx, err, "checking if team exists")
} else if !exists {
return nil, false, authz.ForbiddenWithInternal("fleet does not exist", nil, nil, nil)
}
}
vuln, err = svc.ds.Vulnerability(ctx, cve, teamID, useCVSScores)
switch {
case fleet.IsNotFound(err):
var errKnown error
known, errKnown = svc.ds.IsCVEKnownToFleet(ctx, cve)
if errKnown != nil {
return nil, false, errKnown
}
if !known {
return nil, false, cveNotFoundError{}
}
case err != nil:
return nil, false, err
default:
known = true
}
return vuln, known, 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)
}