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

[19685] network rate limit: allow limiting by client IP #19875

Merged
merged 41 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
43ef5f4
initial proof of concept
llu94 Feb 8, 2022
d94ceaa
formatter changes
llu94 Feb 8, 2022
56940ba
api changes
llu94 Feb 15, 2022
8cdf1f8
new proto definitions
llu94 Feb 16, 2022
5765b72
reverse API changes
llu94 Feb 17, 2022
d0aad28
Rework based on PR feedback using substitution formatter
llu94 Feb 21, 2022
4d337d0
dynamic downstream IP implementation and documents
llu94 Feb 23, 2022
c7831fd
fix formatting
llu94 Feb 27, 2022
7d42c9e
windows release error fix
llu94 Mar 6, 2022
f54983e
apply format fix
llu94 Mar 6, 2022
c0acd99
switch to bool field in proto to toggle
llu94 Mar 14, 2022
ccf76d6
implementation switched to substitution formatting
llu94 Mar 15, 2022
83bac11
apply format fixes
llu94 Mar 15, 2022
7f0b91f
change to fully enabled substitution formatter
llu94 Mar 18, 2022
444c919
code cleanup and added docs
llu94 Mar 19, 2022
4b950f6
removed unnecessary imports and added release note
llu94 Mar 19, 2022
3356c52
Fix doc formatting
llu94 Mar 19, 2022
798b005
fix release note formatting issue
llu94 Mar 19, 2022
1bf8ce9
release note in alphabetical order
llu94 Mar 19, 2022
6ee1257
formatting response to PR feedback
llu94 Mar 26, 2022
d0071d0
fix integration test issues
llu94 Mar 27, 2022
7ee3e84
resolve asan test error
llu94 Mar 29, 2022
49b902b
respond to PR feedback on docs and filter code
llu94 Mar 30, 2022
87f2862
remove unnecessary comment
llu94 Mar 30, 2022
72c4544
Formatter objects created at config
llu94 Apr 2, 2022
cafd9ff
fix clang tidy failure
llu94 Apr 2, 2022
8c12b22
fix format
llu94 Apr 2, 2022
38b9c3e
fix docs
llu94 Apr 3, 2022
71e4e7c
fix docs
llu94 Apr 3, 2022
6308451
fix test failures
llu94 Apr 4, 2022
9f396d9
fix runtime_features
llu94 Apr 4, 2022
8948a85
fix merge conflict and remove unused code
llu94 Apr 6, 2022
c75048b
switch to unique_ptr
llu94 Apr 7, 2022
a32b8a5
Fix test code in line with PR feedback
llu94 Apr 9, 2022
0a190f3
revert rst_check.py changes
llu94 Apr 9, 2022
6957bc8
fix rst_check.py tool
llu94 Apr 9, 2022
f7a454e
remove unneccesary imports and white space
llu94 Apr 11, 2022
eb2eb11
Merge branch 'main' into rate_limit_fix
llu94 Apr 11, 2022
5e90345
fix alphabetical order
llu94 Apr 11, 2022
613d1fb
PR feed back, formatting and removal of extraneous test code
llu94 Apr 13, 2022
7ed59d9
add comments about why http headers are needed in network filter
llu94 Apr 14, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,27 @@ The ratelimit filter emits dynamic metadata as an opaque ``google.protobuf.Struc
*only* when the gRPC ratelimit service returns a :ref:`CheckResponse
<envoy_v3_api_msg_service.ratelimit.v3.RateLimitResponse>` with a filled :ref:`dynamic_metadata
<envoy_v3_api_field_service.ratelimit.v3.RateLimitResponse.dynamic_metadata>` field.

Substitution Formatting
-----------------------

.. _config_network_filters_ratelimit_substitution_formatter:

