diff --git a/changes/14522-fix-cloudflare-warp-versioning b/changes/14522-fix-cloudflare-warp-versioning new file mode 100644 index 0000000000..742c2f7338 --- /dev/null +++ b/changes/14522-fix-cloudflare-warp-versioning @@ -0,0 +1 @@ +* Fixed how Fleet ingest Windows' Cloudflare WARP software version. diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index f19d51fca8..3fbede1c77 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -1175,7 +1175,7 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho level.Debug(logger).Log( "msg", "host reported software with invalid last opened timestamp", "host_id", host.ID, - "row", row, + "row", fmt.Sprintf("%+v", row), ) } @@ -1194,13 +1194,13 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho level.Debug(logger).Log( "msg", "failed to parse software row", "host_id", host.ID, - "row", row, + "row", fmt.Sprintf("%+v", row), "err", err, ) continue } - sanitizeSoftware(host, s) + sanitizeSoftware(host, s, logger) software = append(software, *s) @@ -1231,7 +1231,7 @@ var macOSMSTeamsVersion = regexp.MustCompile(`(\d).00.(\d)(\d+)`) // sanitizeSoftware performs any sanitization required to the ingested software fields. // // Some fields are reported with known incorrect values and we need to fix them before using them. -func sanitizeSoftware(h *fleet.Host, s *fleet.Software) { +func sanitizeSoftware(h *fleet.Host, s *fleet.Software, logger log.Logger) { softwareSanitizers := []struct { checkSoftware func(*fleet.Host, *fleet.Software) bool mutateSoftware func(*fleet.Software) @@ -1250,6 +1250,31 @@ func sanitizeSoftware(h *fleet.Host, s *fleet.Software) { } }, }, + // In the Windows Registry, Cloudflare WARP defines its major version with the last two digits, e.g. `23.9.248.0`. + // On NVD, the vulnerabilities are reported using the full year, e.g. `2023.9.248.0`. + { + checkSoftware: func(h *fleet.Host, s *fleet.Software) bool { + return h.Platform == "windows" && s.Name == "Cloudflare WARP" && s.Source == "programs" + }, + mutateSoftware: func(s *fleet.Software) { + // Perform some sanity check on the version before mutating it. + parts := strings.Split(s.Version, ".") + if len(parts) <= 1 { + level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version) + return + } + _, err := strconv.Atoi(parts[0]) + if err != nil { + level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) + return + } + // In case Cloudflare starts returning the full year. + if len(parts[0]) == 4 { + return + } + s.Version = "20" + s.Version // Cloudflare WARP was released on 2019. + }, + }, } for _, softwareSanitizer := range softwareSanitizers { diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index b28837cdcc..e168591a92 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -1352,9 +1352,87 @@ func TestSanitizeSoftware(t *testing.T) { Version: "1.2.3", }, }, + { + name: "Cloudflare WARP on Windows, version not using full year", + h: &fleet.Host{ + Platform: "windows", + }, + s: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "23.9.248.0", + Source: "programs", + }, + sanitized: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "2023.9.248.0", + Source: "programs", + }, + }, + { + name: "Cloudflare WARP on Windows, version using full year", + h: &fleet.Host{ + Platform: "windows", + }, + s: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "2023.9.248.0", + Source: "programs", + }, + sanitized: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "2023.9.248.0", + Source: "programs", + }, + }, + { + name: "Cloudflare WARP on Windows with invalid version", + h: &fleet.Host{ + Platform: "windows", + }, + s: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "foobar", + Source: "programs", + }, + sanitized: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "foobar", + Source: "programs", + }, + }, + { + name: "Cloudflare WARP on Windows with invalid version", + h: &fleet.Host{ + Platform: "windows", + }, + s: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "foo.bar", + Source: "programs", + }, + sanitized: &fleet.Software{ + Name: "Cloudflare WARP", + Version: "foo.bar", + Source: "programs", + }, + }, + { + name: "Other on Windows", + h: &fleet.Host{ + Platform: "windows", + }, + s: &fleet.Software{ + Name: "Other", + Version: "1.2.3", + }, + sanitized: &fleet.Software{ + Name: "Other", + Version: "1.2.3", + }, + }, } { t.Run(tc.name, func(t *testing.T) { - sanitizeSoftware(tc.h, tc.s) + sanitizeSoftware(tc.h, tc.s, log.NewNopLogger()) require.Equal(t, tc.sanitized, tc.s) }) }