From 520d88e4cb4e8c5014531281a88dcc8076e18bfd Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Mon, 5 Aug 2024 23:21:00 +0530 Subject: [PATCH] Matchers: Add dynamic metadata to the http inputs (#34891) fixes: https://github.com/envoyproxy/envoy/issues/34092 --------- Signed-off-by: Vikas Choudhary --- CODEOWNERS | 4 + api/BUILD | 1 + .../network/v3/network_inputs.proto | 45 +++++ .../matching/input_matchers/metadata/v3/BUILD | 12 ++ .../input_matchers/metadata/v3/metadata.proto | 26 +++ api/versioning/BUILD | 1 + changelogs/current.yaml | 4 + .../common_messages/common_messages.rst | 1 + .../advanced/matching/matching_api.rst | 1 + envoy/http/filter.h | 3 + source/extensions/extensions_build_config.bzl | 6 + source/extensions/extensions_metadata.yaml | 14 ++ .../filters/http/rbac/rbac_filter.cc | 3 + .../matching/http/metadata_input/BUILD | 22 +++ .../http/metadata_input/meta_input.cc | 20 +++ .../matching/http/metadata_input/meta_input.h | 82 +++++++++ .../matching/input_matchers/metadata/BUILD | 36 ++++ .../input_matchers/metadata/config.cc | 29 ++++ .../matching/input_matchers/metadata/config.h | 33 ++++ .../input_matchers/metadata/matcher.cc | 28 +++ .../input_matchers/metadata/matcher.h | 32 ++++ .../matching/input_matchers/metadata/BUILD | 33 ++++ .../metadata/dyn_meta_matcher_test.cc | 162 ++++++++++++++++++ tools/extensions/extensions_schema.yaml | 1 + 24 files changed, 599 insertions(+) create mode 100644 api/envoy/extensions/matching/input_matchers/metadata/v3/BUILD create mode 100644 api/envoy/extensions/matching/input_matchers/metadata/v3/metadata.proto create mode 100644 source/extensions/matching/http/metadata_input/BUILD create mode 100644 source/extensions/matching/http/metadata_input/meta_input.cc create mode 100644 source/extensions/matching/http/metadata_input/meta_input.h create mode 100644 source/extensions/matching/input_matchers/metadata/BUILD create mode 100644 source/extensions/matching/input_matchers/metadata/config.cc create mode 100644 source/extensions/matching/input_matchers/metadata/config.h create mode 100644 source/extensions/matching/input_matchers/metadata/matcher.cc create mode 100644 source/extensions/matching/input_matchers/metadata/matcher.h create mode 100644 test/extensions/matching/input_matchers/metadata/BUILD create mode 100644 test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 23e833dfa840..68e00305fd21 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -177,12 +177,16 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /*/extensions/matching/input_matchers/runtime_fraction @ravenblackx @sergkir85 # CEL input matcher /*/extensions/matching/input_matchers/cel_matcher @tyxia @yanavlasov +# dynamic metadata input matcher +/*/extensions/matching/input_matchers/metadata @vikaschoudhary16 @kyessenov # environment generic input /*/extensions/matching/common_inputs/environment @donyu @mattklein123 # format string matching /*/extensions/matching/actions/format_string @kyessenov @cpakulski # CEL data input /*/extensions/matching/http/cel_input @tyxia @yanavlasov +# dynamic metadata input +/*/extensions/matching/http/metadata_input @vikaschoudhary16 @kyessenov # user space socket pair, event, connection and listener /*/extensions/io_socket/user_space @kyessenov @lambdai @soulxu /*/extensions/bootstrap/internal_listener @kyessenov @adisuissa diff --git a/api/BUILD b/api/BUILD index 7a1db4f427aa..931fbea9c428 100644 --- a/api/BUILD +++ b/api/BUILD @@ -291,6 +291,7 @@ proto_library( "//envoy/extensions/matching/common_inputs/ssl/v3:pkg", "//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg", "//envoy/extensions/matching/input_matchers/ip/v3:pkg", + "//envoy/extensions/matching/input_matchers/metadata/v3:pkg", "//envoy/extensions/matching/input_matchers/runtime_fraction/v3:pkg", "//envoy/extensions/network/dns_resolver/apple/v3:pkg", "//envoy/extensions/network/dns_resolver/cares/v3:pkg", diff --git a/api/envoy/extensions/matching/common_inputs/network/v3/network_inputs.proto b/api/envoy/extensions/matching/common_inputs/network/v3/network_inputs.proto index 59756bc0c07b..bea415a7101f 100644 --- a/api/envoy/extensions/matching/common_inputs/network/v3/network_inputs.proto +++ b/api/envoy/extensions/matching/common_inputs/network/v3/network_inputs.proto @@ -103,3 +103,48 @@ message ApplicationProtocolInput { message FilterStateInput { string key = 1 [(validate.rules).string = {min_len: 1}]; } + +// Input that matches dynamic metadata by key. +// DynamicMetadataInput provides a general interface using ``filter`` and ``path`` to retrieve value from +// :ref:`Metadata `. +// +// For example, for the following Metadata: +// +// .. code-block:: yaml +// +// filter_metadata: +// envoy.xxx: +// prop: +// foo: bar +// xyz: +// hello: envoy +// +// The following DynamicMetadataInput will retrieve a string value "bar" from the Metadata. +// +// .. code-block:: yaml +// +// filter: envoy.xxx +// path: +// - key: prop +// - key: foo +// +// [#extension: envoy.matching.inputs.dynamic_metadata] +message DynamicMetadataInput { + // Specifies the segment in a path to retrieve value from Metadata. + // Note: Currently it's not supported to retrieve a value from a list in Metadata. This means that + // if the segment key refers to a list, it has to be the last segment in a path. + message PathSegment { + oneof segment { + option (validate.required) = true; + + // If specified, use the key to retrieve the value in a Struct. + string key = 1 [(validate.rules).string = {min_len: 1}]; + } + } + + // The filter name to retrieve the Struct from the Metadata. + string filter = 1 [(validate.rules).string = {min_len: 1}]; + + // The path to retrieve the Value from the Struct. + repeated PathSegment path = 2 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/envoy/extensions/matching/input_matchers/metadata/v3/BUILD b/api/envoy/extensions/matching/input_matchers/metadata/v3/BUILD new file mode 100644 index 000000000000..bfc486330911 --- /dev/null +++ b/api/envoy/extensions/matching/input_matchers/metadata/v3/BUILD @@ -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_xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/matching/input_matchers/metadata/v3/metadata.proto b/api/envoy/extensions/matching/input_matchers/metadata/v3/metadata.proto new file mode 100644 index 000000000000..19d74fb41bf7 --- /dev/null +++ b/api/envoy/extensions/matching/input_matchers/metadata/v3/metadata.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package envoy.extensions.matching.input_matchers.metadata.v3; + +import "envoy/type/matcher/v3/value.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.matching.input_matchers.metadata.v3"; +option java_outer_classname = "MetadataProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/metadata/v3;metadatav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: metadata matcher] +// [#extension: envoy.matching.matchers.metadata_matcher] + +// Metadata matcher for metadata from http matching input data. +message Metadata { + // The Metadata is matched if the value retrieved by metadata matching input is matched to this value. + type.matcher.v3.ValueMatcher value = 1 [(validate.rules).message = {required: true}]; + + // If true, the match result will be inverted. + bool invert = 4; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 96791470d5df..ea5e0db90006 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -230,6 +230,7 @@ proto_library( "//envoy/extensions/matching/common_inputs/ssl/v3:pkg", "//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg", "//envoy/extensions/matching/input_matchers/ip/v3:pkg", + "//envoy/extensions/matching/input_matchers/metadata/v3:pkg", "//envoy/extensions/matching/input_matchers/runtime_fraction/v3:pkg", "//envoy/extensions/network/dns_resolver/apple/v3:pkg", "//envoy/extensions/network/dns_resolver/cares/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 95b1c2dbf4a9..d2729ce93cd2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -125,6 +125,10 @@ new_features: ` to allow overriding TLS certificate selection behavior. An extension can select certificate base on the incoming SNI, in both sync and async mode. +- area: matching + change: | + Added dynamic metadata matcher support :ref:`Dynamic metadata input ` + and :ref:`Dynamic metadata input matcher `. - area: ratelimit change: | Added the ability to modify :ref:`hits_addend ` diff --git a/docs/root/api-v3/common_messages/common_messages.rst b/docs/root/api-v3/common_messages/common_messages.rst index a1f3488a86d8..0b5c9032b933 100644 --- a/docs/root/api-v3/common_messages/common_messages.rst +++ b/docs/root/api-v3/common_messages/common_messages.rst @@ -31,6 +31,7 @@ Common messages ../extensions/early_data/v3/default_early_data_policy.proto ../config/core/v3/http_uri.proto ../extensions/matching/input_matchers/ip/v3/ip.proto + ../extensions/matching/input_matchers/metadata/v3/metadata.proto ../extensions/matching/input_matchers/runtime_fraction/v3/runtime_fraction.proto ../config/core/v3/address.proto ../config/core/v3/protocol.proto diff --git a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst index 8876c47814b6..09ac66e2539f 100644 --- a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst +++ b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst @@ -30,6 +30,7 @@ These input functions are available for matching HTTP requests: * :ref:`Response header value `. * :ref:`Response trailer value `. * :ref:`Query parameters value `. +* :ref:`Dynamic metadata `. .. _extension_category_envoy.matching.network.input: diff --git a/envoy/http/filter.h b/envoy/http/filter.h index 49d2609a1152..f9cc5450c87b 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -1200,6 +1200,9 @@ class HttpMatchingData { virtual const Network::ConnectionInfoProvider& connectionInfoProvider() const PURE; const StreamInfo::FilterState& filterState() const { return streamInfo().filterState(); } + const envoy::config::core::v3::Metadata& metadata() const { + return streamInfo().dynamicMetadata(); + } const Network::Address::Instance& localAddress() const { return *connectionInfoProvider().localAddress(); diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index ed7cb6a0391d..f06cd6fb1034 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -81,6 +81,7 @@ EXTENSIONS = { "envoy.matching.matchers.ip": "//source/extensions/matching/input_matchers/ip:config", "envoy.matching.matchers.runtime_fraction": "//source/extensions/matching/input_matchers/runtime_fraction:config", "envoy.matching.matchers.cel_matcher": "//source/extensions/matching/input_matchers/cel_matcher:config", + "envoy.matching.matchers.metadata_matcher": "//source/extensions/matching/input_matchers/metadata:config", # # Network Matchers @@ -109,6 +110,11 @@ EXTENSIONS = { # "envoy.matching.inputs.cel_data_input": "//source/extensions/matching/http/cel_input:cel_input_lib", + # + # Dynamic Metadata Matching Input + # + "envoy.matching.inputs.dynamic_metadata": "//source/extensions/matching/http/metadata_input:metadata_input_lib", + # # Matching actions # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 483b4294bc84..35893425a135 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1565,6 +1565,13 @@ envoy.matching.inputs.filter_state: status: stable type_urls: - envoy.extensions.matching.common_inputs.network.v3.FilterStateInput +envoy.matching.inputs.dynamic_metadata: + categories: + - envoy.matching.http.input + security_posture: unknown + status: stable + type_urls: + - envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput envoy.matching.inputs.uri_san: categories: - envoy.matching.http.input @@ -1597,6 +1604,13 @@ envoy.matching.custom_matchers.trie_matcher: status: stable type_urls: - xds.type.matcher.v3.IPMatcher +envoy.matching.matchers.metadata_matcher: + categories: + - envoy.matching.input_matchers + security_posture: unknown + status: alpha + type_urls: + - envoy.extensions.matching.input_matchers.metadata.v3.Metadata envoy.load_balancing_policies.least_request: categories: - envoy.load_balancing_policies diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc index e5ad66b2c9b9..cefa97f010c4 100644 --- a/source/extensions/filters/http/rbac/rbac_filter.cc +++ b/source/extensions/filters/http/rbac/rbac_filter.cc @@ -47,6 +47,9 @@ absl::Status ActionValidationVisitor::performDataInputValidation( {TypeUtil::descriptorFullNameToTypeUrl( envoy::extensions::matching::common_inputs::ssl::v3::SubjectInput::descriptor() ->full_name())}, + {TypeUtil::descriptorFullNameToTypeUrl(envoy::extensions::matching::common_inputs::network:: + v3::DynamicMetadataInput::descriptor() + ->full_name())}, {TypeUtil::descriptorFullNameToTypeUrl( xds::type::matcher::v3::HttpAttributesCelMatchInput::descriptor()->full_name())}}; if (allowed_inputs_set.contains(type_url)) { diff --git a/source/extensions/matching/http/metadata_input/BUILD b/source/extensions/matching/http/metadata_input/BUILD new file mode 100644 index 000000000000..8415d265c488 --- /dev/null +++ b/source/extensions/matching/http/metadata_input/BUILD @@ -0,0 +1,22 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "metadata_input_lib", + srcs = ["meta_input.cc"], + hdrs = ["meta_input.h"], + deps = [ + "//envoy/http:filter_interface", + "//envoy/matcher:matcher_interface", + "//envoy/registry", + "//source/common/config:metadata_lib", + "@envoy_api//envoy/extensions/matching/common_inputs/network/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/matching/http/metadata_input/meta_input.cc b/source/extensions/matching/http/metadata_input/meta_input.cc new file mode 100644 index 000000000000..f5542211b4b7 --- /dev/null +++ b/source/extensions/matching/http/metadata_input/meta_input.cc @@ -0,0 +1,20 @@ +#include "source/extensions/matching/http/metadata_input/meta_input.h" + +#include "envoy/registry/registry.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace Http { +namespace MetadataInput { + +class HttpDynamicMetadataInputFactory + : public DynamicMetadataInputBaseFactory {}; +REGISTER_FACTORY(HttpDynamicMetadataInputFactory, + Matcher::DataInputFactory); + +} // namespace MetadataInput +} // namespace Http +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/matching/http/metadata_input/meta_input.h b/source/extensions/matching/http/metadata_input/meta_input.h new file mode 100644 index 000000000000..cec7b0253052 --- /dev/null +++ b/source/extensions/matching/http/metadata_input/meta_input.h @@ -0,0 +1,82 @@ +#pragma once + +#include "envoy/extensions/matching/common_inputs/network/v3/network_inputs.pb.h" +#include "envoy/extensions/matching/common_inputs/network/v3/network_inputs.pb.validate.h" +#include "envoy/http/filter.h" +#include "envoy/matcher/matcher.h" + +#include "source/common/config/metadata.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace Http { +namespace MetadataInput { + +class MetadataMatchData : public ::Envoy::Matcher::CustomMatchData { +public: + explicit MetadataMatchData(const ProtobufWkt::Value& value) : value_(value) {} + const ProtobufWkt::Value& value_; +}; + +template +class DynamicMetadataInput : public Matcher::DataInput { +public: + DynamicMetadataInput( + const envoy::extensions::matching::common_inputs::network::v3::DynamicMetadataInput& + input_config) + : filter_(input_config.filter()), path_(initializePath(input_config.path())) {} + + Matcher::DataInputGetResult get(const MatchingDataType& data) const override { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + std::make_unique( + Envoy::Config::Metadata::metadataValue(&data.metadata(), filter_, path_))}; + } + +private: + static std::vector initializePath( + const Protobuf::RepeatedPtrField& segments) { + std::vector path; + for (const auto& seg : segments) { + path.push_back(seg.key()); + } + return path; + } + + const std::string filter_; + const std::vector path_; +}; + +template +class DynamicMetadataInputBaseFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "envoy.matching.inputs.dynamic_metadata"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor) override { + const auto& typed_config = MessageUtil::downcastAndValidate< + const envoy::extensions::matching::common_inputs::network::v3::DynamicMetadataInput&>( + message, validation_visitor); + auto config_ptr = std::make_shared< + envoy::extensions::matching::common_inputs::network::v3::DynamicMetadataInput>( + typed_config); + return [config_ptr] { + return std::make_unique>(*config_ptr); + }; + }; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::matching::common_inputs::network::v3::DynamicMetadataInput>(); + } +}; + +DECLARE_FACTORY(HttpDymanicMetadataInputFactory); + +} // namespace MetadataInput +} // namespace Http +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/matching/input_matchers/metadata/BUILD b/source/extensions/matching/input_matchers/metadata/BUILD new file mode 100644 index 000000000000..91710ef4ab89 --- /dev/null +++ b/source/extensions/matching/input_matchers/metadata/BUILD @@ -0,0 +1,36 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "metadata_lib", + srcs = ["matcher.cc"], + hdrs = ["matcher.h"], + deps = [ + "//envoy/matcher:matcher_interface", + "//source/common/common:matchers_lib", + "//source/extensions/matching/http/metadata_input:metadata_input_lib", + "@envoy_api//envoy/extensions/matching/input_matchers/metadata/v3:pkg_cc_proto", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":metadata_lib", + "//envoy/matcher:matcher_interface", + "//envoy/registry", + "//envoy/server:factory_context_interface", + "@envoy_api//envoy/extensions/matching/input_matchers/metadata/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/matching/input_matchers/metadata/config.cc b/source/extensions/matching/input_matchers/metadata/config.cc new file mode 100644 index 000000000000..6b78ba7a8e17 --- /dev/null +++ b/source/extensions/matching/input_matchers/metadata/config.cc @@ -0,0 +1,29 @@ +#include "source/extensions/matching/input_matchers/metadata/config.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace InputMatchers { +namespace Metadata { + +Envoy::Matcher::InputMatcherFactoryCb +Config::createInputMatcherFactoryCb(const Protobuf::Message& config, + Server::Configuration::ServerFactoryContext& factory_context) { + const auto& matcher_config = MessageUtil::downcastAndValidate< + const envoy::extensions::matching::input_matchers::metadata::v3::Metadata&>( + config, factory_context.messageValidationVisitor()); + const auto& value = matcher_config.value(); + const auto value_matcher = Envoy::Matchers::ValueMatcher::create(value, factory_context); + const bool invert = matcher_config.invert(); + return [value_matcher, invert]() { return std::make_unique(value_matcher, invert); }; +} +/** + * Static registration for the Metadata matcher. @see RegisterFactory. + */ +REGISTER_FACTORY(Config, Envoy::Matcher::InputMatcherFactory); + +} // namespace Metadata +} // namespace InputMatchers +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/matching/input_matchers/metadata/config.h b/source/extensions/matching/input_matchers/metadata/config.h new file mode 100644 index 000000000000..6b6207c8bc0f --- /dev/null +++ b/source/extensions/matching/input_matchers/metadata/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/extensions/matching/input_matchers/metadata/v3/metadata.pb.h" +#include "envoy/extensions/matching/input_matchers/metadata/v3/metadata.pb.validate.h" +#include "envoy/matcher/matcher.h" +#include "envoy/server/factory_context.h" + +#include "source/common/protobuf/utility.h" +#include "source/extensions/matching/input_matchers/metadata/matcher.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace InputMatchers { +namespace Metadata { + +class Config : public Envoy::Matcher::InputMatcherFactory { +public: + Envoy::Matcher::InputMatcherFactoryCb createInputMatcherFactoryCb( + const Protobuf::Message& config, + Server::Configuration::ServerFactoryContext& factory_context) override; + + std::string name() const override { return "envoy.matching.matchers.metadata_matcher"; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; +} // namespace Metadata +} // namespace InputMatchers +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/matching/input_matchers/metadata/matcher.cc b/source/extensions/matching/input_matchers/metadata/matcher.cc new file mode 100644 index 000000000000..6ede36a3f174 --- /dev/null +++ b/source/extensions/matching/input_matchers/metadata/matcher.cc @@ -0,0 +1,28 @@ +#include "source/extensions/matching/input_matchers/metadata/matcher.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace InputMatchers { +namespace Metadata { + +Matcher::Matcher(const Envoy::Matchers::ValueMatcherConstSharedPtr value_matcher, const bool invert) + : value_matcher_(value_matcher), invert_(invert) {} + +bool Matcher::match(const Envoy::Matcher::MatchingDataType& input) { + if (auto* ptr = absl::get_if>(&input); + ptr != nullptr) { + const Matching::Http::MetadataInput::MetadataMatchData* match_data = + dynamic_cast(ptr->get()); + if (match_data != nullptr) { + return value_matcher_->match(match_data->value_) ^ invert_; + } + } + return false; +} + +} // namespace Metadata +} // namespace InputMatchers +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/matching/input_matchers/metadata/matcher.h b/source/extensions/matching/input_matchers/metadata/matcher.h new file mode 100644 index 000000000000..e6e8a356c7b1 --- /dev/null +++ b/source/extensions/matching/input_matchers/metadata/matcher.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "envoy/extensions/matching/input_matchers/metadata/v3/metadata.pb.h" +#include "envoy/matcher/matcher.h" +#include "envoy/type/matcher/v3/value.pb.h" + +#include "source/common/common/matchers.h" +#include "source/extensions/matching/http/metadata_input/meta_input.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace InputMatchers { +namespace Metadata { + +class Matcher : public Envoy::Matcher::InputMatcher, Logger::Loggable { +public: + Matcher(const Envoy::Matchers::ValueMatcherConstSharedPtr, const bool); + bool match(const Envoy::Matcher::MatchingDataType& input) override; + +private: + Envoy::Matchers::ValueMatcherConstSharedPtr value_matcher_; + bool invert_; +}; + +} // namespace Metadata +} // namespace InputMatchers +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/matching/input_matchers/metadata/BUILD b/test/extensions/matching/input_matchers/metadata/BUILD new file mode 100644 index 000000000000..e77588a746a2 --- /dev/null +++ b/test/extensions/matching/input_matchers/metadata/BUILD @@ -0,0 +1,33 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "dyn_meta_matcher_test", + srcs = ["dyn_meta_matcher_test.cc"], + extension_names = ["envoy.matching.matchers.metadata_matcher"], + deps = [ + "//source/common/matcher:matcher_lib", + "//source/extensions/matching/http/metadata_input:metadata_input_lib", + "//source/extensions/matching/input_matchers/metadata:config", + "//source/extensions/matching/input_matchers/metadata:metadata_lib", + "//test/common/matcher:test_utility_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/matcher:matcher_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:registry_lib", + "@com_github_cncf_xds//xds/type/matcher/v3:pkg_cc_proto", + "@envoy_api//envoy/config/common/matcher/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc new file mode 100644 index 000000000000..58df2f51e16b --- /dev/null +++ b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc @@ -0,0 +1,162 @@ +#include +#include + +#include "envoy/config/common/matcher/v3/matcher.pb.validate.h" +#include "envoy/config/core/v3/extension.pb.h" +#include "envoy/matcher/matcher.h" +#include "envoy/registry/registry.h" + +#include "source/common/matcher/matcher.h" +#include "source/common/protobuf/utility.h" +#include "source/extensions/matching/http/metadata_input/meta_input.h" +#include "source/extensions/matching/input_matchers/metadata/config.h" +#include "source/extensions/matching/input_matchers/metadata/matcher.h" + +#include "test/common/matcher/test_utility.h" +#include "test/mocks/matcher/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" +#include "xds/type/matcher/v3/matcher.pb.validate.h" + +namespace Envoy { +namespace Extensions { +namespace Matching { +namespace InputMatchers { +namespace Metadata { + +constexpr absl::string_view kFilterNamespace = "meta_matcher"; +constexpr absl::string_view kMetadataKey = "service_name"; +constexpr absl::string_view kMetadataValue = "test_service"; + +class MetadataMatcherTest : public ::testing::Test { +public: + MetadataMatcherTest() + : inject_action_(action_factory_), + data_(Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_)) {} + + ::Envoy::Matcher::MatchTreeSharedPtr + buildMatcherTree(bool invert = false) { + envoy::extensions::matching::input_matchers::metadata::v3::Metadata meta_matcher; + meta_matcher.mutable_value()->mutable_string_match()->set_exact(kMetadataValue); + meta_matcher.set_invert(invert); + + xds::type::matcher::v3::Matcher matcher; + auto* inner_matcher = matcher.mutable_matcher_list()->add_matchers(); + auto* single_predicate = inner_matcher->mutable_predicate()->mutable_single_predicate(); + + envoy::extensions::matching::common_inputs::network::v3::DynamicMetadataInput metadata_input; + metadata_input.set_filter(kFilterNamespace); + metadata_input.mutable_path()->Add()->set_key(kMetadataKey); + single_predicate->mutable_input()->set_name("envoy.matching.inputs.dynamic_metadata"); + single_predicate->mutable_input()->mutable_typed_config()->PackFrom(metadata_input); + + auto* custom_matcher = single_predicate->mutable_custom_match(); + custom_matcher->mutable_typed_config()->PackFrom(meta_matcher); + + xds::type::matcher::v3::Matcher::OnMatch on_match; + std::string on_match_config = R"EOF( + action: + name: test_action + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: match!! + )EOF"; + MessageUtil::loadFromYaml(on_match_config, on_match, + ProtobufMessage::getStrictValidationVisitor()); + + inner_matcher->mutable_on_match()->MergeFrom(on_match); + + auto string_factory_on_match = ::Envoy::Matcher::TestDataInputStringFactory("value"); + + ::Envoy::Matcher::MockMatchTreeValidationVisitor + validation_visitor; + EXPECT_CALL(validation_visitor, + performDataInputValidation( + _, "type.googleapis.com/" + "envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput")); + ::Envoy::Matcher::MatchTreeFactory + matcher_factory(context_, factory_context_, validation_visitor); + auto match_tree = matcher_factory.create(matcher); + + return match_tree(); + } + + void setDynamicMetadata(const std::string& namespace_str, const std::string& metadata_key, + const std::string& metadata_value) { + Envoy::Config::Metadata::mutableMetadataValue(stream_info_.metadata_, namespace_str, + metadata_key) + .set_string_value(metadata_value); + } + + ::Envoy::Matcher::StringActionFactory action_factory_; + Registry::InjectFactory> inject_action_; + testing::NiceMock stream_info_; + absl::string_view context_ = ""; + testing::NiceMock factory_context_; + envoy::config::core::v3::Metadata metadata_; + + Envoy::Http::Matching::HttpMatchingDataImpl data_; +}; + +TEST_F(MetadataMatcherTest, DynamicMetadataMatched) { + setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), + std::string(kMetadataValue)); + Envoy::Http::Matching::HttpMatchingDataImpl data = + Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); + auto matcher_tree = buildMatcherTree(); + const auto result = matcher_tree->match(data_); + // The match was complete, match found. + EXPECT_EQ(result.match_state_, ::Envoy::Matcher::MatchState::MatchComplete); + EXPECT_TRUE(result.on_match_.has_value()); + EXPECT_NE(result.on_match_->action_cb_, nullptr); +} + +TEST_F(MetadataMatcherTest, DynamicMetadataNotMatched) { + setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); + Envoy::Http::Matching::HttpMatchingDataImpl data = + Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); + auto matcher_tree = buildMatcherTree(); + + const auto result = matcher_tree->match(data_); + // The match was completed, no match found. + EXPECT_EQ(result.match_state_, ::Envoy::Matcher::MatchState::MatchComplete); + EXPECT_EQ(result.on_match_, absl::nullopt); +} + +TEST_F(MetadataMatcherTest, DynamicMetadataNotMatchedWithInvert) { + setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); + Envoy::Http::Matching::HttpMatchingDataImpl data = + Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); + auto matcher_tree = buildMatcherTree(true); + + const auto result = matcher_tree->match(data_); + // The match was completed, no match found but the invert flag was set. + EXPECT_EQ(result.match_state_, ::Envoy::Matcher::MatchState::MatchComplete); + EXPECT_TRUE(result.on_match_.has_value()); + EXPECT_NE(result.on_match_->action_cb_, nullptr); +} + +TEST_F(MetadataMatcherTest, BadData) { + envoy::extensions::matching::input_matchers::metadata::v3::Metadata meta_matcher; + meta_matcher.mutable_value()->mutable_string_match()->set_exact(kMetadataValue); + const auto& matcher_config = MessageUtil::downcastAndValidate< + const envoy::extensions::matching::input_matchers::metadata::v3::Metadata&>( + meta_matcher, factory_context_.messageValidationVisitor()); + const auto& v = matcher_config.value(); + auto value_matcher = Envoy::Matchers::ValueMatcher::create(v, factory_context_); + + ::Envoy::Matcher::MatchingDataType data = absl::monostate(); + EXPECT_NO_THROW(Matcher(value_matcher, false).match(data)); + + ::Envoy::Matcher::MatchingDataType data2 = std::string("test"); + EXPECT_NO_THROW(Matcher(value_matcher, false).match(data2)); +} + +} // namespace Metadata +} // namespace InputMatchers +} // namespace Matching +} // namespace Extensions +} // namespace Envoy diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index c1e9f4ffdb1d..c8f589a5fa67 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -22,6 +22,7 @@ builtin: - envoy.matching.inputs.transport_protocol - envoy.matching.inputs.application_protocol - envoy.matching.inputs.filter_state +- envoy.matching.inputs.dynamic_metadata - envoy.matching.inputs.uri_san - envoy.matching.inputs.dns_san - envoy.matching.inputs.subject