From eec2ce111a1ba36fde92e701d9ee6ec54232f45c Mon Sep 17 00:00:00 2001 From: Juan Fernandez Date: Mon, 9 Mar 2026 13:49:07 -0400 Subject: [PATCH] Increase body size limits for osquerylog and osquery/dist/write endpoints (#40946) Resolves #40813 * Added configurable body size limits for the `/api/osquery/log`, `/api/osquery/distributed/write` and `/api/osquery/config` endpoints. * Fixed false positive `PayloadTooLargeError` errors. --------- Co-authored-by: Lucas Manuel Rodriguez --- ...13-increase-osquery-log-endpoint-body-size | 2 + .../fleet-server-configuration.md | 24 +++++ server/config/config.go | 13 +++ server/fleet/request.go | 8 +- server/platform/endpointer/endpoint_utils.go | 32 +++++- .../endpointer/endpoint_utils_test.go | 84 ++++++++++++++++ server/service/endpoint_utils_test.go | 15 +++ server/service/handler.go | 12 ++- server/service/integration_core_test.go | 99 +++++++++++++++++++ 9 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 changes/40813-increase-osquery-log-endpoint-body-size diff --git a/changes/40813-increase-osquery-log-endpoint-body-size b/changes/40813-increase-osquery-log-endpoint-body-size new file mode 100644 index 0000000000..37af6546c0 --- /dev/null +++ b/changes/40813-increase-osquery-log-endpoint-body-size @@ -0,0 +1,2 @@ +* Added configurable body size limits for the `/api/osquery/log` and `/api/osquery/distributed/write` endpoints. +* Fixed false positive `PayloadTooLargeError` errors. \ No newline at end of file diff --git a/docs/Configuration/fleet-server-configuration.md b/docs/Configuration/fleet-server-configuration.md index efa15f2262..fb99039515 100644 --- a/docs/Configuration/fleet-server-configuration.md +++ b/docs/Configuration/fleet-server-configuration.md @@ -1295,6 +1295,30 @@ The minimum time difference between the software's "last opened at" timestamp re min_software_last_opened_at_diff: 4h ``` +### osquery_max_log_write_body_size + +Maximum HTTP request body size accepted by the `osquery/log` endpoint. Increase this if osquery agents are submitting log batches that exceed the default limit. Accepts a byte size with a unit suffix (e.g. `10MiB`, `500KB`). A value of `0` uses the built-in default. Values smaller than the server-wide minimum request body size are silently raised to that minimum. + +- Default value: `10MiB` +- Environment variable: `FLEET_OSQUERY_MAX_LOG_WRITE_BODY_SIZE` +- Config file format: + ```yaml + osquery: + max_log_write_body_size: 20MiB + ``` + +### osquery_max_distributed_write_body_size + +Maximum HTTP request body size accepted by the `osquery/distributed/write` endpoint. Increase this if osquery agents are submitting distributed query results that exceed the default limit. Accepts a byte size with a unit suffix (e.g. `10MiB`, `500KB`). A value of `0` uses the built-in default. Values smaller than the server-wide minimum request body size are silently raised to that minimum. + +- Default value: `5MiB` +- Environment variable: `FLEET_OSQUERY_MAX_DISTRIBUTED_WRITE_BODY_SIZE` +- Config file format: + ```yaml + osquery: + max_distributed_write_body_size: 10MiB + ``` + ## External activity audit logging > Available in Fleet Premium. Activity information is available for all Fleet Free and Fleet Premium instances using the [Activities API](https://fleetdm.com/docs/using-fleet/rest-api#activities). diff --git a/server/config/config.go b/server/config/config.go index 0d23e30e2e..ea3e6ad0fa 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -211,6 +211,13 @@ type OsqueryConfig struct { AsyncHostRedisPopCount int `yaml:"async_host_redis_pop_count"` AsyncHostRedisScanKeysCount int `yaml:"async_host_redis_scan_keys_count"` MinSoftwareLastOpenedAtDiff time.Duration `yaml:"min_software_last_opened_at_diff"` + + // MaxLogWriteBodySize overrides the default body size limit for the + // osquery/log endpoint. A value of 0 means use the built-in default. + MaxLogWriteBodySize int64 `yaml:"max_log_write_body_size"` + // MaxDistributedWriteBodySize overrides the default body size limit for the + // osquery/distributed/write endpoint. A value of 0 means use the built-in default. + MaxDistributedWriteBodySize int64 `yaml:"max_distributed_write_body_size"` } // AsyncTaskName is the type of names that identify tasks supporting @@ -1300,6 +1307,10 @@ func (man Manager) addConfigs() { "Batch size to scan redis keys in async collection") man.addConfigDuration("osquery.min_software_last_opened_at_diff", 2*time.Minute, "Minimum time difference of the software's last opened timestamp (compared to the last one saved) to trigger an update to the database") + man.addConfigByteSize("osquery.max_log_write_body_size", "0", + "Maximum body size for the osquery/log endpoint (e.g. 10MiB, 500KB). 0 means use the built-in default (10MiB). Values below the server minimum request body size are raised to that minimum.") + man.addConfigByteSize("osquery.max_distributed_write_body_size", "0", + "Maximum body size for the osquery/distributed/write endpoint (e.g. 10MiB, 500KB). 0 means use the built-in default (5MiB). Values below the server minimum request body size are raised to that minimum.") // Activities man.addConfigBool("activity.enable_audit_log", false, @@ -1743,6 +1754,8 @@ func (man Manager) LoadConfig() FleetConfig { AsyncHostRedisPopCount: man.getConfigInt("osquery.async_host_redis_pop_count"), AsyncHostRedisScanKeysCount: man.getConfigInt("osquery.async_host_redis_scan_keys_count"), MinSoftwareLastOpenedAtDiff: man.getConfigDuration("osquery.min_software_last_opened_at_diff"), + MaxLogWriteBodySize: man.getConfigByteSize("osquery.max_log_write_body_size"), + MaxDistributedWriteBodySize: man.getConfigByteSize("osquery.max_distributed_write_body_size"), }, Activity: ActivityConfig{ EnableAuditLog: man.getConfigBool("activity.enable_audit_log"), diff --git a/server/fleet/request.go b/server/fleet/request.go index 7f5f3c5e5c..0bf93e68ab 100644 --- a/server/fleet/request.go +++ b/server/fleet/request.go @@ -15,7 +15,9 @@ const ( MaxEULASize int64 = 25 * units.MiB MaxMDMCommandSize int64 = 2 * units.MiB // MaxMultiScriptQuerySize, sets a max size for payloads that take multiple scripts and SQL queries. - MaxMultiScriptQuerySize int64 = 5 * units.MiB - MaxMicrosoftMDMSize int64 = 2 * units.MiB - MaxOsqueryDistributedWriteSize int64 = 5 * units.MiB + MaxMultiScriptQuerySize int64 = 5 * units.MiB + MaxMicrosoftMDMSize int64 = 2 * units.MiB + + DefaultMaxOsqueryDistributedWriteSize int64 = 5 * units.MiB + DefaultMaxOsqueryLogWriteSize int64 = 10 * units.MiB ) diff --git a/server/platform/endpointer/endpoint_utils.go b/server/platform/endpointer/endpoint_utils.go index dc4065bec8..f11132a435 100644 --- a/server/platform/endpointer/endpoint_utils.go +++ b/server/platform/endpointer/endpoint_utils.go @@ -500,6 +500,23 @@ type requestValidator interface { // query parameter decoding logic. // // If adding a new way to parse/decode the requset, make sure to wrap the body in a limited reader with the maxRequestBodySize + +// limitExhaustedBody returns true when the LimitedReader was the cause of an +// unexpected EOF — i.e. the underlying body actually had more data beyond the +// limit. It does this by attempting a single-byte read from the underlying +// reader (limitedReader.R) which bypasses the limit wrapper. A successful read +// means the body exceeded the limit; an immediate EOF means the body ended +// exactly at the limit (malformed JSON, not an oversized payload). +// +// This is used to avoid false-positive PayloadTooLargeError responses for +// bodies whose JSON is malformed and happen to be exactly maxRequestBodySize +// bytes long. +func limitExhaustedBody(limitedReader *io.LimitedReader) bool { + var peek [1]byte + n, _ := limitedReader.R.Read(peek[:]) + return n > 0 +} + func MakeDecoder( iface interface{}, jsonUnmarshal func(body io.Reader, req any) error, @@ -518,8 +535,9 @@ func MakeDecoder( } if rd, ok := iface.(RequestDecoder); ok { return func(ctx context.Context, r *http.Request) (interface{}, error) { + var limitedReader *io.LimitedReader if maxRequestBodySize != -1 { - limitedReader := io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader) + limitedReader = io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader) r.Body = &LimitedReadCloser{ LimitedReader: limitedReader, @@ -527,7 +545,7 @@ func MakeDecoder( } } ret, err := rd.DecodeRequest(ctx, r) - if err != nil && errors.Is(err, io.ErrUnexpectedEOF) { + if err != nil && errors.Is(err, io.ErrUnexpectedEOF) && limitedReader != nil && limitedReader.N == 0 && limitExhaustedBody(limitedReader) { return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize} } return ret, err @@ -544,8 +562,9 @@ func MakeDecoder( nilBody := false var rewriter *JSONKeyRewriteReader + var limitedReader *io.LimitedReader if maxRequestBodySize != -1 { - limitedReader := io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader) + limitedReader = io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader) r.Body = &LimitedReadCloser{ LimitedReader: limitedReader, @@ -590,7 +609,7 @@ func MakeDecoder( } } - if errors.Is(err, io.ErrUnexpectedEOF) { + if errors.Is(err, io.ErrUnexpectedEOF) && limitedReader != nil && limitedReader.N == 0 && limitExhaustedBody(limitedReader) { return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize} } @@ -681,7 +700,10 @@ func MakeDecoder( } if errors.Is(err, io.ErrUnexpectedEOF) { - return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize} + if limitedReader != nil && limitedReader.N == 0 && limitExhaustedBody(limitedReader) { + return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize} + } + return nil, BadRequestErr("json decoder error", err) } return nil, err } diff --git a/server/platform/endpointer/endpoint_utils_test.go b/server/platform/endpointer/endpoint_utils_test.go index 9811cce643..da6c17bcf2 100644 --- a/server/platform/endpointer/endpoint_utils_test.go +++ b/server/platform/endpointer/endpoint_utils_test.go @@ -2,10 +2,14 @@ package endpointer import ( "context" + "encoding/json" + "errors" "fmt" + "io" "log/slog" "net/http" "net/http/httptest" + "strings" "testing" authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz" @@ -14,6 +18,7 @@ import ( "github.com/go-kit/kit/endpoint" kithttp "github.com/go-kit/kit/transport/http" "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -226,3 +231,82 @@ func TestRegisterDeprecatedPathAliasesPanicsOnMissing(t *testing.T) { }) }) } + +func defaultJSONUnmarshal(body io.Reader, req any) error { + return json.NewDecoder(body).Decode(req) +} + +type testRequestDecoderType struct { + Data string `json:"data"` +} + +func (d *testRequestDecoderType) DecodeRequest(ctx context.Context, r *http.Request) (any, error) { + err := json.NewDecoder(r.Body).Decode(d) + return d, err +} + +// TestMakeDecoderRequestDecoderFalsePositive verifies that a body containing +// malformed JSON that is within the size limit does not produce a +// PayloadTooLargeError (false positive) +func TestMakeDecoderRequestDecoderFalsePositive(t *testing.T) { + const limit = 50 + + makeDecoder := func(limit int64) kithttp.DecodeRequestFunc { + return MakeDecoder(&testRequestDecoderType{}, defaultJSONUnmarshal, nil, nil, nil, nil, limit) + } + + t.Run("malformed JSON within limit returns decode error, not 413", func(t *testing.T) { + body := strings.NewReader(`{"data": "truncated`) // malformed, within limit + r := httptest.NewRequest("POST", "/", body) + _, err := makeDecoder(limit)(context.Background(), r) + require.Error(t, err) + var ple platform_http.PayloadTooLargeError + require.False(t, errors.As(err, &ple), "malformed body within limit must not produce PayloadTooLargeError") + }) + + t.Run("body over limit returns 413", func(t *testing.T) { + big := `{"data":"` + strings.Repeat("x", limit+10) + `"}` + body := strings.NewReader(big) + r := httptest.NewRequest("POST", "/", body) + _, err := makeDecoder(limit)(context.Background(), r) + require.Error(t, err) + var ple platform_http.PayloadTooLargeError + require.True(t, errors.As(err, &ple), "body over limit must produce PayloadTooLargeError, got: %v", err) + }) + + t.Run("malformed JSON exactly at limit returns decode error, not 413", func(t *testing.T) { + // Build a body of exactly `limit` bytes that is malformed JSON (no closing + // brace). The LimitedReader is exhausted (N==0), but a peek at the + // underlying reader returns EOF — the body ended at the limit, it was not + // cut short. Must not produce PayloadTooLargeError. + prefix := `{"data":"` + body := strings.NewReader(prefix + strings.Repeat("x", limit-len(prefix))) // exactly limit bytes, no closing + r := httptest.NewRequest("POST", "/", body) + _, err := makeDecoder(limit)(context.Background(), r) + require.Error(t, err) + var ple platform_http.PayloadTooLargeError + require.False(t, errors.As(err, &ple), "malformed body exactly at limit must not produce PayloadTooLargeError") + }) + + t.Run("body over limit without Content-Length returns 413", func(t *testing.T) { + // Simulate a chunked request (no Content-Length) whose body exceeds the + // limit. The peek at the underlying reader finds more data → 413. + big := `{"data":"` + strings.Repeat("x", limit+10) + `"}` + r := httptest.NewRequest("POST", "/", strings.NewReader(big)) + r.ContentLength = -1 // strip the Content-Length that httptest set + _, err := makeDecoder(limit)(context.Background(), r) + require.Error(t, err) + var ple platform_http.PayloadTooLargeError + require.True(t, errors.As(err, &ple), "over-limit body without Content-Length must produce PayloadTooLargeError, got: %v", err) + }) + + t.Run("valid body within limit is decoded successfully", func(t *testing.T) { + body := strings.NewReader(`{"data":"hello"}`) + r := httptest.NewRequest("POST", "/", body) + result, err := makeDecoder(limit)(context.Background(), r) + require.NoError(t, err) + rd, ok := result.(*testRequestDecoderType) + require.True(t, ok) + assert.Equal(t, "hello", rd.Data) + }) +} diff --git a/server/service/endpoint_utils_test.go b/server/service/endpoint_utils_test.go index 63cfc581a6..d949db4d63 100644 --- a/server/service/endpoint_utils_test.go +++ b/server/service/endpoint_utils_test.go @@ -3,6 +3,7 @@ package service import ( "bytes" "context" + "errors" "io" "log/slog" "mime/multipart" @@ -265,6 +266,7 @@ func TestUniversalDecoderSizeLimit(t *testing.T) { } decoder := makeDecoder(universalStruct{}, platform_http.MaxRequestBodySize) + // Body larger than the limit should return PayloadTooLargeError. largeBody := `{"key": "` + strings.Repeat("A", int(platform_http.MaxRequestBodySize)+1) + `"}` req := httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(largeBody)) req = mux.SetURLVars(req, map[string]string{"some-id": "123"}) @@ -273,6 +275,19 @@ func TestUniversalDecoderSizeLimit(t *testing.T) { require.Error(t, err) require.IsType(t, platform_http.PayloadTooLargeError{}, err) + // Body within the limit but with broken JSON + incompleteBody := `{"key": "` + strings.Repeat("A", 100) // missing closing "} + req = httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(incompleteBody)) + req = mux.SetURLVars(req, map[string]string{"some-id": "123"}) + + _, err = decoder(context.Background(), req) + require.Error(t, err) + require.True(t, errors.Is(err, io.ErrUnexpectedEOF), "expected io.ErrUnexpectedEOF, got %T: %v", err, err) + _, isPayloadTooLarge := err.(platform_http.PayloadTooLargeError) + require.False(t, isPayloadTooLarge, "incomplete body within size limit must not produce PayloadTooLargeError, got %T: %v", err, err) + + // Body within the limit and complete ... OK + largeBody = `{"key": "` + strings.Repeat("A", int(platform_http.MaxRequestBodySize)-11) + `"}` // -11 to account for the wrapping JSON req = httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(largeBody)) req = mux.SetURLVars(req, map[string]string{"some-id": "123"}) diff --git a/server/service/handler.go b/server/service/handler.go index 84744020e6..747cdd4e35 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -920,11 +920,19 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC POST("/api/osquery/config", getClientConfigEndpoint, getClientConfigRequest{}) he.WithAltPaths("/api/v1/osquery/distributed/read"). POST("/api/osquery/distributed/read", getDistributedQueriesEndpoint, getDistributedQueriesRequest{}) - he.WithRequestBodySizeLimit(fleet.MaxOsqueryDistributedWriteSize).WithAltPaths("/api/v1/osquery/distributed/write"). + distWriteLimit := config.Osquery.MaxDistributedWriteBodySize + if distWriteLimit == 0 { + distWriteLimit = fleet.DefaultMaxOsqueryDistributedWriteSize + } + he.WithRequestBodySizeLimit(distWriteLimit).WithAltPaths("/api/v1/osquery/distributed/write"). POST("/api/osquery/distributed/write", submitDistributedQueryResultsEndpoint, submitDistributedQueryResultsRequestShim{}) he.WithAltPaths("/api/v1/osquery/carve/begin"). POST("/api/osquery/carve/begin", carveBeginEndpoint, carveBeginRequest{}) - he.WithAltPaths("/api/v1/osquery/log"). + logWriteLimit := config.Osquery.MaxLogWriteBodySize + if logWriteLimit == 0 { + logWriteLimit = fleet.DefaultMaxOsqueryLogWriteSize + } + he.WithRequestBodySizeLimit(logWriteLimit).WithAltPaths("/api/v1/osquery/log"). POST("/api/osquery/log", submitLogsEndpoint, submitLogsRequest{}) he.WithAltPaths("/api/v1/osquery/yara/{name}"). POST("/api/osquery/yara/{name}", getYaraEndpoint, getYaraRequest{}) diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 72a0b5ac4b..90597232fa 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/WatchBeam/clock" + "github.com/docker/go-units" "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server" activity_api "github.com/fleetdm/fleet/v4/server/activity/api" @@ -15890,3 +15891,101 @@ func (s *integrationTestSuite) TestDeleteCertificateTemplateSpec() { require.Equal(t, fleet.MDMOperationTypeRemove, profile.OperationType, "%s profile operation_type should be remove after deletion", tc.hostName) } } + +// TestOsqueryBodySizeLimit verifies the body size limits on the +// /api/osquery/log and /api/osquery/distributed/write endpoints: +// - Bodies exceeding the default limit are rejected with HTTP 413. +// - Bodies within the limit are accepted. +// - A malformed (truncated) body within the limit is NOT reported as HTTP 413 +// (guards against the false-positive PayloadTooLargeError fix). +// - Setting Osquery.MaxLogWriteBodySize / MaxDistributedWriteBodySize in the +// server config overrides the built-in defaults. +func (s *integrationTestSuite) TestOsqueryBodySizeLimit() { + t := s.T() + + host := createOrbitEnrolledHost(t, "linux", "body-limit", s.ds) + + // Body over DefaultMaxOsqueryLogWriteSize must be rejected with 413. The padding + // is inside a JSON string value so the body is syntactically valid up to + // the point where the reader is cut off. + logPrefix := fmt.Sprintf(`{"node_key":%q,"log_type":"status","data":["`, *host.NodeKey) + logSuffix := `"]}` + logPadSize := int(fleet.DefaultMaxOsqueryLogWriteSize) + 1 - len(logPrefix) - len(logSuffix) + require.Positive(t, logPadSize, "padding must be positive; DefaultMaxOsqueryLogWriteSize may be too small") + overLimitLog := []byte(logPrefix + strings.Repeat("x", logPadSize) + logSuffix) + s.DoRawNoAuth("POST", "/api/osquery/log", overLimitLog, http.StatusRequestEntityTooLarge) + + // A well-formed body within the limit is accepted. + withinLimitLog, err := json.Marshal(submitLogsRequest{ + NodeKey: *host.NodeKey, + LogType: "status", + Data: []json.RawMessage{}, + }) + require.NoError(t, err) + s.DoRawNoAuth("POST", "/api/osquery/log", withinLimitLog, http.StatusOK) + + // A truncated (malformed) body within the limit must NOT return 413. + // Before the fix, io.ErrUnexpectedEOF from the JSON decoder was incorrectly + // converted to PayloadTooLargeError even when the reader had not been exhausted. + // The correct response is 400 Bad Request. + truncatedLog := fmt.Appendf(nil, `{"node_key":%q,"log_type":"status","data":[`, *host.NodeKey) // missing closing ]} + s.DoRawNoAuth("POST", "/api/osquery/log", truncatedLog, http.StatusBadRequest) + + // Body over DefaultMaxOsqueryDistributedWriteSize must be rejected with 413. + distPrefix := fmt.Sprintf(`{"node_key":%q,"queries":{"q1":[{"data":"`, *host.NodeKey) + distSuffix := `"}]},"statuses":{"q1":0},"messages":{},"stats":{}}` + distPadSize := int(fleet.DefaultMaxOsqueryDistributedWriteSize) + 1 - len(distPrefix) - len(distSuffix) + require.Positive(t, distPadSize, "padding must be positive; DefaultMaxOsqueryDistributedWriteSize may be too small") + overLimitDist := []byte(distPrefix + strings.Repeat("x", distPadSize) + distSuffix) + s.DoRawNoAuth("POST", "/api/osquery/distributed/write", overLimitDist, http.StatusRequestEntityTooLarge) + + // A well-formed body within the limit is accepted. + withinLimitDist, err := json.Marshal(submitDistributedQueryResultsRequestShim{ + NodeKey: *host.NodeKey, + Results: map[string]json.RawMessage{}, + Statuses: map[string]any{}, + Messages: map[string]string{}, + Stats: map[string]*fleet.Stats{}, + }) + require.NoError(t, err) + s.DoRawNoAuth("POST", "/api/osquery/distributed/write", withinLimitDist, http.StatusOK) + + // A truncated body within the limit must NOT return 413 (same false-positive guard). + // io.ErrUnexpectedEOF from the bodyDecoder path is now wrapped as BadRequestErr → 400. + truncatedDist := fmt.Appendf(nil, `{"node_key":%q,"queries":{"q1":[`, *host.NodeKey) // missing closing + s.DoRawNoAuth("POST", "/api/osquery/distributed/write", truncatedDist, http.StatusBadRequest) + + // Verify that Osquery.MaxLogWriteBodySize and MaxDistributedWriteBodySize + // in the server config override the built-in defaults. + s.Run("config override", func() { + const customLimit = 2 * units.MiB + + cfg := config.TestConfig() + cfg.Osquery.MaxLogWriteBodySize = customLimit + cfg.Osquery.MaxDistributedWriteBodySize = customLimit + + _, customServer := RunServerForTestsWithDS(s.T(), s.ds, &TestServerOpts{ + FleetConfig: &cfg, + SkipCreateTestUsers: true, + }) + s.T().Cleanup(customServer.Close) + ts := withServer{server: customServer} + ts.s = &s.Suite + + // body over the custom limit must return 413. + logPad := customLimit + 1 - len(logPrefix) - len(logSuffix) + require.Positive(s.T(), logPad, "padding must be positive; customLimit may be too small") + ts.DoRawNoAuth("POST", "/api/osquery/log", []byte(logPrefix+strings.Repeat("x", logPad)+logSuffix), http.StatusRequestEntityTooLarge) + + // body within the custom limit must succeed. + ts.DoRawNoAuth("POST", "/api/osquery/log", withinLimitLog, http.StatusOK) + + // body over the custom limit must return 413. + distPad := customLimit + 1 - len(distPrefix) - len(distSuffix) + require.Positive(s.T(), distPad, "padding must be positive; customLimit may be too small") + ts.DoRawNoAuth("POST", "/api/osquery/distributed/write", []byte(distPrefix+strings.Repeat("x", distPad)+distSuffix), http.StatusRequestEntityTooLarge) + + // body within the custom limit must succeed. + ts.DoRawNoAuth("POST", "/api/osquery/distributed/write", withinLimitDist, http.StatusOK) + }) +}