diff --git a/config/envoyconfig/listeners.go b/config/envoyconfig/listeners.go index ed3a24200..53cbddb65 100644 --- a/config/envoyconfig/listeners.go +++ b/config/envoyconfig/listeners.go @@ -340,7 +340,8 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter( IdleTimeout: durationpb.New(options.IdleTimeout), MaxStreamDuration: maxStreamDuration, }, - RequestTimeout: durationpb.New(options.ReadTimeout), + HttpProtocolOptions: http1ProtocolOptions, + RequestTimeout: durationpb.New(options.ReadTimeout), Tracing: &envoy_http_connection_manager.HttpConnectionManager_Tracing{ RandomSampling: &envoy_type_v3.Percent{Value: options.TracingSampleRate * 100}, Provider: tracingProvider, diff --git a/config/envoyconfig/listeners_test.go b/config/envoyconfig/listeners_test.go index 44d2c4b0d..72357711c 100644 --- a/config/envoyconfig/listeners_test.go +++ b/config/envoyconfig/listeners_test.go @@ -742,6 +742,16 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) { "useRemoteAddress": true, "skipXffAppend": true, "xffNumTrustedHops": 1, + "httpProtocolOptions": { + "headerKeyFormat": { + "statefulFormatter": { + "name": "preserve_case", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig" + } + } + } + }, "localReplyConfig":{ "mappers":[ { diff --git a/config/envoyconfig/protocols.go b/config/envoyconfig/protocols.go index b10dda6f0..3e34792b1 100644 --- a/config/envoyconfig/protocols.go +++ b/config/envoyconfig/protocols.go @@ -4,6 +4,7 @@ import ( "context" envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_extensions_http_header_formatters_preserve_case_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3" envoy_extensions_upstreams_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -28,6 +29,18 @@ const ( initialConnectionWindowSizeLimit uint32 = 1 * 1024 * 1024 ) +var http1ProtocolOptions = &envoy_config_core_v3.Http1ProtocolOptions{ + // fix for #3935, preserve case of HTTP headers for applications that are case-sensitive + HeaderKeyFormat: &envoy_config_core_v3.Http1ProtocolOptions_HeaderKeyFormat{ + HeaderFormat: &envoy_config_core_v3.Http1ProtocolOptions_HeaderKeyFormat_StatefulFormatter{ + StatefulFormatter: &envoy_config_core_v3.TypedExtensionConfig{ + Name: "preserve_case", + TypedConfig: marshalAny(&envoy_extensions_http_header_formatters_preserve_case_v3.PreserveCaseFormatterConfig{}), + }, + }, + }, +} + var http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{ AllowConnect: true, MaxConcurrentStreams: wrapperspb.UInt32(maxConcurrentStreams), @@ -35,13 +48,19 @@ var http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{ InitialConnectionWindowSize: wrapperspb.UInt32(initialConnectionWindowSizeLimit), } -func buildTypedExtensionProtocolOptions(endpoints []Endpoint, upstreamProtocol upstreamProtocolConfig) map[string]*anypb.Any { +func buildTypedExtensionProtocolOptions( + endpoints []Endpoint, + upstreamProtocol upstreamProtocolConfig, +) map[string]*anypb.Any { return map[string]*anypb.Any{ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": marshalAny(buildUpstreamProtocolOptions(endpoints, upstreamProtocol)), } } -func buildUpstreamProtocolOptions(endpoints []Endpoint, upstreamProtocol upstreamProtocolConfig) *envoy_extensions_upstreams_http_v3.HttpProtocolOptions { +func buildUpstreamProtocolOptions( + endpoints []Endpoint, + upstreamProtocol upstreamProtocolConfig, +) *envoy_extensions_upstreams_http_v3.HttpProtocolOptions { switch upstreamProtocol { case upstreamProtocolHTTP2: // when explicitly configured, force HTTP/2 @@ -66,6 +85,7 @@ func buildUpstreamProtocolOptions(endpoints []Endpoint, upstreamProtocol upstrea return &envoy_extensions_upstreams_http_v3.HttpProtocolOptions{ UpstreamProtocolOptions: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_AutoConfig{ AutoConfig: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_AutoHttpConfig{ + HttpProtocolOptions: http1ProtocolOptions, Http2ProtocolOptions: http2ProtocolOptions, }, }, @@ -78,7 +98,7 @@ func buildUpstreamProtocolOptions(endpoints []Endpoint, upstreamProtocol upstrea UpstreamProtocolOptions: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig_{ ExplicitHttpConfig: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig{ ProtocolConfig: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{ - HttpProtocolOptions: &envoy_config_core_v3.Http1ProtocolOptions{}, + HttpProtocolOptions: http1ProtocolOptions, }, }, }, diff --git a/config/envoyconfig/protocols_test.go b/config/envoyconfig/protocols_test.go new file mode 100644 index 000000000..f25bb6852 --- /dev/null +++ b/config/envoyconfig/protocols_test.go @@ -0,0 +1,36 @@ +package envoyconfig + +import ( + "testing" + + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_extensions_http_header_formatters_preserve_case_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3" + envoy_extensions_upstreams_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/testing/protocmp" +) + +func Test_buildUpstreamProtocolOptions(t *testing.T) { + t.Parallel() + + assert.Empty(t, + cmp.Diff(&envoy_extensions_upstreams_http_v3.HttpProtocolOptions{ + UpstreamProtocolOptions: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig_{ + ExplicitHttpConfig: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig{ + ProtocolConfig: &envoy_extensions_upstreams_http_v3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{ + HttpProtocolOptions: &envoy_config_core_v3.Http1ProtocolOptions{ + HeaderKeyFormat: &envoy_config_core_v3.Http1ProtocolOptions_HeaderKeyFormat{ + HeaderFormat: &envoy_config_core_v3.Http1ProtocolOptions_HeaderKeyFormat_StatefulFormatter{ + StatefulFormatter: &envoy_config_core_v3.TypedExtensionConfig{ + Name: "preserve_case", + TypedConfig: marshalAny(&envoy_extensions_http_header_formatters_preserve_case_v3.PreserveCaseFormatterConfig{}), + }, + }, + }, + }, + }, + }, + }, + }, buildUpstreamProtocolOptions(nil, upstreamProtocolHTTP1), protocmp.Transform())) +}