diff --git a/server/service/osquery.go b/server/service/osquery.go index b007c279f7..67dfc36133 100644 --- a/server/service/osquery.go +++ b/server/service/osquery.go @@ -1958,18 +1958,45 @@ func getMostRecentResults(results []*fleet.ScheduledQueryResult) []*fleet.Schedu // The expected format for s is "pack{Global|team-}" // // Returns "" if it failed to parse the pack_delimiter. + +var ( + dcounter = regexp.MustCompile(`(Global)|(team-\d+)`) + pattern = regexp.MustCompile(`^(.*)(?:(Global)|(team-\d+))`) +) + func findPackDelimiterString(scheduledQueryName string) string { - // Go's regexp doesn't support backreferences so we have to perform some manual work. scheduledQueryName = scheduledQueryName[4:] // always starts with "pack" - for l := 1; l < len(scheduledQueryName); l++ { - sep := scheduledQueryName[:l] - rest := scheduledQueryName[l:] - pattern := fmt.Sprintf(`^(?:(Global)|(team-\d+))%s.+`, regexp.QuoteMeta(sep)) - matched, _ := regexp.MatchString(pattern, rest) - if matched { - return sep + + count := dcounter.FindAllString(scheduledQueryName, -1) + + // If Global or team- does not appear, then the + // pack_delimiter is invalid. + if len(count) == 0 { + return "" + } + + if len(count) == 1 { + matches := pattern.FindStringSubmatch(scheduledQueryName) + if len(matches) > 1 { + return matches[1] } } + + // Handle edge cases where "Global" or "team-"" appears multiple times in the query + // name. Regex is not pre-compiled, so it is a less performant operation. + // Go's regexp doesn't support backreferences so we have to perform some manual work. + if len(count) > 1 { + for l := 1; l < len(scheduledQueryName); l++ { + sep := scheduledQueryName[:l] + rest := scheduledQueryName[l:] + pattern := fmt.Sprintf(`^(?:(Global)|(team-\d+))%s.+`, regexp.QuoteMeta(sep)) + matched, _ := regexp.MatchString(pattern, rest) + if matched { + return sep + } + } + } + return "" } @@ -1995,6 +2022,10 @@ func getQueryNameAndTeamIDFromResult(path string) (*uint, string, error) { // For pattern: pack/Global/Name globalPattern := "pack" + sep + "Global" + sep if strings.HasPrefix(path, globalPattern) { + name := strings.TrimPrefix(path, globalPattern) + if name == "" { + return nil, "", fmt.Errorf("parsing query name: %s", path) + } return nil, strings.TrimPrefix(path, globalPattern), nil } @@ -2006,6 +2037,9 @@ func getQueryNameAndTeamIDFromResult(path string) (*uint, string, error) { if len(teamIDAndQueryNameParts) != 2 { return nil, "", fmt.Errorf("parsing team number part: %s", path) } + if teamIDAndQueryNameParts[1] == "" { + return nil, "", fmt.Errorf("parsing query name: %s", path) + } teamNumberUint, err := strconv.ParseUint(teamIDAndQueryNameParts[0], 10, 32) if err != nil { return nil, "", fmt.Errorf("parsing team number: %w", err) diff --git a/server/service/osquery_test.go b/server/service/osquery_test.go index 10387de164..7f3fb1142a 100644 --- a/server/service/osquery_test.go +++ b/server/service/osquery_test.go @@ -906,11 +906,11 @@ func TestGetQueryNameAndTeamIDFromResult(t *testing.T) { expectedName string hasErr bool }{ - {"pack/Global/Query Name", nil, "Query Name", false}, - {"pack/team-1/Query Name", ptr.Uint(1), "Query Name", false}, - {"pack/team-12345/Another Query", ptr.Uint(12345), "Another Query", false}, - {"pack/team-foo/Query", nil, "", true}, - {"pack/Global/QueryWith/Slash", nil, "QueryWith/Slash", false}, + {"pack/Global/Query Name", nil, "Query Name", false}, // valid global query + {"pack/team-1/Query Name", ptr.Uint(1), "Query Name", false}, // valid team query + {"pack/team-12345/Another Query", ptr.Uint(12345), "Another Query", false}, // valid team query + {"pack/team-foo/Query", nil, "", true}, // missing team ID + {"pack/Global/QueryWith/Slash", nil, "QueryWith/Slash", false}, // query name contains forward slash {"packGlobalGlobalGlobalGlobal", nil, "Global", false}, // pack_delimiter=Global {"packXGlobalGlobalXGlobalQueryWith/Slash", nil, "QueryWith/Slash", false}, // pack_delimiter=XGlobal {"pack//Global//QueryWith/Slash", nil, "QueryWith/Slash", false}, // pack_delimiter=// @@ -920,6 +920,8 @@ func TestGetQueryNameAndTeamIDFromResult(t *testing.T) { {"pack123😁123team-1123😁123QueryWith/Slash", ptr.Uint(1), "QueryWith/Slash", false}, // pack_delimiter=123😁123 {"pack(foo)team-1(foo)fo(o)bar", ptr.Uint(1), "fo(o)bar", false}, // pack_delimiter=(foo) {"packteam-1team-1team-1team-1", ptr.Uint(1), "team-1", false}, // pack_delimiter=team-1 + {"pack/Global/GlobalInQueryName", nil, "GlobalInQueryName", false}, // query name contains Global + {"pack/team-1/team-1InQueryName", ptr.Uint(1), "team-1InQueryName", false}, // query name contains team-1 {"InvalidString", nil, "", true}, {"Invalid/Query", nil, "", true}, @@ -3803,3 +3805,26 @@ func TestDetailQueriesLinuxDistros(t *testing.T) { require.Contains(t, m, "software_linux") } } + +// Benchmark function +func BenchmarkFindPackDelimiterStringCommon(b *testing.B) { + // Input data for benchmarking + input := "pack/Global/Foo" + + // Run the benchmark + b.ResetTimer() + for i := 0; i < b.N; i++ { + findPackDelimiterString(input) + } +} + +func BenchmarkFindPackDelimiterStringTeamPack(b *testing.B) { + // Input data for benchmarking + input := "packGlobalGlobalGlobalGlobal" // global pack delimiter, global team, query name global + + // Run the benchmark + b.ResetTimer() + for i := 0; i < b.N; i++ { + findPackDelimiterString(input) + } +}