Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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/envoy/config/route/v3/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,7 @@ message VirtualCluster {
}

// Global rate limiting :ref:`architecture overview <arch_overview_global_rate_limit>`.
// Also applies to Local rate limiting :ref:`using descriptors <config_http_filters_local_rate_limit_descriptors>`.
message RateLimit {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit";

Expand Down
1 change: 1 addition & 0 deletions api/envoy/config/route/v4alpha/route_components.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/envoy/extensions/common/ratelimit/v3/ratelimit.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.common.ratelimit.v3;

import "envoy/type/v3/ratelimit_unit.proto";
import "envoy/type/v3/token_bucket.proto";

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
Expand Down Expand Up @@ -92,3 +93,11 @@ message RateLimitDescriptor {
// Optional rate limit override to supply to the ratelimit service.
RateLimitOverride limit = 2;
}

message LocalRateLimitDescriptor {
// Descriptor entries.
repeated v3.RateLimitDescriptor.Entry entries = 1 [(validate.rules).repeated = {min_items: 1}];

// Token Bucket algorithm for local ratelimiting.
type.v3.TokenBucket token_bucket = 2 [(validate.rules).message = {required: true}];
}
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/extensions/common/ratelimit/v3:pkg",
"//envoy/type/v3:pkg",
"@com_github_cncf_udpa//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/extensions/common/ratelimit/v3/ratelimit.proto";
import "envoy/type/v3/http_status.proto";
import "envoy/type/v3/token_bucket.proto";

Expand All @@ -19,7 +20,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: 7]
// [#next-free-field: 10]
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 @@ -67,4 +68,23 @@ message LocalRateLimit {
// have been rate limited.
repeated config.core.v3.HeaderValueOption response_headers_to_add = 6
[(validate.rules).repeated = {max_items: 10}];

// The rate limit descriptor list to use in the local rate limit to override
// on.
// Example on how to use ::ref:`this <config_http_filters_local_rate_limit_descriptors>`
//
// .. note::
//
// In the current implementation the descriptor's token bucket :ref:`fill_interval
// <envoy_api_field_type.v3.TokenBucket.fill_interval>` should be a multiple
// global :ref:`token bucket's<envoy_api_field_extensions.filters.http.local_ratelimit.v3.LocalRateLimit.token_bucket>` fill interval.
repeated common.ratelimit.v3.LocalRateLimitDescriptor descriptors = 8;

// Specifies the rate limit configurations to be applied with the same
// stage number. If not set, the default stage number is 0.
//
// .. note::
//
// The filter supports a range of 0 - 10 inclusively for stage numbers.
uint32 stage = 9 [(validate.rules).uint32 = {lte: 10}];
}
1 change: 1 addition & 0 deletions docs/root/api-v3/config/common/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Common
matcher/v3/*
../../extensions/common/dynamic_forward_proxy/v3/*
../../extensions/common/tap/v3/*
../../extensions/common/ratelimit/v3/*
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,89 @@ The route specific configuration:
Note that if this filter is configured as globally disabled and there are no virtual host or route level
token buckets, no rate limiting will be applied.

.. _config_http_filters_local_rate_limit_descriptors:

Using Descriptors to rate limit on
----------------------------------

Descriptors can be used to override local rate limiting based on presence of certain descriptors/route actions.
A route's :ref:`rate limit action <envoy_v3_api_msg_config.route.v3.RateLimit>` is used to match up a
:ref:`local descriptor <envoy_v3_api_msg_extensions.common.ratelimit.v3.LocalRateLimitDescriptor>` in the filter config descriptor list.
The local descriptor's token bucket config is used to decide if the request should be
rate limited or not, if the local descriptor's entries match the route's rate limit actions descriptor entries.
Otherwise the default token bucket config is used.

Example filter configuration using descriptors is as follows:

.. validated-code-block:: yaml
:type-name: envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager

route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/foo" }
route: { cluster: service_protected_by_rate_limit }
typed_per_filter_config:
envoy.filters.http.local_ratelimit:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: test
token_bucket:
max_tokens: 1000
tokens_per_fill: 1000
fill_interval: 60s
filter_enabled:
runtime_key: test_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: test_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-test-rate-limit
value: 'true'
descriptors:
- entries:
- key: client_id
value: foo
- key: path
value: /foo/bar
token_bucket:
max_tokens: 10
tokens_per_fill: 10
fill_interval: 60s
- entries:
- key: client_id
value: foo
- key: path
value: /foo/bar2
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60s
- match: { prefix: "/" }
route: { cluster: default_service }
rate_limits:
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Maybe add a comment to route_components.proto where this is defined that rate limits also apply to local rate limit, not just global?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure..updating

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried doing this in v3/route_components.proto but fix_format kept complaining problems about frozen proto in other protos like rbac proto...

Copy link
Member

Choose a reason for hiding this comment

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

You should be able to update docs in other places. Please try again and figure out what the issue is assuming you are syncing the protos correctly.

- actions: # any actions in here
- request_headers:
header_name: ":path"
descriptor_key: "path"
- generic_key:
descriptor_value: "foo"
descriptor_key: "client_id"

For this config, requests are ratelimited for routes prefixed with "/foo"
In that, if requests come from client_id "foo" for "/foo/bar" path, then 10 req/min are allowed.
But if they come from client_id "foo" for "/foo/bar2" path, then 100 req/min are allowed.
Otherwise 1000 req/min are allowed.

Statistics
----------

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions include/envoy/ratelimit/ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "envoy/type/v3/ratelimit_unit.pb.h"

#include "absl/time/time.h"
#include "absl/types/optional.h"

namespace Envoy {
Expand All @@ -24,6 +25,10 @@ struct RateLimitOverride {
struct DescriptorEntry {
std::string key_;
std::string value_;

friend bool operator==(const DescriptorEntry& lhs, const DescriptorEntry& rhs) {
return lhs.key_ == rhs.key_ && lhs.value_ == rhs.value_;
}
};

/**
Expand All @@ -34,5 +39,48 @@ struct Descriptor {
absl::optional<RateLimitOverride> limit_ = absl::nullopt;
};

/**
* A single token bucket. See token_bucket.proto.
*/
struct TokenBucket {
uint32_t max_tokens_;
uint32_t tokens_per_fill_;
absl::Duration fill_interval_;

friend bool operator==(const TokenBucket& lhs, const TokenBucket& rhs) {
return lhs.max_tokens_ == rhs.max_tokens_ && lhs.tokens_per_fill_ == rhs.tokens_per_fill_ &&
lhs.fill_interval_ == rhs.fill_interval_;
}

// Support absl::Hash.
template <typename H>
friend H AbslHashValue(H h, const TokenBucket& d) { // NOLINT(readability-identifier-naming)
h = H::combine(std::move(h), d.max_tokens_, d.tokens_per_fill_, d.fill_interval_);
return h;
}
};

