Skip to content

Commit

Permalink
http: add HTTP/1.1 case preservation
Browse files Browse the repository at this point in the history
1) Add new stateful header formatter extension point
2) Add preserve case formatter extension

Fixes #14363

Signed-off-by: Matt Klein <[email protected]>
  • Loading branch information
mattklein123 committed Mar 23, 2021
1 parent 531c6ac commit f8b938c
Show file tree
Hide file tree
Showing 47 changed files with 700 additions and 135 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,5 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
/*/extensions/io_socket/user_space @lambdai @antoniovicente
# Default UUID4 request ID extension
/*/extensions/request_id/uuid @mattklein123 @alyssawilk
# HTTP header formatters
/*/extensions/http/header_formatters/preserve_case @mattklein123 @jmarantz
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ proto_library(
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
7 changes: 7 additions & 0 deletions api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

package envoy.config.core.v3;

import "envoy/config/core/v3/extension.proto";
import "envoy/type/v3/percent.proto";

import "google/protobuf/duration.proto";
Expand Down Expand Up @@ -118,6 +119,7 @@ message Http1ProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions";

// [#next-free-field: 9]
message HeaderKeyFormat {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions.HeaderKeyFormat";
Expand All @@ -136,6 +138,11 @@ message Http1ProtocolOptions {
// Note that while this results in most headers following conventional casing, certain headers
// are not covered. For example, the "TE" header will be formatted as "Te".
ProperCaseWords proper_case_words = 1;

// Configuration for stateful formatter extensions that allow using received headers to
// effect the output of encoding headers. E.g., preserving case during proxying.
// [#extension-category: envoy.http.header_formatters]
TypedExtensionConfig stateful_formatter = 8;
}
}

Expand Down
7 changes: 7 additions & 0 deletions api/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";

package envoy.extensions.http.header_formatters.preserve_case.v3;

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.http.header_formatters.preserve_case.v3";
option java_outer_classname = "PreserveCaseProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Preserve case header formatter]
// [#extension: envoy.http.header_formatters.preserve_case]

// Configuration for the preserve case header formatter.
// See the :ref:`header casing <config_http_conn_man_header_casing>` configuration guide for more
// information.
message PreserveCaseFormatterConfig {
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ proto_library(
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions bazel/envoy_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ EXTENSION_CATEGORIES = [
"envoy.grpc_credentials",
"envoy.guarddog_actions",
"envoy.health_checkers",
"envoy.http.header_formatters",
"envoy.internal_redirect_predicates",
"envoy.io_socket",
"envoy.matching.input_matchers",
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v3/config/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Extensions
watchdog/watchdog
descriptors/descriptors
request_id/request_id
http/header_formatters
8 changes: 8 additions & 0 deletions docs/root/api-v3/config/http/header_formatters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
HTTP header formatters
======================

.. toctree::
:glob:
:maxdepth: 2

../../extensions/http/header_formatters/*/v3/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
http_protocol_options:
header_key_format:
stateful_formatter:
name: preserve_case
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
http_filters:
- name: envoy.filters.http.router
route_config:
virtual_hosts:
- name: default
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: service_foo
clusters:
- name: service_foo
connect_timeout: 15s
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options:
header_key_format:
stateful_formatter:
name: preserve_case
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
load_assignment:
cluster_name: some_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
44 changes: 38 additions & 6 deletions docs/root/configuration/http/http_conn_man/header_casing.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
.. _config_http_conn_man_header_casing:

HTTP/1.1 Header Casing
======================

When handling HTTP/1.1, Envoy will normalize the header keys to be all lowercase. While this is
compliant with the HTTP/1.1 spec, in practice this can result in issues when migrating
existing systems that might rely on specific header casing.

To support these use cases, Envoy allows configuring a formatting scheme for the headers, which
will have Envoy transform the header keys during serialization. To configure this formatting on
response headers, specify the format in the :ref:`http_protocol_options <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_protocol_options>`.
To configure this for upstream request headers, specify the formatting in :ref:`http_protocol_options <envoy_v3_api_msg_extensions.upstreams.http.v3.HttpProtocolOptions>` in the Cluster's :ref:`extension_protocol_options<envoy_v3_api_field_config.cluster.v3.Cluster.typed_extension_protocol_options>`.
To support these use cases, Envoy allows :ref:`configuring a formatting scheme for the headers
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.header_key_format>`, which will have Envoy
transform the header keys during serialization.

