Add software sanitation on ingest back, use it to fix DCV Viewer versions (#31251)

We'll want to pull this into a feed so fixes don't take a Fleet release
to propagate, and some fixes currently in the vulns mutations list
should probably move over here (as they're also dealing with non-semver
versions), but that's out of scope for this particular fix.

Fixes #31123.

# 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/`,
`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.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Ian Littman
2025-07-25 08:45:39 -05:00
committed by GitHub
parent f4cc1a2e5f
commit bed1c6a318
3 changed files with 68 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
* Added back software mutation on ingestion to fix non-semver-compliant software versions, starting with DCV Viewer.
+38
View File
@@ -1756,6 +1756,8 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho
continue
}
MutateSoftwareOnIngestion(s, logger)
if shouldRemoveSoftware(host, s) {
continue
}
@@ -1799,6 +1801,42 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho
return nil
}
var (
dcvVersionFormat = regexp.MustCompile(`^(\d+\.\d+)\s*\(r(\d+)\)$`)
softwareSanitizers = []struct {
matches func(*fleet.Software) bool
mutate func(*fleet.Software, log.Logger)
}{
{
matches: func(s *fleet.Software) bool {
return s.Source == "apps" && s.BundleIdentifier == "com.nicesoftware.dcvviewer"
},
mutate: func(s *fleet.Software, logger log.Logger) {
if versionMatches := dcvVersionFormat.FindStringSubmatch(s.Version); len(versionMatches) == 3 {
s.Version = fmt.Sprintf("%s.%s", versionMatches[1], versionMatches[2])
}
},
},
}
)
// MutateSoftwareOnIngestion performs any tweaks required to the ingested software fields.
//
// Some fields are reported with known incorrect values and we need to fix them before using them.
func MutateSoftwareOnIngestion(s *fleet.Software, logger log.Logger) {
for _, softwareSanitizer := range softwareSanitizers {
if softwareSanitizer.matches(s) {
defer func() {
if r := recover(); r != nil {
level.Warn(logger).Log("msg", "panic during software mutation", "softwareName", s.Name, "softwareVersion", s.Version, "error", r)
}
}()
softwareSanitizer.mutate(s, logger)
break
}
}
}
// shouldRemoveSoftware returns whether or not we should remove the given Software item from this
// host's software list.
func shouldRemoveSoftware(h *fleet.Host, s *fleet.Software) bool {
@@ -33,6 +33,35 @@ import (
"golang.org/x/exp/maps"
)
func TestSoftwareIngestionMutations(t *testing.T) {
dcvViewer := &fleet.Software{
BundleIdentifier: "com.nicesoftware.dcvviewer",
Source: "apps",
Version: "2024.0 (r8004)",
}
MutateSoftwareOnIngestion(dcvViewer, log.NewNopLogger())
assert.Equal(t, "2024.0.8004", dcvViewer.Version)
noOp := &fleet.Software{
BundleIdentifier: "com.nicesoftware.dcvviewer",
Source: "apps",
Version: "2024",
}
MutateSoftwareOnIngestion(dcvViewer, log.NewNopLogger())
assert.Equal(t, "2024", noOp.Version)
noMatch := &fleet.Software{
BundleIdentifier: "com.google.chrome",
Source: "apps",
Version: "2024.0 (r8004)",
}
MutateSoftwareOnIngestion(noMatch, log.NewNopLogger())
assert.Equal(t, "2024.0 (r8004)", noMatch.Version)
}
func TestDetailQueryNetworkInterfaces(t *testing.T) {
var initialHost fleet.Host
host := initialHost