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

Thrift: Payload to metadata filter #23409

Merged
merged 32 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from 27 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
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ proto_library(
"//envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3:pkg",
"//envoy/extensions/filters/network/tcp_proxy/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/router/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/v3:pkg",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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 = [
"//envoy/type/matcher/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
syntax = "proto3";

package envoy.extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3;

import "envoy/type/matcher/v3/regex.proto";

import "google/protobuf/wrappers.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3";
option java_outer_classname = "PayloadToMetadataProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3;payload_to_metadatav3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Payload-To-Metadata Filter]
//
// The configuration for transforming payloads into metadata. This is useful
// for matching load balancer subsets, logging, etc.
//
// Payload to Metadata :ref:`configuration overview <config_thrift_filters_payload_to_metadata>`.
// [#extension: envoy.filters.thrift.payload_to_metadata]

message PayloadToMetadata {
enum ValueType {
STRING = 0;
NUMBER = 1;
}

// [#next-free-field: 6]
message KeyValuePair {
// The namespace — if this is empty, the filter's namespace will be used.
string metadata_namespace = 1;

// The key to use within the namespace.
string key = 2 [(validate.rules).string = {min_len: 1}];

oneof value_type {
// The value to pair with the given key.
//
// When used for on_present case, if value is non-empty it'll be used instead
// of the field value. If both are empty, the field value is used as-is.
//
// When used for on_missing case, a non-empty value must be provided.
string value = 3;

// If present, the header's value will be matched and substituted with this.
// If there is no match or substitution, the field value is used as-is.
//
// This is only used for on_present.
type.matcher.v3.RegexMatchAndSubstitute regex_value_rewrite = 4;
}

// The value's type — defaults to string.
ValueType type = 5 [(validate.rules).enum = {defined_only: true}];
}

// A Rule defines what metadata to apply when a field is present or missing.
// [#next-free-field: 6]
message Rule {
oneof match_specifier {
option (validate.required) = true;

// If specified, the route must exactly match the request method name. As a special case,
// an empty string matches any request method name.
string method_name = 1;

// If specified, the route must have the service name as the request method name prefix.
// As a special case, an empty string matches any service name. Only relevant when service
// multiplexing.
string service_name = 2;
}

// Specifies that a match will be performed on the value of a field.
FieldSelector field_selector = 3 [(validate.rules).message = {required: true}];

// If the field is present, apply this metadata KeyValuePair.
KeyValuePair on_present = 4;

// If the field is missing, apply this metadata KeyValuePair.
//
// The value in the KeyValuePair must be set, since it'll be used in lieu
// of the missing field value.
KeyValuePair on_missing = 5;
}

message FieldSelector {
// field name to log
string name = 1 [(validate.rules).string = {min_len: 1}];

// field id to match. Limit to the range of int16 to fit thrift format.
google.protobuf.Int32Value id = 2 [
JuniorHsu marked this conversation as resolved.
Show resolved Hide resolved
(validate.rules).int32 = {lte: 32767 gte: -32768},
(validate.rules).message = {required: true}
];

// next node of the field selector
FieldSelector child = 3;
}

// The list of rules to apply to requests.
repeated Rule request_rules = 1 [(validate.rules).repeated = {min_items: 1}];
JuniorHsu marked this conversation as resolved.
Show resolved Hide resolved
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ proto_library(
"//envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3:pkg",
"//envoy/extensions/filters/network/tcp_proxy/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/router/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/v3:pkg",
Expand Down
42 changes: 42 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,48 @@ removed_config_or_runtime:
removed ``envoy.reloadable_features.append_or_truncate`` and legacy code paths.

new_features:
- area: header_formatters
change: |
all access log formatters can be used as custom request/response headers. Custom header's syntax is parsed using access logger's parser and
header values are obtained using access log's substitution formatters. This feature can be reversed by setting runtime guard
``envoy.reloadable_features.unified_header_formatter`` to false.
- area: http
change: |
made the :ref:`admission control <envoy_v3_api_msg_extensions.filters.http.admission_control.v3.AdmissionControl>` work as an upstream filter.
change: |
added default-false ``envoy.reloadable_features.http1_use_balsa_parser`` for experimental BalsaParser.
change: |
added ``envoy.reloadable_features.allow_upstream_filters`` for experimental upstream filters.
- area: dns_resolver
change: |
added DNS stats for c-ares DNS resolver. Detailed documentation is available :ref:`here <arch_overview_dns_resolution>`.
- area: gzip
change: |
added support for :ref:`max_inflate_ratio<envoy_v3_api_msg_extensions.compression.gzip.decompressor.v3.Gzip>`.
- area: access_log
change: |
added downstream handshake timing to connection streamInfo. Can be accessed by custom access loggers.
- area: build
change: |
official released binary is now built on Ubuntu 20.04, requires glibc >= 2.30.
- area: listener
change: |
added multiple listening addresses in single listener. :ref:`listener additional addresses<envoy_v3_api_field_config.listener.v3.Listener.additional_addresses>`.
- area: load balancer
change: |
added a new field to subset load balancer config: :ref:`metadata_fallback_policy<envoy_v3_api_field_config.cluster.v3.Cluster.LbSubsetConfig.metadata_fallback_policy>`.
- area: thrift
change: |
added stats for downstream connection close to detect SR drop.
- area: thrift
change: |
added payload to metadata filter which matches a given payload field's value would be extracted and attached to the request as dynamic metadata.
- area: compression
change: |
added support for :ref:`choose_first<envoy_v3_api_msg_extensions.filters.http.compressor.v3.Compressor>`.
- area: cors
change: |
added support for cors PNA. This behavioral change can be temporarily reverted by setting runtime guard ``envoy_reloadable_features_cors_private_network_access`` to false. More details refer to https://developer.chrome.com/blog/private-network-access-preflight.
JuniorHsu marked this conversation as resolved.
Show resolved Hide resolved
- area: upstream
change: |
added a new field :ref:`socket_options <envoy_v3_api_field_config.core.v3.ExtraSourceAddress.socket_options>` to the ExtraSourceAddress, allowing specifying discrete socket options for each source address.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 9090
filter_chains:
- filters:
- name: envoy.filters.network.thrift_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy
stat_prefix: ingress_thrift
route_config:
name: local_route
routes:
- match:
method_name: ""
route:
cluster: versioned-cluster
thrift_filters:
- name: envoy.filters.thrift.payload_to_metadata
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata
request_rules:
- method_name: foo
field_selector:
name: info
id: 2
child:
name: version
id: 1
on_present:
metadata_namespace: envoy.lb
key: version
on_missing:
metadata_namespace: envoy.lb
key: default
value: 'unknown'
clusters:
- name: versioned-cluster
type: STRICT_DNS
lb_policy: ROUND_ROBIN
lb_subset_config:
fallback_policy: NO_FALLBACK
subset_selectors:
- keys:
- default
- keys:
- version
load_assignment:
cluster_name: versioned-cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 19090
metadata:
filter_metadata:
envoy.lb:
default: "true"
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 19091
metadata:
filter_metadata:
envoy.lb:
version: "1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syntax = "proto3";

package request;

message Request {
message Info {
string version = 1;
}
string data = 1;
Info info = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.. _config_thrift_filters_payload_to_metadata:

Envoy Payload-To-Metadata Filter
================================
* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata``.
* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata>`

A typical use case for this filter is to dynamically match a specified payload field of requests
with load balancer subsets. For this, a given payload field's value would be extracted and attached
to the request as dynamic metadata which would then be used to match a subset of endpoints.

We already have :ref:`header-To-metadata filter <envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.header_to_metadata.v3.HeaderToMetadata>`
to achieve the similar goal. However, we have two reasons for introducing new :ref:`payload-To-metadata filter
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata>`:

1. Transports like framed transport don't support THeaders, which is unable to use :ref:`Header-To-Metadata filter
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.header_to_metadata.v3.HeaderToMetadata>`.

2. Directly referring to payload field stops envoy relying on that the downstream service always copies the field
to the THeader correctly and guarantees single truth of source.

This filter is configured with :ref:`request_rules
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.request_rules>`
that will be matched against requests. A
:ref:`field_selector
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.field_selector>`
of a :ref:`rule
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule>`
represents the head of a linked list, each node of the linked list has a :ref:`name
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.FieldSelector.name>`
for logging and an :ref:`id
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.FieldSelector.id>`
for matching. The :ref:`field_selector
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.field_selector>`
is tied to a payload field when the linked list corresponds to a downward path which rooted in the top-level of the
request message structure. :ref:`on_present
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.on_present>`
is triggered when corresponding the payload is present. Otherwise, :ref:`on_missing
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.on_missing>`
is triggered.

Note that if the corresponding payload for a :ref:`rule
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule>`
is present but :ref:`on_present
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.on_present>`
is missing, no metadata is added for this :ref:`rule
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule>`.
. If the corresponding payload for a :ref:`rule
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule>`
is an empty string, neither :ref:`on_present
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.on_present>`
nor :ref:`on_missing
<envoy_v3_api_field_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule.on_missing>`
is triggered. i.e., no metadata is added for this :ref:`rule
<envoy_v3_api_msg_extensions.filters.network.thrift_proxy.filters.payload_to_metadata.v3.PayloadToMetadata.Rule>`.

Currently payload to metadata filter doesn't support container type payload, i.e., list, set and map.

This filter is designed to support payload passthrough. Performing payload to metadata filter
can do deserialization once, and pass the metadata to other filters. This means that load balancing
decisions, consumed from log and routing could all use payload information with a single parse.
Also notably performing the parsing in payload passthrough buffer will mean deserialization once
and not re-serializing, which is the most performant outcome.

If any of the filter chain doesn't support payload passthrough, a customized non-passthrough
filter to setup metadata is encouraged from point of performance view.

Example
-------

A sample filter configuration to route traffic to endpoints based on the presence or
absence of a version payload could be:

.. literalinclude:: _include/payload-to-metadata-filter.yaml
:language: yaml
:lines: 20-38
:lineno-start: 20
:linenos:
:caption: :download:`payload-to-metadata-filter.yaml <_include/payload-to-metadata-filter.yaml>`

A corresponding upstream cluster configuration could be:

.. literalinclude:: _include/header-to-metadata-filter.yaml
:language: yaml
:lines: 39-49
:lineno-start: 37
:linenos:
:caption: :download:`header-to-metadata-filter.yaml <_include/header-to-metadata-filter.yaml>`

The request thrift structure could be:

.. literalinclude:: _include/request.proto
:language: proto

This would then allow requests of method name ``foo`` with the ``version`` payload field which is
under ``info`` field set to be matched against endpoints with the corresponding version. Whereas
requests with that payload missing would be matched with the default endpoints.

The regex matching and substitution is similiar with :ref:`header to metadata filter <config_thrift_filters_header_to_metadata>`.


Statistics
----------

Currently, this filter generates no statistics.
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Envoy has the following builtin Thrift filters.
:maxdepth: 2

header_to_metadata_filter
payload_to_metadata_filter
rate_limit_filter
router_filter
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ EXTENSIONS = {

"envoy.filters.thrift.router": "//source/extensions/filters/network/thrift_proxy/router:config",
"envoy.filters.thrift.header_to_metadata": "//source/extensions/filters/network/thrift_proxy/filters/header_to_metadata:config",
"envoy.filters.thrift.payload_to_metadata": "//source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata:config",
"envoy.filters.thrift.rate_limit": "//source/extensions/filters/network/thrift_proxy/filters/ratelimit:config",

#
Expand Down
Loading