To configure this formatting on response headers, specify the format in the
:ref:`http_protocol_options
<envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_protocol_options>`.
To configure this for upstream request headers, specify the formatting in
:ref:`http_protocol_options <envoy_v3_api_msg_extensions.upstreams.http.v3.HttpProtocolOptions>` in
the cluster's
:ref:`extension_protocol_options<envoy_v3_api_field_config.cluster.v3.Cluster.typed_extension_protocol_options>`.

Currently Envoy supports two different types of header key formatters:

Stateless formatters
--------------------

Stateless formatters are run on encoding and do not depend on any previous knowledge of the headers.
An example of this type of formatter is the :ref:`proper case words
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.HeaderKeyFormat.proper_case_words>`
formatter. These formatters are useful when converting to non-HTTP/1 to HTTP/1 or when stateful
formatting is not desired due to increases memory requirements.

Stateful formatters
-------------------

Stateful formatters are instantiated on decoding, passed every decoded header, attached to the
header map, and then available during encoding to format the headers prior to writing. Thus, they
traverse the entire proxy stack. An example of this type of formatter is the :ref:`preserve case
formatter
<envoy_v3_api_msg_extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig>`
configured via the :ref:`stateful_formatter
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.HeaderKeyFormat.stateful_formatter>` field.
The following is an example configuration which will preserve HTTP/1 header case across the proxy.

See :ref:`below <faq_configuration_timeouts_transport_socket>` for other connection timeouts.
on the :ref:`Cluster <envoy_v3_api_field_config.cluster.v3.Cluster.http_protocol_options>`. FIXME
.. literalinclude:: _include/preserve-case.yaml
:language: yaml
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ New Features
* http: added support for `Envoy::ScopeTrackedObject` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. Furthermore, HTTP/1 and HTTP/2 clients now dumps the originating request whose response from the upstream caused Envoy to crash.
* http: added support for :ref:`preconnecting <envoy_v3_api_msg_config.cluster.v3.Cluster.PreconnectPolicy>`. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1.
* http: added new runtime config `envoy.reloadable_features.check_unsupported_typed_per_filter_config`, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it.
* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing <config_http_conn_man_header_casing>` documentation for more information.
* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false.
* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`.
* kill_request: :ref:`Kill Request <config_http_filters_kill_request>` Now supports bidirection killing.
Expand Down
1 change: 1 addition & 0 deletions generated_api_shadow/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ proto_library(
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
7 changes: 7 additions & 0 deletions generated_api_shadow/envoy/config/core/v3/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions generated_api_shadow/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions include/envoy/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ envoy_cc_library(
"abseil_inlined_vector",
],
deps = [
":header_formatter_interface",
"//source/common/common:assert_lib",
"//source/common/common:hash_lib",
],
Expand Down Expand Up @@ -141,3 +142,11 @@ envoy_cc_library(
"//include/envoy/tracing:trace_reason_interface",
],
)

envoy_cc_library(
name = "header_formatter_interface",
hdrs = ["header_formatter.h"],
deps = [
"//include/envoy/config:typed_config_interface",
],
)
3 changes: 2 additions & 1 deletion include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "envoy/buffer/buffer.h"
#include "envoy/common/pure.h"
#include "envoy/grpc/status.h"
#include "envoy/http/header_formatter.h"
#include "envoy/http/header_map.h"
#include "envoy/http/metadata_interface.h"
#include "envoy/http/protocol.h"
Expand Down Expand Up @@ -436,7 +437,7 @@ struct Http1Settings {
};

// How header keys should be formatted when serializing HTTP/1.1 headers.
HeaderKeyFormat header_key_format_{HeaderKeyFormat::Default};
absl::variant<HeaderKeyFormat, StatefulHeaderKeyFormatterFactoryPtr> header_key_format_;

// Behaviour on invalid HTTP messaging:
// - if true, the HTTP/1.1 connection is left open (where possible)
Expand Down
Loading

0 comments on commit f8b938c

Please sign in to comment.