Coverage Report

Created: 2023-11-12 09:30

/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
1.61k
                                          absl::string_view) override {
47
1.61k
    return absl::OkStatus();
48
1.61k
  }
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
1.60k
      : 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
3.96k
      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
315
      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
1.73k
      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
2.14k
      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
2.14k
    const envoy::config::route::v3::RateLimit::Action::QueryParameterValueMatch& action) {
274
2.14k
  std::vector<ConfigUtility::QueryParameterMatcherPtr> ret;
275
3.23k
  for (const auto& query_parameter : action.query_parameters()) {
276
3.23k
    ret.push_back(std::make_unique<ConfigUtility::QueryParameterMatcher>(query_parameter));
277
3.23k
  }
278
2.14k
  return ret;
279
2.14k
}
280
281
RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl(
282
    const envoy::config::route::v3::RateLimit& config,
283
    Server::Configuration::CommonFactoryContext& context)
284
    : disable_key_(config.disable_key()),
285
19.2k
      stage_(static_cast<uint64_t>(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, stage, 0))) {
286
60.9k
  for (const auto& action : config.actions()) {
287
60.9k
    switch (action.action_specifier_case()) {
288
11.1k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kSourceCluster:
289
11.1k
      actions_.emplace_back(new SourceClusterAction());
290
11.1k
      break;
291
10.9k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDestinationCluster:
292
10.9k
      actions_.emplace_back(new DestinationClusterAction());
293
10.9k
      break;
294
2.29k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRequestHeaders:
295
2.29k
      actions_.emplace_back(new RequestHeadersAction(action.request_headers()));
296
2.29k
      break;
297
9.30k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kRemoteAddress:
298
9.30k
      actions_.emplace_back(new RemoteAddressAction());
299
9.30k
      break;
300
9.13k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kGenericKey:
301
9.13k
      actions_.emplace_back(new GenericKeyAction(action.generic_key()));
302
9.13k
      break;
303
315
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDynamicMetadata:
304
315
      actions_.emplace_back(new MetaDataAction(action.dynamic_metadata()));
305
315
      break;
306
3.96k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMetadata:
307
3.96k
      actions_.emplace_back(new MetaDataAction(action.metadata()));
308
3.96k
      break;
309
1.73k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kHeaderValueMatch:
310
1.73k
      actions_.emplace_back(new HeaderValueMatchAction(action.header_value_match()));
311
1.73k
      break;
312
1.68k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kExtension: {
313
1.68k
      ProtobufMessage::ValidationVisitor& validator = context.messageValidationVisitor();
314
1.68k
      auto* factory = Envoy::Config::Utility::getFactory<RateLimit::DescriptorProducerFactory>(
315
1.68k
          action.extension());
316
1.68k
      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
1.67k
        RateLimitDescriptorValidationVisitor validation_visitor;
322
1.67k
        Matcher::MatchInputFactory<Http::HttpMatchingData> input_factory(validator,
323
1.67k
                                                                         validation_visitor);
324
1.67k
        Matcher::DataInputFactoryCb<Http::HttpMatchingData> data_input_cb =
325
1.67k
            input_factory.createDataInput(action.extension());
326
1.67k
        actions_.emplace_back(std::make_unique<MatchInputRateLimitDescriptor>(
327
1.67k
            action.extension().name(), data_input_cb()));
328
1.67k
        break;
329
1.67k
      }
330
2
      auto message = Envoy::Config::Utility::translateAnyToFactoryConfig(
331
2
          action.extension().typed_config(), validator, *factory);
332
2
      RateLimit::DescriptorProducerPtr producer =
333
2
          factory->createDescriptorProducerFromProto(*message, context);
334
2
      if (producer) {
335
0
        actions_.emplace_back(std::move(producer));
336
2
      } else {
337
2
        throwEnvoyExceptionOrPanic(
338
2
            absl::StrCat("Rate limit descriptor extension failed: ", action.extension().name()));
339
2
      }
340
0
      break;
341
2
    }
342
8.26k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMaskedRemoteAddress:
343
8.26k
      actions_.emplace_back(new MaskedRemoteAddressAction(action.masked_remote_address()));
344
8.26k
      break;
345
2.14k
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::
346
2.14k
        kQueryParameterValueMatch:
347
2.14k
      actions_.emplace_back(
348
2.14k
          new QueryParameterValueMatchAction(action.query_parameter_value_match()));
349
2.14k
      break;
350
0
    case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::ACTION_SPECIFIER_NOT_SET:
351
0
      throwEnvoyExceptionOrPanic("invalid config");
352
60.9k
    }
353
60.9k
  }
354
19.1k
  if (config.has_limit()) {
355
858
    switch (config.limit().override_specifier_case()) {
356
858
    case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::kDynamicMetadata:
357
858
      limit_override_.emplace(
358
858
          new DynamicMetadataRateLimitOverride(config.limit().dynamic_metadata()));
359
858
      break;
360
0
    case envoy::config::route::v3::RateLimit_Override::OverrideSpecifierCase::
361
0
        OVERRIDE_SPECIFIER_NOT_SET:
362
0
      throwEnvoyExceptionOrPanic("invalid config");
363
858
    }
364
858
  }
365
19.1k
}
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
6.45k
    : 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
6.45k
    : RateLimitPolicyImpl() {
403
19.2k
  for (const auto& rate_limit : rate_limits) {
404
19.2k
    std::unique_ptr<RateLimitPolicyEntry> rate_limit_policy_entry(
405
19.2k
        new RateLimitPolicyEntryImpl(rate_limit, context));
406
19.2k
    uint64_t stage = rate_limit_policy_entry->stage();
407
19.2k
    ASSERT(stage < rate_limit_entries_reference_.size());
408
19.2k
    rate_limit_entries_reference_[stage].emplace_back(*rate_limit_policy_entry);
409
19.2k
    rate_limit_entries_.emplace_back(std::move(rate_limit_policy_entry));
410
19.2k
  }
411
6.45k
}
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