The network rate limit filter also supports substitution formatting based on stream info populated at request time for its descriptors.
The value field for :ref:`rate_limit_descriptor <envoy_v3_api_field_extensions.filters.network.ratelimit.v3.RateLimit.descriptors>`
accepts runtime substitution.
The format for the substitution formatting can be found in the :ref:`access logging <config_access_log>` documentation

Example usage:

.. code-block:: yaml

name: envoy.filters.network.ratelimit
domain: foo
descriptors:
- entries:
- key: remote_address
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
- key: foo
value: bar
stat_prefix: name
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ New Features
* matching: the matching API can now express a match tree that will always match by omitting a matcher at the top level.
* outlier_detection: :ref:`max_ejection_time_jitter<envoy_v3_api_field_config.cluster.v3.OutlierDetection.base_ejection_time>` configuration added to allow adding a random value to the ejection time to prevent 'thundering herd' scenarios. Defaults to 0 so as to not break or change the behavior of existing deployments.
* ratelimit: added :ref:`rate_limited_status <envoy_v3_api_field_extensions.filters.http.ratelimit.v3.RateLimit.rate_limited_status>` to support return a custom HTTP response status code to the downstream client when the request has been rate limited.
* ratelimit: network rate limiter supports runtime value substitution using stream info and substitution formatting via :ref:` Network Rate Limiter <config_network_filters_ratelimit_substitution_formatter>`.
* redis: support for hostnames returned in ``cluster_slots`` response is now available.
* router: added a path-separated prefix matcher, to make route creation more efficient. :ref:`path_separated_prefix <envoy_v3_api_field_config.route.v3.RouteMatch.path_separated_prefix>`.
* schema_validator_tool: added ``bootstrap`` checking to the
Expand Down
41 changes: 37 additions & 4 deletions source/extensions/filters/network/ratelimit/ratelimit.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#include "source/extensions/filters/network/ratelimit/ratelimit.h"

#include <cstdint>
#include <iterator>
#include <string>
#include <vector>

#include "envoy/extensions/filters/network/ratelimit/v3/rate_limit.pb.h"
#include "envoy/stats/scope.h"

#include "source/common/common/fmt.h"
#include "source/common/formatter/substitution_formatter.h"
#include "source/common/tracing/http_tracer_impl.h"
#include "source/extensions/filters/network/well_known_names.h"

