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 1 commit
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 @@ -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.
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
// [#extension-category: envoy.http.header_formatters]
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
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:
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved

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
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
header map, and then available during encoding to format the headers prior to writing. Thus, they
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
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_;
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved

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