diff --git a/changes/31123-dcv-viewer-fix b/changes/31123-dcv-viewer-fix new file mode 100644 index 0000000000..d63ebf1cc2 --- /dev/null +++ b/changes/31123-dcv-viewer-fix @@ -0,0 +1 @@ +* Added back software mutation on ingestion to fix non-semver-compliant software versions, starting with DCV Viewer. diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index eef2f60ec8..64330e5976 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -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 { diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index be87f0da2e..0d4a2884df 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -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