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
|