fleet/server/vulnerabilities/nvd/indexed_cpe_item.go
Jonathan Katz d70500a6e9
Add sw_edition to cpe db generation and cpe translations (#32879)
Fixes: #31989 
# Adding sw_edition to CPE generation and translation
This PR adds the ability to override sw_edition with cpe translations.
This adds a new column to cpe.sqlite that is generated daily.
Old versions of fleet will still work with the new cpe db and
translations.
Versions from this change forward will require the new cpe db for cpe
translations to work.

# Checklist for submitter

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

- [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/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually

## Backwards Compatibility
Testing with physical machines and for Firefox ESR fix
| Fleet version | cpe db | translations | vuln. soft. # | Firefox ESR
cpe | Firefox ESR vuln. # |
| ------- | ------ | ------------ | ------------- | ---------------- |
------------------- |
| Updated | old | old | 58 | `:*:macos:*:*` | 168 |
| Updated | new | new | 58 | `:esr:macos:*:*` | 92 |
| 4.71.1 | old | old | 58 | `:*:macos:*:*` | 168 |
| 4.71.1 | new | new | 58 | `:*:macos:*:*` | 168 |

Testing with osquery-perf hosts
| Fleet version | cpe db | translations | vuln. soft. # |
Vulnerabilities |
| ------- | ------ | ------------ | ------------- | --------------- |
| Updated | old    | old          | 156/161       | 3136            |
| Updated | new    | new          | 156/161       | 3136            |
| 4.71.1  | old    | old          | 156/161       | 3951            |
| 4.71.1  | new    | new          | 156/161       | 3951            |

---------

Co-authored-by: Ian Littman <iansltx@gmail.com>
2025-09-17 11:30:49 -04:00

77 lines
2.2 KiB
Go

package nvd
import (
"regexp"
"strings"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/wfn"
)
type IndexedCPEItem struct {
ID int `json:"id" db:"rowid"`
Part string
Product string `json:"product" db:"product"`
Vendor string `json:"vendor" db:"vendor"`
SWEdition string `json:"sw_edition" db:"sw_edition"`
Deprecated bool `json:"deprecated" db:"deprecated"`
Weight int `db:"weight"`
}
func (i *IndexedCPEItem) FmtStr(s *fleet.Software) string {
cpe := wfn.NewAttributesWithAny()
cpe.Part = "a"
cpe.Vendor = i.Vendor
cpe.Product = i.Product
cpe.TargetSW = targetSW(s)
cpe.SWEdition = i.SWEdition
// Some version strings (e.g. Python pre-releases) contain a part that should be placed in the
// CPE's update field. Parse that out (if it exists).
// See https://github.com/fleetdm/fleet/issues/25882.
version, update := parseUpdateFromVersion(sanitizeVersion(s.Version))
cpe.Version = version
cpe.Update = update
if cpe.Product == "python" && cpe.Vendor == "python" && cpe.Update == wfn.Any {
cpe.Update = wfn.NA
}
if i.Part != "" {
cpe.Part = i.Part
}
// Make sure we don't return a 'match all' CPE
if cpe.Vendor == wfn.Any || cpe.Product == wfn.Any {
return ""
}
return cpe.BindToFmtString()
}
var versionWithUpdate = regexp.MustCompile(`(\d+\.\d+\.\d+)((?:a|b|rc)\d+)$`)
func parseUpdateFromVersion(originalVersion string) (version, update string) {
// Return the unchanged original version by default
version = originalVersion
if versionWithUpdate.MatchString(originalVersion) {
versionBytes := []byte{}
updateBytes := []byte{}
for _, submatches := range versionWithUpdate.FindAllStringSubmatchIndex(originalVersion, -1) {
versionBytes = versionWithUpdate.ExpandString(versionBytes, "${1}", originalVersion, submatches)
updateBytes = versionWithUpdate.ExpandString(updateBytes, "${2}", originalVersion, submatches)
version = string(versionBytes)
switch updateBytes[0] {
case 'a':
update = strings.ReplaceAll(string(updateBytes), "a", "alpha")
case 'b':
update = strings.ReplaceAll(string(updateBytes), "b", "beta")
case 'r':
update = string(updateBytes)
}
}
}
return version, update
}