From 75b17df6e30844e884f10d49b20af06db15116bd Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Mon, 30 Oct 2023 19:41:16 -0300 Subject: [PATCH] Fix Windows Cloudflare WARP version ingestion (#14797) #14522 - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes (docs/Using Fleet/manage-access.md)~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - ~For Orbit and Fleet Desktop changes:~ - ~[ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.~ - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~ --- changes/14522-fix-cloudflare-warp-versioning | 1 + server/service/osquery_utils/queries.go | 33 +++++++- server/service/osquery_utils/queries_test.go | 80 +++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 changes/14522-fix-cloudflare-warp-versioning 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) }) }