Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/router/router_ratelimit.cc
Line
Count
Source (jump to first uncovered line)
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
2.16k
                                          absl::string_view) override {
47
2.16k
    return absl::OkStatus();
48
2.16k
  }
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
2.13k
      : 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
  // Note: we don't do validity checking for CIDR range here because we know
174
  // from addressAsString this is a valid address.
175
0
  Network::Address::CidrRange cidr_entry =
176
0
      *Network::Address::CidrRange::create(remote_address->ip()->addressAsString(), mask_len);
177
0
  descriptor_entry = {"masked_remote_address", cidr_entry.asString()};
178
179
0
  return true;
180
0
}
181
182
bool GenericKeyAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
183
                                          const std::string&, const Http::RequestHeaderMap&,
184
0
                                          const StreamInfo::StreamInfo&) const {
185
0
  descriptor_entry = {descriptor_key_, descriptor_value_};
186
0
  return true;
187
0
}
188
189
MetaDataAction::MetaDataAction(const envoy::config::route::v3::RateLimit::Action::MetaData& action)
190
    : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()),
191
      default_value_(action.default_value()), source_(action.source()),
192
1.73k
      skip_if_absent_(action.skip_if_absent()) {}
193
194
MetaDataAction::MetaDataAction(
195
    const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action)
196
    : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()),
197
      default_value_(action.default_value()),
198
      source_(envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC),
199
455
      skip_if_absent_(false) {}
200
201
bool MetaDataAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
202
                                        const std::string&, const Http::RequestHeaderMap&,
203
0
                                        const StreamInfo::StreamInfo& info) const {
204
0
  const envoy::config::core::v3::Metadata* metadata_source;
205
206
0
  switch (source_) {
207
0
    PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
208
0
  case envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC:
209
0
    metadata_source = &info.dynamicMetadata();
210
0
    break;
211
0
  case envoy::config::route::v3::RateLimit::Action::MetaData::ROUTE_ENTRY:
212
0
    metadata_source = &info.route()->metadata();
213
0
    break;
214
0
  }
215
216
0
  const std::string metadata_string_value =
217
0
      Envoy::Config::Metadata::metadataValue(metadata_source, metadata_key_).string_value();
218
219
0
  if (!metadata_string_value.empty()) {
220
0
    descriptor_entry = {descriptor_key_, metadata_string_value};
221
0
    return true;
222
0
  } else if (metadata_string_value.empty() && !default_value_.empty()) {
223
0
    descriptor_entry = {descriptor_key_, default_value_};
224
0
    return true;
225
0
  }
226
227
  // If the metadata key is not present and no default value is set, skip this
228
  // descriptor if skip_if_absent is true. If skip_if_absent is false, do not
229
  // call rate limiting service.
230
0
  return skip_if_absent_;
231
0
}
232
233
HeaderValueMatchAction::HeaderValueMatchAction(
234
    const envoy::config::route::v3::RateLimit::Action::HeaderValueMatch& action,
235
    Server::Configuration::CommonFactoryContext& context)
236
    : descriptor_value_(action.descriptor_value()),
237
      descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "header_match"),
238
      expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)),
239
2.14k
      action_headers_(Http::HeaderUtility::buildHeaderDataVector(action.headers(), context)) {}
240
241
bool HeaderValueMatchAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_entry,
242
                                                const std::string&,
243
                                                const Http::RequestHeaderMap& headers,
244
0
                                                const StreamInfo::StreamInfo&) const {
245
0
  if (expect_match_ == Http::HeaderUtility::matchHeaders(headers, action_headers_)) {
246
0
    descriptor_entry = {descriptor_key_, descriptor_value_};
247
0
    return true;
248
0
  } else {
249
0
    return false;
250
0
  }
251
0
}
252
253
QueryParameterValueMatchAction::QueryParameterValueMatchAction(
254
    const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action,
255
    Server::Configuration::CommonFactoryContext& context)
256
    : descriptor_value_(action.descriptor_value()),
257
      descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "query_match"),
258
      expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)),
259
3.69k
      action_query_parameters_(buildQueryParameterMatcherVector(action, context)) {}
