Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: add HTTP/1.1 case preservation #15619

Merged
merged 14 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ 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
# External Rate Limit
/*/extensions/filters/common/ratelimit @esmet @mattklein123
/*/extensions/filters/http/ratelimit @esmet @mattklein123
Expand Down
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
// affect the output of encoding headers. E.g., preserving case during proxying.
// [#extension-category: envoy.http.stateful_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.stateful_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.stateful_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
45 changes: 39 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,47 @@
.. _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 mutually exclusive 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 from non-HTTP/1 to HTTP/1 (within a single
proxy or across multiple hops) or when stateful formatting is not desired due to increased memory
requirements.

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

Stateful formatters are instantiated on decoding, called for every decoded header, attached to the
header map, and are 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 @@ -140,6 +140,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.

20 changes: 20 additions & 0 deletions include/envoy/common/optref.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ template <class T> struct OptRef : public absl::optional<std::reference_wrapper<
OptRef(T& t) : absl::optional<std::reference_wrapper<T>>(t) {}
OptRef() = default;

/**
* Copy constructor that allows conversion.
*/
template <class From> explicit OptRef(OptRef<From> rhs) {
if (rhs.has_value()) {
*this = rhs.ref();
}
}

/**
* Assignment that allows conversion.
*/
template <class From> OptRef<T>& operator=(OptRef<From> rhs) {
this->reset();
if (rhs.has_value()) {
*this = rhs.ref();
}
return *this;
}

/**
* Helper to call a method on T. The caller is responsible for ensuring
* has_value() is true.
Expand Down
9 changes: 9 additions & 0 deletions include/envoy/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,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 @@ -142,3 +143,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",
],
)
Loading