diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index f472c6fe47e..dc9ad8b26fb 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -110,6 +110,7 @@ https://github.com/elastic/beats/compare/v8.2.0\...main[Check the HEAD diff] - Fix panic in TCP and UDP inputs on Linux when collecting socket metrics from OS. {issue}35064[35064] - Correctly collect TCP and UDP metrics for unspecified address values. {pull}35111[35111] - Fix base for UDP and TCP queue metrics and UDP drops metric. {pull}35123[35123] +- Sanitize filenames for request tracer in httpjson and cel inputs. {pull}35143[35143] *Heartbeat* diff --git a/x-pack/filebeat/input/httpjson/input.go b/x-pack/filebeat/input/httpjson/input.go index c10c2e80098..6e1d3e8ca36 100644 --- a/x-pack/filebeat/input/httpjson/input.go +++ b/x-pack/filebeat/input/httpjson/input.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "net/url" + "path/filepath" "strings" "time" @@ -113,7 +114,8 @@ func run( stdCtx := ctxtool.FromCanceller(ctx.Cancelation) if config.Request.Tracer != nil { - config.Request.Tracer.Filename = strings.ReplaceAll(config.Request.Tracer.Filename, "*", ctx.ID) + id := sanitizeFileName(ctx.ID) + config.Request.Tracer.Filename = strings.ReplaceAll(config.Request.Tracer.Filename, "*", id) } httpClient, err := newHTTPClient(stdCtx, config, log) @@ -159,6 +161,16 @@ func run( return nil } +// The Request.Tracer.Filename may have ":" when a httpjson input has cursor config +// The MacOs Finder will treat this as path-separator and causes to show up strange filepaths. +// This function will sanitize characters like ":" and "/" to replace them with "_" just to be +// safe on all operating systems. +func sanitizeFileName(name string) string { + name = strings.ReplaceAll(name, ":", string(filepath.Separator)) + name = filepath.Clean(name) + return strings.ReplaceAll(name, string(filepath.Separator), "_") +} + func newHTTPClient(ctx context.Context, config config, log *logp.Logger) (*httpClient, error) { // Make retryable HTTP client netHTTPClient, err := config.Request.Transport.Client(clientOptions(config.Request.URL.URL, config.Request.KeepAlive.settings())...) diff --git a/x-pack/filebeat/input/httpjson/input_test.go b/x-pack/filebeat/input/httpjson/input_test.go index 5bcd7b157a2..3a4b04050ff 100644 --- a/x-pack/filebeat/input/httpjson/input_test.go +++ b/x-pack/filebeat/input/httpjson/input_test.go @@ -11,6 +11,7 @@ import ( "math/rand" "net/http" "net/http/httptest" + "os" "testing" "time" @@ -292,6 +293,49 @@ func TestInput(t *testing.T) { `{"@timestamp":"2002-10-02T15:00:02Z","foo":"bar"}`, }, }, + { + name: "Test filename truncation", + setupServer: func(t *testing.T, h http.HandlerFunc, config map[string]interface{}) { + registerRequestTransforms() + t.Cleanup(func() { registeredTransforms = newRegistry() }) + // mock timeNow func to return a fixed value + timeNow = func() time.Time { + t, _ := time.Parse(time.RFC3339, "2002-10-02T15:00:00Z") + return t + } + + server := httptest.NewServer(h) + config["request.url"] = server.URL + t.Cleanup(server.Close) + t.Cleanup(func() { timeNow = time.Now }) + }, + baseConfig: map[string]interface{}{ + "interval": 1, + "request.method": http.MethodGet, + "request.transforms": []interface{}{ + map[string]interface{}{ + "set": map[string]interface{}{ + "target": "url.params.$filter", + "value": "alertCreationTime ge [[.cursor.timestamp]]", + "default": `alertCreationTime ge [[formatDate (now (parseDuration "-10m")) "2006-01-02T15:04:05Z"]]`, + }, + }, + }, + "cursor": map[string]interface{}{ + "timestamp": map[string]interface{}{ + "value": `[[index .last_response.body "@timestamp"]]`, + }, + }, + "request.tracer.filename": "../../logs/httpjson/http-request-trace-*.ndjson", + "verifyfilepath": true, + }, + handler: dateCursorHandler(), + expected: []string{ + `{"@timestamp":"2002-10-02T15:00:00Z","foo":"bar"}`, + `{"@timestamp":"2002-10-02T15:00:01Z","foo":"bar"}`, + `{"@timestamp":"2002-10-02T15:00:02Z","foo":"bar"}`, + }, + }, { name: "Test pagination", setupServer: func(t *testing.T, h http.HandlerFunc, config map[string]interface{}) { @@ -1191,6 +1235,13 @@ func TestInput(t *testing.T) { } } } + if tc.baseConfig["verifyfilepath"] != nil { + if _, err := os.Stat("../../logs/httpjson/http-request-trace-httpjson-foo-eb837d4c-5ced-45ed-b05c-de658135e248_https_somesource_someapi.ndjson"); err == nil { + assert.NoError(t, g.Wait()) + } else { + t.Errorf("Expected log filename not found") + } + } assert.NoError(t, g.Wait()) }) } @@ -1257,7 +1308,7 @@ func newV2Context() (v2.Context, func()) { ctx, cancel := context.WithCancel(context.Background()) return v2.Context{ Logger: logp.NewLogger("httpjson_test"), - ID: "test_id", + ID: "httpjson-foo-eb837d4c-5ced-45ed-b05c-de658135e248::https://somesource/someapi", Cancelation: ctx, }, cancel }