260
261
bool QueryParameterValueMatchAction::populateDescriptor(
262
    RateLimit::DescriptorEntry& descriptor_entry, const std::string&,
263
0
    const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const {
264
0
  Http::Utility::QueryParamsMulti query_parameters =
265
0
      Http::Utility::QueryParamsMulti::parseAndDecodeQueryString(headers.getPathValue());
266
0
  if (expect_match_ ==
267
0
      ConfigUtility::matchQueryParams(query_parameters, action_query_parameters_)) {
268
0
    descriptor_entry = {descriptor_key_, descriptor_value_};
269
0
    return true;
270
0
  } else {
271
0
    return false;
272
0
  }
273
0
}
274
275
std::vector<ConfigUtility::QueryParameterMatcherPtr>
276
QueryParameterValueMatchAction::buildQueryParameterMatcherVector(
277
    const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action,
278
3.69k
    Server::Configuration::CommonFactoryContext& context) {
279
3.69k
  std::vector<ConfigUtility::QueryParameterMatcherPtr> ret;
280
7.02k
  for (const auto& query_parameter : action.query_parameters()) {
281
7.02k
    ret.push_back(std::make_unique<ConfigUtility::QueryParameterMatcher>(query_parameter, context));
282
7.02k
  }
283
3.69k
  return ret;
284
3.69k
}
285
286
RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl(
287
    const envoy::config::route::v3::RateLimit& config,
288
    Server::Configuration::CommonFactoryContext& context, absl::Status& creation_status)
289
    : disable_key_(config.disable_key()),
290
15.5k
      stage_(static_cast<uint64_t>(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, stage, 0))) {
291
52.5k
  for (const auto& action : config.actions()) {
292
52.5k
    switch (action.action_specifier_case()) {
293
9.94k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kSourceCluster:
294
9.94k
      actions_.emplace_back(new SourceClusterAction());
295
9.94k
      break;
296
7.26k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDestinationCluster:
297
7.26k
      actions_.emplace_back(new DestinationClusterAction());
298
7.26k
      break;
299
2.47k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRequestHeaders:
300
2.47k
      actions_.emplace_back(new RequestHeadersAction(action.request_headers()));
301
2.47k
      break;
302
10.5k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRemoteAddress:
303
10.5k
      actions_.emplace_back(new RemoteAddressAction());
304
10.5k
      break;
305
4.71k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kGenericKey:
306
4.71k
      actions_.emplace_back(new GenericKeyAction(action.generic_key()));
307
4.71k
      break;
308
455
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDynamicMetadata:
309
455
      actions_.emplace_back(new MetaDataAction(action.dynamic_metadata()));
310
455
      break;
311
1.73k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMetadata:
312
1.73k
      actions_.emplace_back(new MetaDataAction(action.metadata()));
313
1.73k
      break;
314
2.14k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kHeaderValueMatch:
315
2.14k
      actions_.emplace_back(new HeaderValueMatchAction(action.header_value_match(), context));
316
2.14k
      break;
317
2.22k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kExtension: {
318
2.22k
      ProtobufMessage::ValidationVisitor& validator = context.messageValidationVisitor();
319
2.22k
      auto* factory = Envoy::Config::Utility::getFactory<RateLimit::DescriptorProducerFactory>(
320
2.22k
          action.extension());
321
2.22k
      if (!factory) {
322
        // If no descriptor extension is found, fallback to using HTTP matcher
323
        // input functions. Note that if the same extension name or type was
324
        // dual registered as an extension descriptor and an HTTP matcher input
325
        // function, the descriptor extension takes priority.
326
2.22k
        RateLimitDescriptorValidationVisitor validation_visitor;
327
2.22k
        Matcher::MatchInputFactory<Http::HttpMatchingData> input_factory(validator,
328
2.22k
                                                                         validation_visitor);
329
2.22k
        Matcher::DataInputFactoryCb<Http::HttpMatchingData> data_input_cb =
330
2.22k
            input_factory.createDataInput(action.extension());
331
2.22k
        actions_.emplace_back(std::make_unique<MatchInputRateLimitDescriptor>(
332
2.22k
            action.extension().name(), data_input_cb()));
333
2.22k
        break;
334
2.22k
      }
335
1
      auto message = Envoy::Config::Utility::translateAnyToFactoryConfig(
336
1
          action.extension().typed_config(), validator, *factory);
337
1
      RateLimit::DescriptorProducerPtr producer =
338
1
          factory->createDescriptorProducerFromProto(*message, context);
339
1
      if (producer) {
340
0
        actions_.emplace_back(std::move(producer));
341
1
      } else {
342
1
        creation_status = absl::InvalidArgumentError(
343
1
            absl::StrCat("Rate limit descriptor extension failed: ", action.extension().name()));
344
1
        return;
345
1
      }
346
0
      break;
347
1
    }
348
7.33k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMaskedRemoteAddress:
349
7.33k
      actions_.emplace_back(new MaskedRemoteAddressAction(action.masked_remote_address()));
350
7.33k
      break;
351
3.69k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::
352
3.69k
        kQueryParameterValueMatch:
353
3.69k
      actions_.emplace_back(
354
3.69k
          new QueryParameterValueMatchAction(action.query_parameter_value_match(), context));
355
3.69k
      break;
356
0
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::ACTION_SPECIFIER_NOT_SET:
357
0
      PANIC_DUE_TO_CORRUPT_ENUM;
358
52.5k
    }
359
52.5k
  }
360
15.4k
  if (config.has_limit()) {
361
332
    switch (config.limit().override_specifier_case()) {
362
332
    case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::kDynamicMetadata:
363
332
      limit_override_.emplace(
364
332
          new DynamicMetadataRateLimitOverride(config.limit().dynamic_metadata()));
365
332
      break;
366
0
    case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::
367
0
        OVERRIDE_SPECIFIER_NOT_SET:
368
0
      PANIC_DUE_TO_CORRUPT_ENUM;
369
332
    }
370
332
  }
371
15.4k
}
372
373
void RateLimitPolicyEntryImpl::populateDescriptors(std::vector<RateLimit::Descriptor>& descriptors,
374
                                                   const std::string& local_service_cluster,
375
                                                   const Http::RequestHeaderMap& headers,
376
0
                                                   const StreamInfo::StreamInfo& info) const {
377
0
  RateLimit::Descriptor descriptor;
378
0
  bool result =
379
0
      populateDescriptor(actions_, descriptor.entries_, local_service_cluster, headers, info);
380
381
0
  if (limit_override_) {
382
0
    limit_override_.value()->populateOverride(descriptor, &info.dynamicMetadata());
383
0
  }
384
385
0
  if (result) {
386
0
    descriptors.emplace_back(descriptor);
387
0
  }
388
0
}
389
390
void RateLimitPolicyEntryImpl::populateLocalDescriptors(
391
    std::vector<Envoy::RateLimit::LocalDescriptor>& descriptors,
392
    const std::string& local_service_cluster, const Http::RequestHeaderMap& headers,
393
0
    const StreamInfo::StreamInfo& info) const {
394
0
  RateLimit::LocalDescriptor descriptor({});
395
0
  bool result =
396
0
      populateDescriptor(actions_, descriptor.entries_, local_service_cluster, headers, info);
397
0
  if (result) {
398
0
    descriptors.emplace_back(descriptor);
399
0
  }
400
0
}
401
402
RateLimitPolicyImpl::RateLimitPolicyImpl()
403
4.90k
    : rate_limit_entries_reference_(RateLimitPolicyImpl::MAX_STAGE_NUMBER + 1) {}