Expand All @@ -18,16 +21,44 @@ namespace RateLimitFilter {
Config::Config(const envoy::extensions::filters::network::ratelimit::v3::RateLimit& config,
Stats::Scope& scope, Runtime::Loader& runtime)
: domain_(config.domain()), stats_(generateStats(config.stat_prefix(), scope)),
runtime_(runtime), failure_mode_deny_(config.failure_mode_deny()) {
runtime_(runtime), failure_mode_deny_(config.failure_mode_deny()),
request_headers_(Http::RequestHeaderMapImpl::create()),
response_headers_(Http::ResponseHeaderMapImpl::create()),
response_trailers_(Http::ResponseTrailerMapImpl::create()) {
for (const auto& descriptor : config.descriptors()) {
RateLimit::Descriptor new_descriptor;
for (const auto& entry : descriptor.entries()) {
new_descriptor.entries_.push_back({entry.key(), entry.value()});
substitution_formatters_.push_back(
std::make_unique<Formatter::FormatterImpl>(entry.value(), false));
}
descriptors_.push_back(new_descriptor);
original_descriptors_.push_back(new_descriptor);
}
}

std::vector<RateLimit::Descriptor>
Config::applySubstitutionFormatter(StreamInfo::StreamInfo& stream_info) {

std::vector<RateLimit::Descriptor> dynamic_descriptors;
dynamic_descriptors.reserve(descriptors().size());
std::vector<std::unique_ptr<Formatter::FormatterImpl>>::iterator formatter_it =
substitution_formatters_.begin();
for (const RateLimit::Descriptor& descriptor : descriptors()) {
RateLimit::Descriptor new_descriptor;
llu94 marked this conversation as resolved.
Show resolved Hide resolved
new_descriptor.entries_.reserve(descriptor.entries_.size());
for (const RateLimit::DescriptorEntry& descriptor_entry : descriptor.entries_) {

std::string value = descriptor_entry.value_;
value = formatter_it->get()->format(*request_headers_.get(), *response_headers_.get(),
*response_trailers_.get(), stream_info, value);
formatter_it++;
new_descriptor.entries_.push_back({descriptor_entry.key_, value});
}
dynamic_descriptors.push_back(new_descriptor);
}
return dynamic_descriptors;
}

InstanceStats Config::generateStats(const std::string& name, Stats::Scope& scope) {
std::string final_prefix = fmt::format("ratelimit.{}.", name);
return {ALL_TCP_RATE_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix),
Expand All @@ -50,8 +81,10 @@ Network::FilterStatus Filter::onNewConnection() {
config_->stats().active_.inc();
config_->stats().total_.inc();
calling_limit_ = true;
client_->limit(*this, config_->domain(), config_->descriptors(), Tracing::NullSpan::instance(),
filter_callbacks_->connection().streamInfo());
client_->limit(
*this, config_->domain(),
config_->applySubstitutionFormatter(filter_callbacks_->connection().streamInfo()),
Tracing::NullSpan::instance(), filter_callbacks_->connection().streamInfo());
calling_limit_ = false;
}

Expand Down
16 changes: 14 additions & 2 deletions source/extensions/filters/network/ratelimit/ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include "envoy/stats/scope.h"
#include "envoy/stats/stats_macros.h"

#include "source/common/formatter/substitution_formatter.h"
#include "source/common/http/header_map_impl.h"
#include "source/common/http/header_utility.h"
#include "source/extensions/filters/common/ratelimit/ratelimit.h"

namespace Envoy {
Expand Down Expand Up @@ -47,19 +50,28 @@ class Config {
Config(const envoy::extensions::filters::network::ratelimit::v3::RateLimit& config,
Stats::Scope& scope, Runtime::Loader& runtime);
const std::string& domain() { return domain_; }
const std::vector<RateLimit::Descriptor>& descriptors() { return descriptors_; }
const std::vector<RateLimit::Descriptor>& descriptors() { return original_descriptors_; }
Runtime::Loader& runtime() { return runtime_; }
const InstanceStats& stats() { return stats_; }
bool failureModeAllow() const { return !failure_mode_deny_; };
std::vector<RateLimit::Descriptor>
applySubstitutionFormatter(StreamInfo::StreamInfo& stream_info);

private:
static InstanceStats generateStats(const std::string& name, Stats::Scope& scope);

llu94 marked this conversation as resolved.
Show resolved Hide resolved
std::string domain_;
std::vector<RateLimit::Descriptor> descriptors_;
std::vector<RateLimit::Descriptor> original_descriptors_;
std::vector<std::unique_ptr<Formatter::FormatterImpl>> substitution_formatters_;
const InstanceStats stats_;
Runtime::Loader& runtime_;
const bool failure_mode_deny_;
// These are needed because Envoy doesn't have substitution formatters independent of HTTP
// components yet An issue has been created to resolve this:
// https://github.com/envoyproxy/envoy/issues/20818
const Http::RequestHeaderMapPtr request_headers_;
llu94 marked this conversation as resolved.
Show resolved Hide resolved
const Http::ResponseHeaderMapPtr response_headers_;
const Http::ResponseTrailerMapPtr response_trailers_;
};

using ConfigSharedPtr = std::shared_ptr<Config>;
Expand Down
1 change: 1 addition & 0 deletions test/extensions/filters/network/ratelimit/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ TEST(RateLimitFilterConfigTest, CorrectProto) {
RateLimitConfigFactory factory;
Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context);
Network::MockConnection connection;

EXPECT_CALL(connection, addReadFilter(_));
cb(connection);
}
Expand Down
Loading