/**
* A single rate limit request descriptor. See ratelimit.proto.
*/
struct LocalDescriptor {
std::vector<DescriptorEntry> entries_;
TokenBucket token_bucket_;

friend bool operator==(const LocalDescriptor& lhs, const LocalDescriptor& rhs) {
return lhs.entries_ == rhs.entries_ && lhs.token_bucket_ == rhs.token_bucket_;
}

// Support absl::Hash.
template <typename H>
friend H AbslHashValue(H h, const LocalDescriptor& d) { // NOLINT(readability-identifier-naming)
for (const auto& entry : d.entries_) {
h = H::combine(std::move(h), entry.key_, entry.value_);
}
h = H::combine(std::move(h), d.token_bucket_);
return h;
}
};

} // namespace RateLimit
} // namespace Envoy
20 changes: 18 additions & 2 deletions include/envoy/router/router_ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class RateLimitAction {
virtual ~RateLimitAction() = default;

/**
* Potentially append a descriptor entry to the end of descriptor.
* Potentially fill a descriptor entry to the end of descriptor.
* @param route supplies the target route for the request.
* @param descriptor supplies the descriptor to optionally fill.
* @param local_service_cluster supplies the name of the local service cluster.
Expand All @@ -50,7 +50,7 @@ class RateLimitAction {
* @return true if the RateLimitAction populated the descriptor.
*/
virtual bool
populateDescriptor(const RouteEntry& route, RateLimit::Descriptor& descriptor,
populateDescriptor(const RouteEntry& route, RateLimit::DescriptorEntry& descriptor,
const std::string& local_service_cluster, const Http::HeaderMap& headers,
const Network::Address::Instance& remote_address,
const envoy::config::core::v3::Metadata* dynamic_metadata) const PURE;
Expand Down Expand Up @@ -89,6 +89,22 @@ class RateLimitPolicyEntry {
const std::string& local_service_cluster, const Http::HeaderMap& headers,
const Network::Address::Instance& remote_address,
const envoy::config::core::v3::Metadata* dynamic_metadata) const PURE;

/**
* Potentially populate the local descriptor array with new local descriptors to query.
* @param route supplies the target route for the request.
* @param descriptors supplies the descriptor array to optionally fill.
* @param local_service_cluster supplies the name of the local service cluster.
* @param headers supplies the header for the request.
* @param remote_address supplies the trusted downstream address for the connection.
* @param dynamic_metadata supplies the dynamic metadata for the request.
*/
virtual void
populateLocalDescriptors(const RouteEntry& route,
std::vector<RateLimit::LocalDescriptor>& descriptors,
const std::string& local_service_cluster, const Http::HeaderMap& headers,
const Network::Address::Instance& remote_address,
const envoy::config::core::v3::Metadata* dynamic_metadata) const PURE;
};

/**
Expand Down
Loading