404
405
RateLimitPolicyImpl::RateLimitPolicyImpl(
406
    const Protobuf::RepeatedPtrField<envoy::config::route::v3::RateLimit>& rate_limits,
407
    Server::Configuration::CommonFactoryContext& context, absl::Status& creation_status)
408
4.90k
    : RateLimitPolicyImpl() {
409
4.90k
  creation_status = absl::OkStatus();
410
15.5k
  for (const auto& rate_limit : rate_limits) {
411
15.5k
    std::unique_ptr<RateLimitPolicyEntry> rate_limit_policy_entry(
412
15.5k
        new RateLimitPolicyEntryImpl(rate_limit, context, creation_status));
413
15.5k
    uint64_t stage = rate_limit_policy_entry->stage();
414
15.5k
    ASSERT(stage < rate_limit_entries_reference_.size());
415
15.5k
    rate_limit_entries_reference_[stage].emplace_back(*rate_limit_policy_entry);
416
15.5k
    rate_limit_entries_.emplace_back(std::move(rate_limit_policy_entry));
417
15.5k
  }
418
4.90k
}
419
420
const std::vector<std::reference_wrapper<const Router::RateLimitPolicyEntry>>&
421
0
RateLimitPolicyImpl::getApplicableRateLimit(uint64_t stage) const {
422
0
  ASSERT(stage < rate_limit_entries_reference_.size());
423
0
  return rate_limit_entries_reference_[stage];
424
0
}
425
426
} // namespace Router
427
} // namespace Envoy