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

local rate limit: add new rate_limits support to the filter #36099

Merged
merged 25 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0090d86
local rate limit: add new rate_limits api to the filter's api
wbpcode Sep 12, 2024
111aaf7
address format
wbpcode Sep 13, 2024
90da99b
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 14, 2024
68df70c
basic rate limit
wbpcode Sep 18, 2024
e9f5510
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 18, 2024
cddefa7
complete the implementation
wbpcode Sep 18, 2024
0dd7f9b
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 19, 2024
5c01cd5
fix format
wbpcode Sep 21, 2024
26efa09
add change log
wbpcode Sep 21, 2024
085b3aa
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 21, 2024
3638131
fix docs
wbpcode Sep 21, 2024
a573560
fix docs
wbpcode Sep 22, 2024
21a569c
remove new rate limit policy and use the previous route ratelimit
wbpcode Sep 22, 2024
cd7fa92
fix docs label
wbpcode Sep 22, 2024
4a776f1
resolve warn
wbpcode Sep 22, 2024
6677aa4
fix coverage
wbpcode Sep 23, 2024
4ad79d2
address comment and add warning
wbpcode Sep 25, 2024
a3e2f62
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 25, 2024
de1a61d
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Sep 26, 2024
135975e
Update api/envoy/extensions/filters/http/local_ratelimit/v3/local_rat…
wbpcode Oct 2, 2024
3d5429a
address comments
wbpcode Oct 3, 2024
384970f
Merge branch 'dev-local-rate-limit-api' of https://github.com/wbpcode…
wbpcode Oct 3, 2024
7cf2a8b
Merge branch 'main' of https://github.com/envoyproxy/envoy into dev-l…
wbpcode Oct 3, 2024
3b8d8b7
Update source/extensions/filters/http/local_ratelimit/local_ratelimit.cc
wbpcode Oct 5, 2024
e9893ed
fix format
wbpcode Oct 5, 2024
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/envoy/extensions/filters/http/local_ratelimit/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2
api_proto_package(
deps = [
"//envoy/config/core/v3:pkg",
"//envoy/config/route/v3:pkg",
"//envoy/extensions/common/ratelimit/v3:pkg",
"//envoy/type/v3:pkg",
"@com_github_cncf_xds//udpa/annotations:pkg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.filters.http.local_ratelimit.v3;

import "envoy/config/core/v3/base.proto";
import "envoy/config/route/v3/route_components.proto";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make more sense to move the RateLimit message to a different file (out of route_components.proto).
Unfortunately I think it must stay in "envoy/config/route/v3/" to avoid a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it make sense. But not just for RateLimit. There are lots of common base message here that shared by other components because historical reason. I will prefer to do that in an independent PR.

import "envoy/extensions/common/ratelimit/v3/ratelimit.proto";
import "envoy/type/v3/http_status.proto";
import "envoy/type/v3/token_bucket.proto";
Expand All @@ -22,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// Local Rate limit :ref:`configuration overview <config_http_filters_local_rate_limit>`.
// [#extension: envoy.filters.http.local_ratelimit]

// [#next-free-field: 17]
// [#next-free-field: 18]
message LocalRateLimit {
// The human readable prefix to use when emitting stats.
string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];
Expand Down Expand Up @@ -147,4 +148,23 @@ message LocalRateLimit {
// of the default ``UNAVAILABLE`` gRPC code for a rate limited gRPC call. The
// HTTP code will be 200 for a gRPC response.
bool rate_limited_as_resource_exhausted = 15;

// Rate limit configuration that is used to generate a list of descriptor entries based on
// the request context. The generated entries will be used to find one or multiple matched rate
// limit rule from the ``descriptors``.
// If this is set, then
// :ref:`VirtualHost.rate_limits<envoy_v3_api_field_config.route.v3.VirtualHost.rate_limits>` or
// :ref:`RouteAction.rate_limits<envoy_v3_api_field_config.route.v3.RouteAction.rate_limits>` fields
Comment on lines +156 to +157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be mentioned in those fields as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get it correctly? Did you mean we should add some comments to the rate_limits fields?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the fields documents again, the legacy rate_limits in the route may be shared by multiple different rate limit filters. Additional comments may bring confusions to the users.

For users who doesn't use this new rate_limits here, nothing is changed.
For users who use this new rate_limits here, they will get the new behavior clearly according to the comments here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it will be possible to add a warning when there are fields that are ignored.
I know it doesn't add much, but as it stands, an operator that attempts to debug why some route isn't working as they are expecting, needs to go through different levels of route settings, and looking at different places in the config.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it will be possible to add a warning when there are fields that are ignored.

Sorry, could you make this a little more clear? should I add warning in the runtime log or the comments?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, could you make this a little more clear? should I add warning in the runtime log or the comments?

I meant the log. The example I'm thinking of is that if the config contains both the new rate-limit filter config and VirtualHost.rate_limits (for example), then Envoy will just warn that the VirtualHost.rate_limits is ignored in favor of the filter config.

// will be ignored.
//
// .. note::
// Not all configuration fields of
// :ref:`rate limit config <envoy_v3_api_msg_config.route.v3.RateLimit>` is supported at here.
// Following fields are not supported:
//
// 1. :ref:`rate limit stage <envoy_v3_api_field_config.route.v3.RateLimit.stage>`.
// 2. :ref:`dynamic metadata <envoy_v3_api_field_config.route.v3.RateLimit.Action.dynamic_metadata>`.
// 3. :ref:`disable_key <envoy_v3_api_field_config.route.v3.RateLimit.disable_key>`.
// 4. :ref:`override limit <envoy_v3_api_field_config.route.v3.RateLimit.limit>`.
Comment on lines +165 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess some of them are not supported because they are not implemented yet, and not because there's some design blocker.
Can you separate the 2 categories, and make that explicit? This will allow future upgrades (PRs) that enable these features.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I think all these will be ignored forever. This is why l initially copied whole RateLimit to a new message and removed them in the new message.

Because historical reason, at the initial design, the ratelimit configuration is split to two parts: filter configuration and rate_limits in the route. We we may have multiple ratelimit filters in the filter chain, but there is only one rate_limits field in single route.
To ensure multiple limit filter could share same rate_limits correctly, the stage is introduced. Different filters may have different stage. Only the matched policies in the policy list will be applied.

The stage is unnecessary because the new rate_limits is part of filter configuration. We needn't the stage match now.

The Limit is only used by the remote ratelimit filter, it make no sense for local rate limit.

The disable_key allow us to disable single policy. But the local ratelimit has provided filter level runtime disable key. I have no plan to support this more fine-grained control because it is hard to say it actually make sense.
IMO, we abused the runtime switch. It is pretty good to guard the behavior changes.

But I think it should not be used as general switch of feature or configuration. XDS is good enough for most cases to update configuration dynamically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to reject/warn when there's config that has those fields

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to reject/warn when there's config that has those fields

I will add a warn log at the implementation.

repeated config.route.v3.RateLimit rate_limits = 17;
}
8 changes: 8 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,14 @@ new_features:
change: |
Added two new methods ``oidsPeerCertificate()`` and ``oidsLocalCertificate()`` to SSL
connection object API :ref:`SSL connection info object <config_http_filters_lua_ssl_socket_info>`.
- area: local_ratelimit
change: |
Add the :ref:`rate_limits
<envoy_v3_api_field_extensions.filters.http.local_ratelimit.v3.LocalRateLimit.rate_limits>`
field to generate rate limit descriptors. If this field is set, the
:ref:`VirtualHost.rate_limits<envoy_v3_api_field_config.route.v3.VirtualHost.rate_limits>` or
:ref:`RouteAction.rate_limits<envoy_v3_api_field_config.route.v3.RouteAction.rate_limits>` fields
will be ignored.
- area: basic_auth
change: |
Added support to provide an override
Expand Down
62 changes: 21 additions & 41 deletions source/common/router/router_ratelimit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#include "source/common/common/empty_string.h"
#include "source/common/config/metadata.h"
#include "source/common/config/utility.h"
#include "source/common/http/matching/data_impl.h"
#include "source/common/matcher/matcher.h"
#include "source/common/protobuf/utility.h"

namespace Envoy {
Expand All @@ -39,44 +37,24 @@ bool populateDescriptor(const std::vector<RateLimit::DescriptorProducerPtr>& act
return result;
}

class RateLimitDescriptorValidationVisitor
: public Matcher::MatchTreeValidationVisitor<Http::HttpMatchingData> {
public:
absl::Status performDataInputValidation(const Matcher::DataInputFactory<Http::HttpMatchingData>&,
absl::string_view) override {
return absl::OkStatus();
} // namespace

// Ratelimit::DescriptorProducer
bool MatchInputRateLimitDescriptor::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
const std::string&,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const {
Http::Matching::HttpMatchingDataImpl data(info);
data.onRequestHeaders(headers);
auto result = data_input_->get(data);
if (!absl::holds_alternative<std::string>(result.data_)) {
return false;
}
};

class MatchInputRateLimitDescriptor : public RateLimit::DescriptorProducer {
public:
MatchInputRateLimitDescriptor(const std::string& descriptor_key,
Matcher::DataInputPtr<Http::HttpMatchingData>&& data_input)
: descriptor_key_(descriptor_key), data_input_(std::move(data_input)) {}

// Ratelimit::DescriptorProducer
bool populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const override {
Http::Matching::HttpMatchingDataImpl data(info);
data.onRequestHeaders(headers);
auto result = data_input_->get(data);
if (absl::holds_alternative<absl::monostate>(result.data_)) {
return false;
}
const std::string& str = absl::get<std::string>(result.data_);
if (!str.empty()) {
descriptor_entry = {descriptor_key_, str};
}
return true;
if (absl::string_view str = absl::get<std::string>(result.data_); !str.empty()) {
descriptor_entry = {descriptor_key_, std::string(str)};
}

private:
const std::string descriptor_key_;
Matcher::DataInputPtr<Http::HttpMatchingData> data_input_;
};

} // namespace
return true;
}

const uint64_t RateLimitPolicyImpl::MAX_STAGE_NUMBER = 10UL;

Expand Down Expand Up @@ -256,7 +234,8 @@ QueryParameterValueMatchAction::QueryParameterValueMatchAction(
: descriptor_value_(action.descriptor_value()),
descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "query_match"),
expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)),
action_query_parameters_(buildQueryParameterMatcherVector(action, context)) {}
action_query_parameters_(
buildQueryParameterMatcherVector(action.query_parameters(), context)) {}

bool QueryParameterValueMatchAction::populateDescriptor(
RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
Expand All @@ -274,10 +253,11 @@ bool QueryParameterValueMatchAction::populateDescriptor(

std::vector<ConfigUtility::QueryParameterMatcherPtr>
QueryParameterValueMatchAction::buildQueryParameterMatcherVector(
const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action,
const Protobuf::RepeatedPtrField<envoy::config::route::v3::QueryParameterMatcher>&
query_parameters,
Server::Configuration::CommonFactoryContext& context) {
std::vector<ConfigUtility::QueryParameterMatcherPtr> ret;
for (const auto& query_parameter : action.query_parameters()) {
for (const auto& query_parameter : query_parameters) {
ret.push_back(std::make_unique<ConfigUtility::QueryParameterMatcher>(query_parameter, context));
}
return ret;
Expand Down
31 changes: 30 additions & 1 deletion source/common/router/router_ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

#include "source/common/config/metadata.h"
#include "source/common/http/header_utility.h"
#include "source/common/http/matching/data_impl.h"
#include "source/common/matcher/matcher.h"
#include "source/common/network/cidr_range.h"
#include "source/common/protobuf/utility.h"
#include "source/common/router/config_utility.h"
Expand Down Expand Up @@ -146,6 +148,7 @@ class MetaDataAction : public RateLimit::DescriptorProducer {
MetaDataAction(const envoy::config::route::v3::RateLimit::Action::MetaData& action);
// for maintaining backward compatibility with the deprecated DynamicMetaData action
MetaDataAction(const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action);

// Ratelimit::DescriptorProducer
bool populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
const std::string& local_service_cluster,
Expand Down Expand Up @@ -198,7 +201,8 @@ class QueryParameterValueMatchAction : public RateLimit::DescriptorProducer {
const StreamInfo::StreamInfo& info) const override;

std::vector<ConfigUtility::QueryParameterMatcherPtr> buildQueryParameterMatcherVector(
const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action,
const Protobuf::RepeatedPtrField<envoy::config::route::v3::QueryParameterMatcher>&
query_parameters,
Server::Configuration::CommonFactoryContext& context);

private:
Expand All @@ -208,6 +212,31 @@ class QueryParameterValueMatchAction : public RateLimit::DescriptorProducer {
const std::vector<ConfigUtility::QueryParameterMatcherPtr> action_query_parameters_;
};

class RateLimitDescriptorValidationVisitor
: public Matcher::MatchTreeValidationVisitor<Http::HttpMatchingData> {
public:
absl::Status performDataInputValidation(const Matcher::DataInputFactory<Http::HttpMatchingData>&,
absl::string_view) override {
return absl::OkStatus();
}
};

class MatchInputRateLimitDescriptor : public RateLimit::DescriptorProducer {
public:
MatchInputRateLimitDescriptor(const std::string& descriptor_key,
Matcher::DataInputPtr<Http::HttpMatchingData>&& data_input)
: descriptor_key_(descriptor_key), data_input_(std::move(data_input)) {}

// Ratelimit::DescriptorProducer
bool populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& info) const override;

private:
const std::string descriptor_key_;
Matcher::DataInputPtr<Http::HttpMatchingData> data_input_;
};

/*
* Implementation of RateLimitPolicyEntry that holds the action for the configuration.
*/
Expand Down
22 changes: 22 additions & 0 deletions source/extensions/filters/common/ratelimit_config/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "ratelimit_config_lib",
srcs = ["ratelimit_config.cc"],
hdrs = ["ratelimit_config.h"],
deps = [
"//envoy/ratelimit:ratelimit_interface",
"//source/common/router:router_ratelimit_lib",
"@com_google_absl//absl/container:inlined_vector",
"@com_google_absl//absl/strings",
"@envoy_api//envoy/config/route/v3:pkg_cc_proto",
],
)
143 changes: 143 additions & 0 deletions source/extensions/filters/common/ratelimit_config/ratelimit_config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "source/extensions/filters/common/ratelimit_config/ratelimit_config.h"

#include "source/common/config/utility.h"
#include "source/common/http/matching/data_impl.h"
#include "source/common/matcher/matcher.h"

namespace Envoy {
namespace Extensions {
namespace Filters {
namespace Common {
namespace RateLimit {

RateLimitPolicy::RateLimitPolicy(const ProtoRateLimit& config,
Server::Configuration::CommonFactoryContext& context,
absl::Status& creation_status, bool no_limit) {
if (config.has_stage() || !config.disable_key().empty()) {
creation_status =
absl::InvalidArgumentError("'stage' field and 'disable_key' field are not supported");
return;
}

if (config.has_limit()) {
if (no_limit) {
creation_status = absl::InvalidArgumentError("'limit' field is not supported");
return;
}
}

for (const ProtoRateLimit::Action& action : config.actions()) {
switch (action.action_specifier_case()) {
case ProtoRateLimit::Action::ActionSpecifierCase::kSourceCluster:
actions_.emplace_back(new Envoy::Router::SourceClusterAction());
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kDestinationCluster:
actions_.emplace_back(new Envoy::Router::DestinationClusterAction());
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kRequestHeaders:
actions_.emplace_back(new Envoy::Router::RequestHeadersAction(action.request_headers()));
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kRemoteAddress:
actions_.emplace_back(new Envoy::Router::RemoteAddressAction());
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kGenericKey:
actions_.emplace_back(new Envoy::Router::GenericKeyAction(action.generic_key()));
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kMetadata:
actions_.emplace_back(new Envoy::Router::MetaDataAction(action.metadata()));
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kHeaderValueMatch:
actions_.emplace_back(
new Envoy::Router::HeaderValueMatchAction(action.header_value_match(), context));
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kExtension: {
ProtobufMessage::ValidationVisitor& validator = context.messageValidationVisitor();
auto* factory =
Envoy::Config::Utility::getFactory<Envoy::RateLimit::DescriptorProducerFactory>(
action.extension());
if (!factory) {
// If no descriptor extension is found, fallback to using HTTP matcher
// input functions. Note that if the same extension name or type was
// dual registered as an extension descriptor and an HTTP matcher input
// function, the descriptor extension takes priority.
Router::RateLimitDescriptorValidationVisitor validation_visitor;
Matcher::MatchInputFactory<Http::HttpMatchingData> input_factory(validator,
validation_visitor);
Matcher::DataInputFactoryCb<Http::HttpMatchingData> data_input_cb =
input_factory.createDataInput(action.extension());
actions_.emplace_back(std::make_unique<Router::MatchInputRateLimitDescriptor>(
action.extension().name(), data_input_cb()));
break;
}
auto message = Envoy::Config::Utility::translateAnyToFactoryConfig(
action.extension().typed_config(), validator, *factory);
Envoy::RateLimit::DescriptorProducerPtr producer =
factory->createDescriptorProducerFromProto(*message, context);
if (producer) {
actions_.emplace_back(std::move(producer));
} else {
creation_status = absl::InvalidArgumentError(
absl::StrCat("Rate limit descriptor extension failed: ", action.extension().name()));
return;
}
break;
}
case ProtoRateLimit::Action::ActionSpecifierCase::kMaskedRemoteAddress:
actions_.emplace_back(new Router::MaskedRemoteAddressAction(action.masked_remote_address()));
break;
case ProtoRateLimit::Action::ActionSpecifierCase::kQueryParameterValueMatch:
actions_.emplace_back(new Router::QueryParameterValueMatchAction(
action.query_parameter_value_match(), context));
break;
default:
creation_status = absl::InvalidArgumentError(fmt::format(
"Unsupported rate limit action: {}", static_cast<int>(action.action_specifier_case())));
return;
}
}
}

void RateLimitPolicy::populateDescriptors(const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& stream_info,
const std::string& local_service_cluster,
RateLimitDescriptors& descriptors) const {
Envoy::RateLimit::LocalDescriptor descriptor;
for (const Envoy::RateLimit::DescriptorProducerPtr& action : actions_) {
Envoy::RateLimit::DescriptorEntry entry;
if (!action->populateDescriptor(entry, local_service_cluster, headers, stream_info)) {
return;
}
if (!entry.key_.empty()) {
descriptor.entries_.emplace_back(std::move(entry));
}
}
descriptors.emplace_back(std::move(descriptor));
}

RateLimitConfig::RateLimitConfig(const Protobuf::RepeatedPtrField<ProtoRateLimit>& configs,
Server::Configuration::CommonFactoryContext& context,
absl::Status& creation_status, bool no_limit) {
for (const ProtoRateLimit& config : configs) {
auto descriptor_generator =
std::make_unique<RateLimitPolicy>(config, context, creation_status, no_limit);
if (!creation_status.ok()) {
return;
}
rate_limit_policies_.emplace_back(std::move(descriptor_generator));
}
}

void RateLimitConfig::populateDescriptors(const Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& stream_info,
const std::string& local_service_cluster,
RateLimitDescriptors& descriptors) const {
for (const auto& generator : rate_limit_policies_) {
generator->populateDescriptors(headers, stream_info, local_service_cluster, descriptors);
}
}

} // namespace RateLimit
} // namespace Common
} // namespace Filters
} // namespace Extensions
} // namespace Envoy
Loading