diff --git a/docs/content/docs/configuration/reference/reference.adoc b/docs/content/docs/configuration/reference/reference.adoc index b79723b6e..44fee2ad6 100644 --- a/docs/content/docs/configuration/reference/reference.adoc +++ b/docs/content/docs/configuration/reference/reference.adoc @@ -30,6 +30,9 @@ serve: read: 2s write: 5s idle: 2m + buffer_limit: + read: 10KB + write: 10KB tls: key_store: path: /path/to/key/store.pem diff --git a/docs/content/docs/configuration/reference/types.adoc b/docs/content/docs/configuration/reference/types.adoc index 5f738102b..4e6db3f4e 100644 --- a/docs/content/docs/configuration/reference/types.adoc +++ b/docs/content/docs/configuration/reference/types.adoc @@ -417,6 +417,33 @@ message: No groups ending with @acme.co present ---- ==== +== Buffer Limit + +Following configuration properties are supported to limit: + +* *`read`*: _link:{{< relref "#_bytesize" >}}[ByteSize]_ (optional) ++ +The maximum size for the read buffer allowed to read the full request including body. Defaults to 4KB. + +* *`write`*: _link:{{< relref "#_bytesize" >}}[ByteSize]_ (optional) ++ +The maximum size for the write buffer of the response. Defaults to 4KB. + +.Setting the read buffer size limit to 1MB and the write buffer size limit to 2KB. +==== +[source, yaml] +---- +read: 1MB +write: 2KB +---- +==== + +== ByteSize + +ByteSize is actually a string type, which adheres to the following pattern: `^[0-9]+(B|KB|MB)$` + +So with `10B` you can define the byte size of 10 bytes and with `2MB` you can say 2 megabytes. + == CORS https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[CORS] (Cross-Origin Resource Sharing) headers can be added and configured by making use of this type. This functionality allows for advanced security features to quickly be set. If CORS headers are set, then heimdall does not pass preflight requests to its decision pipeline, instead the response will be generated and sent back to the client directly. Following properties are supported: diff --git a/docs/content/docs/configuration/services/decision.adoc b/docs/content/docs/configuration/services/decision.adoc index d92a232f8..c4ae3b740 100644 --- a/docs/content/docs/configuration/services/decision.adoc +++ b/docs/content/docs/configuration/services/decision.adoc @@ -61,6 +61,21 @@ decision: ---- ==== +* *`buffer_limit`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_buffer_limit" >}}[BufferLimit]_ (optional) ++ +Read and write buffer limits (defaults to 4KB) for incoming requests and responses created by heimdall. You can however override this by making use of this property and specifying the limits you need. ++ +.Setting the read buffer size limit to 1MB and the write buffer size limit to 2KB. +==== +[source, yaml] +---- +decision: + buffer_limit: + read: 1MB + write: 2KB +---- +==== + * *`tls`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_tls" >}}[TLS]_ (optional) + By default, the Decision service accepts HTTP requests. Depending on your deployment scenario, you could require Heimdall to accept HTTPs requests only (which is highly recommended). You can do so by making use of this option. diff --git a/docs/content/docs/configuration/services/management.adoc b/docs/content/docs/configuration/services/management.adoc index fe2d7a2da..fa5b3e2fe 100644 --- a/docs/content/docs/configuration/services/management.adoc +++ b/docs/content/docs/configuration/services/management.adoc @@ -59,6 +59,21 @@ management: ---- ==== +* *`buffer_limit`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_buffer_limit" >}}[BufferLimit]_ (optional) ++ +Read and write buffer limits (default to 4KB) for incoming requests and responses created by heimdall. You can however override this by making use of this property and specifying the limits you need. ++ +.Setting the read buffer size limit to 1MB and the write buffer size limit to 2KB. +==== +[source, yaml] +---- +management: + buffer_limit: + read: 1MB + write: 2KB +---- +==== + * *`cors`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_cors" >}}[CORS]_ (optional) + https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[CORS] (Cross-Origin Resource Sharing) headers can be added and configured by making use of this option. This functionality allows for advanced security features to quickly be set. diff --git a/docs/content/docs/configuration/services/proxy.adoc b/docs/content/docs/configuration/services/proxy.adoc index 06edaea2a..1d98cb764 100644 --- a/docs/content/docs/configuration/services/proxy.adoc +++ b/docs/content/docs/configuration/services/proxy.adoc @@ -61,6 +61,21 @@ proxy: ---- ==== +* *`buffer_limit`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_buffer_limit" >}}[BufferLimit]_ (optional) ++ +Read and write buffer limits (default to 4KB) for incoming requests and responses created by heimdall. You can however override this by making use of this property and specifying the limits you need. ++ +.Setting the read buffer size limit to 1MB and the write buffer size limit to 2KB. +==== +[source, yaml] +---- +management: + buffer_limit: + read: 1MB + write: 2KB +---- +==== + * *`cors`*: _link:{{< relref "/docs/configuration/reference/types.adoc#_cors" >}}[CORS]_ (optional) + https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[CORS] (Cross-Origin Resource Sharing) headers can be added and configured by making use of this option. This functionality allows for advanced security features to quickly be set. If CORS headers are set, then the Heimdall does not pass preflight requests neither to its pipeline, nor to the upstream service. Instead, the response will be generated and sent back to the client directly. diff --git a/go.mod b/go.mod index 829ce04ae..3ffca03cb 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/iancoleman/strcase v0.2.0 + github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/instana/go-otel-exporter v1.0.0 github.com/jellydator/ttlcache/v3 v3.0.1 github.com/johannesboyne/gofakes3 v0.0.0-20230506070712-04da935ef877 diff --git a/go.sum b/go.sum index 1fca8eace..f30df731b 100644 --- a/go.sum +++ b/go.sum @@ -1160,8 +1160,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= -github.com/go-co-op/gocron v1.28.3 h1:swTsge6u/1Ei51b9VLMz/YTzEzWpbsk5SiR7m5fklTI= -github.com/go-co-op/gocron v1.28.3/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= github.com/go-co-op/gocron v1.29.0 h1:HHKBSnCqurMw8eENEcBIDGwoU1TY7wkH1CKzf1Rm/3M= github.com/go-co-op/gocron v1.29.0/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -1597,6 +1595,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/instana/go-otel-exporter v1.0.0 h1:s7PPvvB8xcSRNaXpgjYpBQWnFZRAqGGJZPkQ/j6RNjU= github.com/instana/go-otel-exporter v1.0.0/go.mod h1:chO0kaNOIV+bhh+eYRBiSShhuOHMV6HHQYgVo/7xxAs= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= @@ -3299,7 +3299,6 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE= google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= diff --git a/internal/config/configuration.go b/internal/config/configuration.go index b0859707f..3a75771d1 100644 --- a/internal/config/configuration.go +++ b/internal/config/configuration.go @@ -43,6 +43,7 @@ func NewConfiguration(envPrefix EnvVarPrefix, configFile ConfigurationPath) (*Co opts := []parser.Option{ parser.WithDecodeHookFunc(mapstructure.StringToTimeDurationHookFunc()), parser.WithDecodeHookFunc(mapstructure.StringToSliceHookFunc(",")), + parser.WithDecodeHookFunc(stringToByteSizeHookFunc()), parser.WithDecodeHookFunc(logLevelDecodeHookFunc), parser.WithDecodeHookFunc(logFormatDecodeHookFunc), parser.WithDecodeHookFunc(decodeTLSCipherSuiteHookFunc), diff --git a/internal/config/default_configuration.go b/internal/config/default_configuration.go index bb7addad3..1f5f9cf92 100644 --- a/internal/config/default_configuration.go +++ b/internal/config/default_configuration.go @@ -19,6 +19,7 @@ package config import ( "time" + "github.com/inhies/go-bytesize" "github.com/rs/zerolog" ) @@ -33,6 +34,8 @@ const ( defaultMetricsServicePort = 10250 defaultProfilingServicePort = 10251 + defaultBufferSize = 4 * bytesize.KB + loopbackIP = "127.0.0.1" ) @@ -46,6 +49,10 @@ func defaultConfig() Configuration { Write: defaultWriteTimeout, Idle: defaultIdleTimeout, }, + BufferLimit: BufferLimit{ + Read: defaultBufferSize, + Write: defaultBufferSize, + }, }, Decision: ServiceConfig{ Port: defaultDecisionServicePort, @@ -54,6 +61,10 @@ func defaultConfig() Configuration { Write: defaultWriteTimeout, Idle: defaultIdleTimeout, }, + BufferLimit: BufferLimit{ + Read: defaultBufferSize, + Write: defaultBufferSize, + }, }, Management: ServiceConfig{ Port: defaultManagementServicePort, @@ -62,6 +73,10 @@ func defaultConfig() Configuration { Write: defaultWriteTimeout, Idle: defaultIdleTimeout, }, + BufferLimit: BufferLimit{ + Read: defaultBufferSize, + Write: defaultBufferSize, + }, }, }, Log: LoggingConfig{ diff --git a/internal/config/mapstructure_decoder.go b/internal/config/mapstructure_decoder.go index b334738b6..6d8ba8706 100644 --- a/internal/config/mapstructure_decoder.go +++ b/internal/config/mapstructure_decoder.go @@ -20,6 +20,8 @@ import ( "crypto/tls" "reflect" + "github.com/inhies/go-bytesize" + "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" "github.com/dadrus/heimdall/internal/heimdall" @@ -125,3 +127,19 @@ func decodeTLSMinVersionHookFunc(from reflect.Type, to reflect.Type, data any) ( return data, errorchain.NewWithMessagef(heimdall.ErrConfiguration, "TLS version %s is unsupported", data) } } + +func stringToByteSizeHookFunc() mapstructure.DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + + if t != reflect.TypeOf(bytesize.ByteSize(0)) { + return data, nil + } + + // Convert it by parsing + // nolint: forcetypeassert + return bytesize.Parse(data.(string)) + } +} diff --git a/internal/config/mapstructure_decoder_test.go b/internal/config/mapstructure_decoder_test.go index fe0f5e821..fa531fd83 100644 --- a/internal/config/mapstructure_decoder_test.go +++ b/internal/config/mapstructure_decoder_test.go @@ -20,6 +20,7 @@ import ( "crypto/tls" "testing" + "github.com/inhies/go-bytesize" "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -36,102 +37,20 @@ func TestDecodeLogLevel(t *testing.T) { } for _, tc := range []struct { - uc string - config []byte - assert func(t *testing.T, err error, level zerolog.Level) + config string + expect zerolog.Level }{ - { - uc: "debug level", - config: []byte(`level: debug`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.DebugLevel, level) - }, - }, - { - uc: "info level", - config: []byte(`level: info`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.InfoLevel, level) - }, - }, - { - uc: "warn level", - config: []byte(`level: warn`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.WarnLevel, level) - }, - }, - { - uc: "error level", - config: []byte(`level: error`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.ErrorLevel, level) - }, - }, - { - uc: "fatal level", - config: []byte(`level: fatal`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.FatalLevel, level) - }, - }, - { - uc: "panic level", - config: []byte(`level: panic`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.PanicLevel, level) - }, - }, - { - uc: "no level", - config: []byte(`level: no`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.NoLevel, level) - }, - }, - { - uc: "disabled", - config: []byte(`level: disabled`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.Disabled, level) - }, - }, - { - uc: "trace level", - config: []byte(`level: trace`), - assert: func(t *testing.T, err error, level zerolog.Level) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, zerolog.TraceLevel, level) - }, - }, + {config: `level: debug`, expect: zerolog.DebugLevel}, + {config: `level: info`, expect: zerolog.InfoLevel}, + {config: `level: warn`, expect: zerolog.WarnLevel}, + {config: `level: error`, expect: zerolog.ErrorLevel}, + {config: `level: fatal`, expect: zerolog.FatalLevel}, + {config: `level: panic`, expect: zerolog.PanicLevel}, + {config: `level: no`, expect: zerolog.NoLevel}, + {config: `level: disabled`, expect: zerolog.Disabled}, + {config: `level: trace`, expect: zerolog.TraceLevel}, } { - t.Run("case="+tc.uc, func(t *testing.T) { + t.Run("case="+tc.config, func(t *testing.T) { // GIVEN var typ Type @@ -141,14 +60,15 @@ func TestDecodeLogLevel(t *testing.T) { }) require.NoError(t, err) - conf, err := testsupport.DecodeTestConfig(tc.config) + conf, err := testsupport.DecodeTestConfig([]byte(tc.config)) require.NoError(t, err) // WHEN err = dec.Decode(conf) // THEN - tc.assert(t, err, typ.Level) + require.NoError(t, err) + assert.Equal(t, tc.expect, typ.Level) }) } } @@ -161,42 +81,14 @@ func TestDecodeLogFormat(t *testing.T) { } for _, tc := range []struct { - uc string - config []byte - assert func(t *testing.T, err error, format LogFormat) + config string + expect LogFormat }{ - { - uc: "gelf format", - config: []byte(`format: gelf`), - assert: func(t *testing.T, err error, format LogFormat) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, LogGelfFormat, format) - }, - }, - { - uc: "text format", - config: []byte(`format: text`), - assert: func(t *testing.T, err error, format LogFormat) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, LogTextFormat, format) - }, - }, - { - uc: "unknown format with text as fallback", - config: []byte(`format: foo`), - assert: func(t *testing.T, err error, format LogFormat) { - t.Helper() - - require.NoError(t, err) - assert.Equal(t, LogTextFormat, format) - }, - }, + {config: `format: gelf`, expect: LogGelfFormat}, + {config: `format: text`, expect: LogTextFormat}, + {config: `format: foo`, expect: LogTextFormat}, } { - t.Run("case="+tc.uc, func(t *testing.T) { + t.Run("case="+tc.config, func(t *testing.T) { // GIVEN var typ Type @@ -206,14 +98,15 @@ func TestDecodeLogFormat(t *testing.T) { }) require.NoError(t, err) - conf, err := testsupport.DecodeTestConfig(tc.config) + conf, err := testsupport.DecodeTestConfig([]byte(tc.config)) require.NoError(t, err) // WHEN err = dec.Decode(conf) // THEN - tc.assert(t, err, typ.Format) + require.NoError(t, err) + assert.Equal(t, tc.expect, typ.Format) }) } } @@ -361,3 +254,41 @@ func TestDecodeTLSMinVersion(t *testing.T) { }) } } + +func TestStringToByteSizeHookFunc(t *testing.T) { + t.Parallel() + + type Type struct { + Size bytesize.ByteSize `mapstructure:"size"` + } + + for _, tc := range []struct { + config string + expect bytesize.ByteSize + }{ + {config: "size: 1B", expect: 1 * bytesize.B}, + {config: "size: 3KB", expect: 3 * bytesize.KB}, + {config: "size: 5MB", expect: 5 * bytesize.MB}, + } { + t.Run("case="+tc.config, func(t *testing.T) { + // GIVEN + var typ Type + + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: stringToByteSizeHookFunc(), + Result: &typ, + }) + require.NoError(t, err) + + conf, err := testsupport.DecodeTestConfig([]byte(tc.config)) + require.NoError(t, err) + + // WHEN + err = dec.Decode(conf) + + // THEN + require.NoError(t, err) + assert.Equal(t, tc.expect, typ.Size) + }) + } +} diff --git a/internal/config/parser/merge.go b/internal/config/parser/merge.go index b9d93c779..1d3433f8b 100644 --- a/internal/config/parser/merge.go +++ b/internal/config/parser/merge.go @@ -29,16 +29,20 @@ func merge(dest, src any) any { vDst := reflect.ValueOf(dest) vSrc := reflect.ValueOf(src) - if vSrc.Type() != vDst.Type() { - panic(fmt.Sprintf("Cannot merge %s and %s. Types are different: %s - %s", dest, src, vDst.Type(), vSrc.Type())) - } - // nolint: exhaustive switch vDst.Kind() { case reflect.Map: + if vSrc.Type() != vDst.Type() { + panic(fmt.Sprintf("Cannot merge %s and %s. Types are different: %s - %s", dest, src, vDst.Type(), vSrc.Type())) + } + // nolint: forcetypeassert return mergeMaps(dest.(map[string]any), src.(map[string]any)) case reflect.Slice: + if vSrc.Type() != vDst.Type() { + panic(fmt.Sprintf("Cannot merge %s and %s. Types are different: %s - %s", dest, src, vDst.Type(), vSrc.Type())) + } + // nolint: forcetypeassert return mergeSlices(dest.([]any), src.([]any)) default: diff --git a/internal/config/serve.go b/internal/config/serve.go index 384149080..54420e3f3 100644 --- a/internal/config/serve.go +++ b/internal/config/serve.go @@ -20,8 +20,15 @@ import ( "crypto/tls" "fmt" "time" + + "github.com/inhies/go-bytesize" ) +type BufferLimit struct { + Read bytesize.ByteSize `koanf:"read"` + Write bytesize.ByteSize `koanf:"write"` +} + type Timeout struct { Read time.Duration `koanf:"read,string"` Write time.Duration `koanf:"write,string"` @@ -75,6 +82,7 @@ type ServiceConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` Timeout Timeout `koanf:"timeout"` + BufferLimit BufferLimit `koanf:"buffer_limit"` CORS *CORS `koanf:"cors,omitempty"` TLS *TLS `koanf:"tls,omitempty"` TrustedProxies *[]string `koanf:"trusted_proxies,omitempty"` diff --git a/internal/config/test_data/test_config.yaml b/internal/config/test_data/test_config.yaml index 6a52e9740..e7eb05eb4 100644 --- a/internal/config/test_data/test_config.yaml +++ b/internal/config/test_data/test_config.yaml @@ -6,6 +6,9 @@ serve: read: 2s write: 5s idle: 2m + buffer_limit: + read: 4KB + write: 4KB tls: key_store: path: /path/to/keystore/file.pem @@ -41,6 +44,9 @@ serve: read: 2s write: 5s idle: 2m + buffer_limit: + read: 4KB + write: 4KB cors: allowed_origins: - example.org @@ -82,6 +88,9 @@ serve: read: 2s write: 5s idle: 2m + buffer_limit: + read: 4KB + write: 4KB cors: allowed_origins: - example.org diff --git a/internal/handler/decision/app.go b/internal/handler/decision/app.go index cd4fb2701..8f884b870 100644 --- a/internal/handler/decision/app.go +++ b/internal/handler/decision/app.go @@ -53,6 +53,8 @@ func newApp(args appArgs) *fiber.App { ReadTimeout: service.Timeout.Read, WriteTimeout: service.Timeout.Write, IdleTimeout: service.Timeout.Idle, + ReadBufferSize: int(service.BufferLimit.Read), + WriteBufferSize: int(service.BufferLimit.Write), DisableStartupMessage: true, EnableTrustedProxyCheck: true, TrustedProxies: x.IfThenElseExec(service.TrustedProxies != nil, diff --git a/internal/handler/envoyextauth/grpcv3/service.go b/internal/handler/envoyextauth/grpcv3/service.go index 850279dca..b37b17bd0 100644 --- a/internal/handler/envoyextauth/grpcv3/service.go +++ b/internal/handler/envoyextauth/grpcv3/service.go @@ -95,6 +95,8 @@ func newService( srv := grpc.NewServer( grpc.KeepaliveParams(keepalive.ServerParameters{Timeout: service.Timeout.Idle}), + grpc.ReadBufferSize(int(service.BufferLimit.Read)), + grpc.WriteBufferSize(int(service.BufferLimit.Write)), grpc.UnknownServiceHandler(func(srv interface{}, stream grpc.ServerStream) error { return status.Error(codes.Unknown, "unknown service or method") }), diff --git a/internal/handler/management/app.go b/internal/handler/management/app.go index 2a52e8488..6bbeeef7d 100644 --- a/internal/handler/management/app.go +++ b/internal/handler/management/app.go @@ -53,6 +53,8 @@ func newApp(args appArgs) *fiber.App { ReadTimeout: service.Timeout.Read, WriteTimeout: service.Timeout.Write, IdleTimeout: service.Timeout.Idle, + ReadBufferSize: int(service.BufferLimit.Read), + WriteBufferSize: int(service.BufferLimit.Write), DisableStartupMessage: true, EnableTrustedProxyCheck: true, JSONDecoder: json.Unmarshal, diff --git a/internal/handler/proxy/app.go b/internal/handler/proxy/app.go index 8758c0439..7e2d7f1a1 100644 --- a/internal/handler/proxy/app.go +++ b/internal/handler/proxy/app.go @@ -57,6 +57,8 @@ func newApp(args appArgs) *fiber.App { ReadTimeout: service.Timeout.Read, WriteTimeout: service.Timeout.Write, IdleTimeout: service.Timeout.Idle, + ReadBufferSize: int(service.BufferLimit.Read), + WriteBufferSize: int(service.BufferLimit.Write), DisableStartupMessage: true, EnableTrustedProxyCheck: true, TrustedProxies: x.IfThenElseExec(service.TrustedProxies != nil, diff --git a/schema/config.schema.json b/schema/config.schema.json index 5c48d6961..a97523ebb 100644 --- a/schema/config.schema.json +++ b/schema/config.schema.json @@ -205,7 +205,35 @@ } } }, - + "bufferLimitConfig": { + "type": "object", + "description": "Overrides the default request and response buffer sizes", + "additionalProperties": false, + "properties": { + "read": { + "description": "The maximum buffer size for reading the entire request, including the body. Increase this parameter to prevent unexpected closing a client connection if the client request exceeds the default 4KB.", + "type": "string", + "default": "4KB", + "pattern": "^[0-9]+(B|KB|MB)$", + "examples": [ + "5KB", + "1MB", + "1.6MB" + ] + }, + "write": { + "description": "The maximum buffer size for writing the response. Increase this parameter to prevent unexpected closing a client connection if a response create by heimdall exceeds the default 4KB.", + "type": "string", + "default": "4KB", + "pattern": "^[0-9]+(B|KB|MB)$", + "examples": [ + "5KB", + "1MB", + "1.6MB" + ] + } + } + }, "responseOverride": { "type": "object", "description": "Overrides the defaults for responses", @@ -1788,6 +1816,9 @@ "timeout": { "$ref": "#/definitions/timeoutConfig" }, + "buffer_limit": { + "$ref": "#/definitions/bufferLimitConfig" + }, "tls": { "$ref": "#/definitions/tlsConfig" }, @@ -1824,6 +1855,9 @@ "timeout": { "$ref": "#/definitions/timeoutConfig" }, + "buffer_limit": { + "$ref": "#/definitions/bufferLimitConfig" + }, "cors": { "$ref": "#/definitions/corsConfig" }, @@ -1863,6 +1897,9 @@ "timeout": { "$ref": "#/definitions/timeoutConfig" }, + "buffer_limit": { + "$ref": "#/definitions/bufferLimitConfig" + }, "cors": { "$ref": "#/definitions/corsConfig" },