LCOV - code coverage report
Current view: top level - source/common/router - router_ratelimit.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 21 264 8.0 %
Date: 2024-01-05 06:35:25 Functions: 3 25 12.0 %

          Line data    Source code
       1             : #include "source/common/router/router_ratelimit.h"
       2             : 
       3             : #include <cstdint>
       4             : #include <memory>
       5             : #include <string>
       6             : #include <vector>
       7             : 
       8             : #include "envoy/config/core/v3/base.pb.h"
       9             : #include "envoy/config/route/v3/route_components.pb.h"
      10             : 
      11             : #include "source/common/common/assert.h"
      12             : #include "source/common/common/empty_string.h"
      13             : #include "source/common/config/metadata.h"
      14             : #include "source/common/config/utility.h"
      15             : #include "source/common/http/matching/data_impl.h"
      16             : #include "source/common/matcher/matcher.h"
      17             : #include "source/common/protobuf/utility.h"
      18             : 
      19             : namespace Envoy {
      20             : namespace Router {
      21             : 
      22             : namespace {
      23             : bool populateDescriptor(const std::vector<RateLimit::DescriptorProducerPtr>& actions,
      24             :                         std::vector<RateLimit::DescriptorEntry>& descriptor_entries,
      25             :                         const std::string& local_service_cluster,
      26           0 :                         const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo& info) {
      27           0 :   bool result = true;
      28           0 :   for (const RateLimit::DescriptorProducerPtr& action : actions) {
      29           0 :     RateLimit::DescriptorEntry descriptor_entry;
      30           0 :     result = result &&
      31           0 :              action->populateDescriptor(descriptor_entry, local_service_cluster, headers, info);
      32           0 :     if (!result) {
      33           0 :       break;
      34           0 :     }
      35           0 :     if (!descriptor_entry.key_.empty()) {
      36           0 :       descriptor_entries.push_back(descriptor_entry);
      37           0 :     }
      38           0 :   }
      39           0 :   return result;
      40           0 : }
      41             : 
      42             : class RateLimitDescriptorValidationVisitor
      43             :     : public Matcher::MatchTreeValidationVisitor<Http::HttpMatchingData> {
      44             : public:
      45             :   absl::Status performDataInputValidation(const Matcher::DataInputFactory<Http::HttpMatchingData>&,
      46           0 :                                           absl::string_view) override {
      47           0 :     return absl::OkStatus();
      48           0 :   }
      49             : };
      50             : 
      51             : class MatchInputRateLimitDescriptor : public RateLimit::DescriptorProducer {
      52             : public:
      53             :   MatchInputRateLimitDescriptor(const std::string& descriptor_key,
      54             :                                 Matcher::DataInputPtr<Http::HttpMatchingData>&& data_input)
      55           0 :       : descriptor_key_(descriptor_key), data_input_(std::move(data_input)) {}
      56             : 
      57             :   // Ratelimit::DescriptorProducer
      58             :   bool populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
      59             :                           const Http::RequestHeaderMap& headers,
      60           0 :                           const StreamInfo::StreamInfo& info) const override {
      61           0 :     Http::Matching::HttpMatchingDataImpl data(info);
      62           0 :     data.onRequestHeaders(headers);
      63           0 :     auto result = data_input_->get(data);
      64           0 :     if (absl::holds_alternative<absl::monostate>(result.data_)) {
      65           0 :       return false;
      66           0 :     }
      67           0 :     const std::string& str = absl::get<std::string>(result.data_);
      68           0 :     if (!str.empty()) {
      69           0 :       descriptor_entry = {descriptor_key_, str};
      70           0 :     }
      71           0 :     return true;
      72           0 :   }
      73             : 
      74             : private:
      75             :   const std::string descriptor_key_;
      76             :   Matcher::DataInputPtr<Http::HttpMatchingData> data_input_;
      77             : };
      78             : 
      79             : } // namespace
      80             : 
      81             : const uint64_t RateLimitPolicyImpl::MAX_STAGE_NUMBER = 10UL;
      82             : 
      83             : bool DynamicMetadataRateLimitOverride::populateOverride(
      84           0 :     RateLimit::Descriptor& descriptor, const envoy::config::core::v3::Metadata* metadata) const {
      85           0 :   const ProtobufWkt::Value& metadata_value =
      86           0 :       Envoy::Config::Metadata::metadataValue(metadata, metadata_key_);
      87           0 :   if (metadata_value.kind_case() != ProtobufWkt::Value::kStructValue) {
      88           0 :     return false;
      89           0 :   }
      90             : 
      91           0 :   const auto& override_value = metadata_value.struct_value().fields();
      92           0 :   const auto& limit_it = override_value.find("requests_per_unit");
      93           0 :   const auto& unit_it = override_value.find("unit");
      94           0 :   if (limit_it != override_value.end() &&
      95           0 :       limit_it->second.kind_case() == ProtobufWkt::Value::kNumberValue &&
      96           0 :       unit_it != override_value.end() &&
      97           0 :       unit_it->second.kind_case() == ProtobufWkt::Value::kStringValue) {
      98           0 :     envoy::type::v3::RateLimitUnit unit;
      99           0 :     if (envoy::type::v3::RateLimitUnit_Parse(unit_it->second.string_value(), &unit)) {
     100           0 :       descriptor.limit_.emplace(RateLimit::RateLimitOverride{
     101           0 :           static_cast<uint32_t>(limit_it->second.number_value()), unit});
     102           0 :       return true;
     103           0 :     }
     104           0 :   }
     105           0 :   return false;
     106           0 : }
     107             : 
     108             : bool SourceClusterAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     109             :                                              const std::string& local_service_cluster,
     110             :                                              const Http::RequestHeaderMap&,
     111           0 :                                              const StreamInfo::StreamInfo&) const {
     112           0 :   descriptor_entry = {"source_cluster", local_service_cluster};
     113           0 :   return true;
     114           0 : }
     115             : 
     116             : bool DestinationClusterAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     117             :                                                   const std::string&, const Http::RequestHeaderMap&,
     118           0 :                                                   const StreamInfo::StreamInfo& info) const {
     119           0 :   if (info.route() == nullptr || info.route()->routeEntry() == nullptr) {
     120           0 :     return false;
     121           0 :   }
     122           0 :   descriptor_entry = {"destination_cluster", info.route()->routeEntry()->clusterName()};
     123           0 :   return true;
     124           0 : }
     125             : 
     126             : bool RequestHeadersAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     127             :                                               const std::string&,
     128             :                                               const Http::RequestHeaderMap& headers,
     129           0 :                                               const StreamInfo::StreamInfo&) const {
     130           0 :   const auto header_value = headers.get(header_name_);
     131             : 
     132             :   // If header is not present in the request and if skip_if_absent is true skip this descriptor,
     133             :   // while calling rate limiting service. If skip_if_absent is false, do not call rate limiting
     134             :   // service.
     135           0 :   if (header_value.empty()) {
     136           0 :     return skip_if_absent_;
     137           0 :   }
     138             :   // TODO(https://github.com/envoyproxy/envoy/issues/13454): Potentially populate all header values.
     139           0 :   descriptor_entry = {descriptor_key_, std::string(header_value[0]->value().getStringView())};
     140           0 :   return true;
     141           0 : }
     142             : 
     143             : bool RemoteAddressAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     144             :                                              const std::string&, const Http::RequestHeaderMap&,
     145           0 :                                              const StreamInfo::StreamInfo& info) const {
     146           0 :   const Network::Address::InstanceConstSharedPtr& remote_address =
     147           0 :       info.downstreamAddressProvider().remoteAddress();
     148           0 :   if (remote_address->type() != Network::Address::Type::Ip) {
     149           0 :     return false;
     150           0 :   }
     151             : 
     152           0 :   descriptor_entry = {"remote_address", remote_address->ip()->addressAsString()};
     153             : 
     154           0 :   return true;
     155           0 : }
     156             : 
     157             : bool MaskedRemoteAddressAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     158             :                                                    const std::string&,
     159             :                                                    const Http::RequestHeaderMap&,
     160           0 :                                                    const StreamInfo::StreamInfo& info) const {
     161           0 :   const Network::Address::InstanceConstSharedPtr& remote_address =
     162           0 :       info.downstreamAddressProvider().remoteAddress();
     163           0 :   if (remote_address->type() != Network::Address::Type::Ip) {
     164           0 :     return false;
     165           0 :   }
     166             : 
     167           0 :   uint32_t mask_len = v4_prefix_mask_len_;
     168           0 :   if (remote_address->ip()->version() == Network::Address::IpVersion::v6) {
     169           0 :     mask_len = v6_prefix_mask_len_;
     170           0 :   }
     171             : 
     172             :   // TODO: increase the efficiency, avoid string transform back and forth
     173           0 :   Network::Address::CidrRange cidr_entry =
     174           0 :       Network::Address::CidrRange::create(remote_address->ip()->addressAsString(), mask_len);
     175           0 :   descriptor_entry = {"masked_remote_address", cidr_entry.asString()};
     176             : 
     177           0 :   return true;
     178           0 : }
     179             : 
     180             : bool GenericKeyAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     181             :                                           const std::string&, const Http::RequestHeaderMap&,
     182           0 :                                           const StreamInfo::StreamInfo&) const {
     183           0 :   descriptor_entry = {descriptor_key_, descriptor_value_};
     184           0 :   return true;
     185           0 : }
     186             : 
     187             : MetaDataAction::MetaDataAction(const envoy::config::route::v3::RateLimit::Action::MetaData& action)
     188             :     : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()),
     189             :       default_value_(action.default_value()), source_(action.source()),
     190           0 :       skip_if_absent_(action.skip_if_absent()) {}
     191             : 
     192             : MetaDataAction::MetaDataAction(
     193             :     const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action)
     194             :     : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()),
     195             :       default_value_(action.default_value()),
     196             :       source_(envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC),
     197           0 :       skip_if_absent_(false) {}
     198             : 
     199             : bool MetaDataAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     200             :                                         const std::string&, const Http::RequestHeaderMap&,
     201           0 :                                         const StreamInfo::StreamInfo& info) const {
     202           0 :   const envoy::config::core::v3::Metadata* metadata_source;
     203             : 
     204           0 :   switch (source_) {
     205           0 :     PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
     206           0 :   case envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC:
     207           0 :     metadata_source = &info.dynamicMetadata();
     208           0 :     break;
     209           0 :   case envoy::config::route::v3::RateLimit::Action::MetaData::ROUTE_ENTRY:
     210           0 :     metadata_source = &info.route()->metadata();
     211           0 :     break;
     212           0 :   }
     213             : 
     214           0 :   const std::string metadata_string_value =
     215           0 :       Envoy::Config::Metadata::metadataValue(metadata_source, metadata_key_).string_value();
     216             : 
     217           0 :   if (!metadata_string_value.empty()) {
     218           0 :     descriptor_entry = {descriptor_key_, metadata_string_value};
     219           0 :     return true;
     220           0 :   } else if (metadata_string_value.empty() && !default_value_.empty()) {
     221           0 :     descriptor_entry = {descriptor_key_, default_value_};
     222           0 :     return true;
     223           0 :   }
     224             : 
     225             :   // If the metadata key is not present and no default value is set, skip this
     226             :   // descriptor if skip_if_absent is true. If skip_if_absent is false, do not
     227             :   // call rate limiting service.
     228           0 :   return skip_if_absent_;
     229           0 : }
     230             : 
     231             : HeaderValueMatchAction::HeaderValueMatchAction(
     232             :     const envoy::config::route::v3::RateLimit::Action::HeaderValueMatch& action)
     233             :     : descriptor_value_(action.descriptor_value()),
     234             :       descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "header_match"),
     235             :       expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)),
     236           0 :       action_headers_(Http::HeaderUtility::buildHeaderDataVector(action.headers())) {}
     237             : 
     238             : bool HeaderValueMatchAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
     239             :                                                 const std::string&,
     240             :                                                 const Http::RequestHeaderMap& headers,
     241           0 :                                                 const StreamInfo::StreamInfo&) const {
     242           0 :   if (expect_match_ == Http::HeaderUtility::matchHeaders(headers, action_headers_)) {
     243           0 :     descriptor_entry = {descriptor_key_, descriptor_value_};
     244           0 :     return true;
     245           0 :   } else {
     246           0 :     return false;
     247           0 :   }
     248           0 : }
     249             : 
     250             : QueryParameterValueMatchAction::QueryParameterValueMatchAction(
     251             :     const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action)
     252             :     : descriptor_value_(action.descriptor_value()),
     253             :       descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "query_match"),
     254             :       expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)),
     255           0 :       action_query_parameters_(buildQueryParameterMatcherVector(action)) {}
     256             : 
     257             : bool QueryParameterValueMatchAction::populateDescriptor(
     258             :     RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
     259           0 :     const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const {
     260           0 :   Http::Utility::QueryParamsMulti query_parameters =
     261           0 :       Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue());
     262           0 :   if (expect_match_ ==
     263           0 :       ConfigUtility::matchQueryParams(query_parameters, action_query_parameters_)) {
     264           0 :     descriptor_entry = {descriptor_key_, descriptor_value_};
     265           0 :     return true;
     266           0 :   } else {
     267           0 :     return false;
     268           0 :   }
     269           0 : }
     270             : 
     271             : std::vector<ConfigUtility::QueryParameterMatcherPtr>
     272             : QueryParameterValueMatchAction::buildQueryParameterMatcherVector(
     273           0 :     const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action) {
     274           0 :   std::vector<ConfigUtility::QueryParameterMatcherPtr> ret;
     275           0 :   for (const auto& query_parameter : action.query_parameters()) {
     276           0 :     ret.push_back(std::make_unique<ConfigUtility::QueryParameterMatcher>(query_parameter));
     277           0 :   }
     278           0 :   return ret;
     279           0 : }
     280             : 
     281             : RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl(
     282             :     const envoy::config::route::v3::RateLimit& config,
     283             :     Server::Configuration::CommonFactoryContext& context)
     284             :     : disable_key_(config.disable_key()),
     285           4 :       stage_(static_cast<uint64_t>(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, stage, 0))) {
     286           4 :   for (const auto& action : config.actions()) {
     287           4 :     switch (action.action_specifier_case()) {
     288           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kSourceCluster:
     289           0 :       actions_.emplace_back(new SourceClusterAction());
     290           0 :       break;
     291           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDestinationCluster:
     292           0 :       actions_.emplace_back(new DestinationClusterAction());
     293           0 :       break;
     294           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRequestHeaders:
     295           0 :       actions_.emplace_back(new RequestHeadersAction(action.request_headers()));
     296           0 :       break;
     297           4 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRemoteAddress:
     298           4 :       actions_.emplace_back(new RemoteAddressAction());
     299           4 :       break;
     300           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kGenericKey:
     301           0 :       actions_.emplace_back(new GenericKeyAction(action.generic_key()));
     302           0 :       break;
     303           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDynamicMetadata:
     304           0 :       actions_.emplace_back(new MetaDataAction(action.dynamic_metadata()));
     305           0 :       break;
     306           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMetadata:
     307           0 :       actions_.emplace_back(new MetaDataAction(action.metadata()));
     308           0 :       break;
     309           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kHeaderValueMatch:
     310           0 :       actions_.emplace_back(new HeaderValueMatchAction(action.header_value_match()));
     311           0 :       break;
     312           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kExtension: {
     313           0 :       ProtobufMessage::ValidationVisitor& validator = context.messageValidationVisitor();
     314           0 :       auto* factory = Envoy::Config::Utility::getFactory<RateLimit::DescriptorProducerFactory>(
     315           0 :           action.extension());
     316           0 :       if (!factory) {
     317             :         // If no descriptor extension is found, fallback to using HTTP matcher
     318             :         // input functions. Note that if the same extension name or type was
     319             :         // dual registered as an extension descriptor and an HTTP matcher input
     320             :         // function, the descriptor extension takes priority.
     321           0 :         RateLimitDescriptorValidationVisitor validation_visitor;
     322           0 :         Matcher::MatchInputFactory<Http::HttpMatchingData> input_factory(validator,
     323           0 :                                                                          validation_visitor);
     324           0 :         Matcher::DataInputFactoryCb<Http::HttpMatchingData> data_input_cb =
     325           0 :             input_factory.createDataInput(action.extension());
     326           0 :         actions_.emplace_back(std::make_unique<MatchInputRateLimitDescriptor>(
     327           0 :             action.extension().name(), data_input_cb()));
     328           0 :         break;
     329           0 :       }
     330           0 :       auto message = Envoy::Config::Utility::translateAnyToFactoryConfig(
     331           0 :           action.extension().typed_config(), validator, *factory);
     332           0 :       RateLimit::DescriptorProducerPtr producer =
     333           0 :           factory->createDescriptorProducerFromProto(*message, context);
     334           0 :       if (producer) {
     335           0 :         actions_.emplace_back(std::move(producer));
     336           0 :       } else {
     337           0 :         throwEnvoyExceptionOrPanic(
     338           0 :             absl::StrCat("Rate limit descriptor extension failed: ", action.extension().name()));
     339           0 :       }
     340           0 :       break;
     341           0 :     }
     342           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMaskedRemoteAddress:
     343           0 :       actions_.emplace_back(new MaskedRemoteAddressAction(action.masked_remote_address()));
     344           0 :       break;
     345           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::
     346           0 :         kQueryParameterValueMatch:
     347           0 :       actions_.emplace_back(
     348           0 :           new QueryParameterValueMatchAction(action.query_parameter_value_match()));
     349           0 :       break;
     350           0 :     case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::ACTION_SPECIFIER_NOT_SET:
     351           0 :       throwEnvoyExceptionOrPanic("invalid config");
     352           4 :     }
     353           4 :   }
     354           4 :   if (config.has_limit()) {
     355           0 :     switch (config.limit().override_specifier_case()) {
     356           0 :     case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::kDynamicMetadata:
     357           0 :       limit_override_.emplace(
     358           0 :           new DynamicMetadataRateLimitOverride(config.limit().dynamic_metadata()));
     359           0 :       break;
     360           0 :     case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::
     361           0 :         OVERRIDE_SPECIFIER_NOT_SET:
     362           0 :       throwEnvoyExceptionOrPanic("invalid config");
     363           0 :     }
     364           0 :   }
     365           4 : }
     366             : 
     367             : void RateLimitPolicyEntryImpl::populateDescriptors(std::vector<RateLimit::Descriptor>& descriptors,
     368             :                                                    const std::string& local_service_cluster,
     369             :                                                    const Http::RequestHeaderMap& headers,
     370           0 :                                                    const StreamInfo::StreamInfo& info) const {
     371           0 :   RateLimit::Descriptor descriptor;
     372           0 :   bool result =
     373           0 :       populateDescriptor(actions_, descriptor.entries_, local_service_cluster, headers, info);
     374             : 
     375           0 :   if (limit_override_) {
     376           0 :     limit_override_.value()->populateOverride(descriptor, &info.dynamicMetadata());
     377           0 :   }
     378             : 
     379           0 :   if (result) {
     380           0 :     descriptors.emplace_back(descriptor);
     381           0 :   }
     382           0 : }
     383             : 
     384             : void RateLimitPolicyEntryImpl::populateLocalDescriptors(
     385             :     std::vector<Envoy::RateLimit::LocalDescriptor>& descriptors,
     386             :     const std::string& local_service_cluster, const Http::RequestHeaderMap& headers,
     387           0 :     const StreamInfo::StreamInfo& info) const {
     388           0 :   RateLimit::LocalDescriptor descriptor({});
     389           0 :   bool result =
     390           0 :       populateDescriptor(actions_, descriptor.entries_, local_service_cluster, headers, info);
     391           0 :   if (result) {
     392           0 :     descriptors.emplace_back(descriptor);
     393           0 :   }
     394           0 : }
     395             : 
     396             : RateLimitPolicyImpl::RateLimitPolicyImpl()
     397           4 :     : rate_limit_entries_reference_(RateLimitPolicyImpl::MAX_STAGE_NUMBER + 1) {}
     398             : 
     399             : RateLimitPolicyImpl::RateLimitPolicyImpl(
     400             :     const Protobuf::RepeatedPtrField<envoy::config::route::v3::RateLimit>& rate_limits,
     401             :     Server::Configuration::CommonFactoryContext& context)
     402           4 :     : RateLimitPolicyImpl() {
     403           4 :   for (const auto& rate_limit : rate_limits) {
     404           4 :     std::unique_ptr<RateLimitPolicyEntry> rate_limit_policy_entry(
     405           4 :         new RateLimitPolicyEntryImpl(rate_limit, context));
     406           4 :     uint64_t stage = rate_limit_policy_entry->stage();
     407           4 :     ASSERT(stage < rate_limit_entries_reference_.size());
     408           4 :     rate_limit_entries_reference_[stage].emplace_back(*rate_limit_policy_entry);
     409           4 :     rate_limit_entries_.emplace_back(std::move(rate_limit_policy_entry));
     410           4 :   }
     411           4 : }
     412             : 
     413             : const std::vector<std::reference_wrapper<const Router::RateLimitPolicyEntry>>&
     414           0 : RateLimitPolicyImpl::getApplicableRateLimit(uint64_t stage) const {
     415           0 :   ASSERT(stage < rate_limit_entries_reference_.size());
     416           0 :   return rate_limit_entries_reference_[stage];
     417           0 : }
     418             : 
     419             : } // namespace Router
     420             : } // namespace Envoy

Generated by: LCOV version 1.15