From 961b158709de963c63758c917bab14ce8bbcacbd Mon Sep 17 00:00:00 2001 From: dd di cesare Date: Wed, 7 Aug 2024 10:04:43 +0200 Subject: [PATCH 1/2] [refactor] Renaming `RateLimitPolicy` to `Policy` Signed-off-by: dd di cesare --- src/configuration.rs | 8 ++++---- src/filter/http_context.rs | 6 +++--- src/policy_index.rs | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index 5e0b69db..6169bb8f 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -101,7 +101,7 @@ pub struct Rule { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub struct RateLimitPolicy { +pub struct Policy { pub name: String, pub domain: String, pub service: String, @@ -109,7 +109,7 @@ pub struct RateLimitPolicy { pub rules: Vec, } -impl RateLimitPolicy { +impl Policy { #[cfg(test)] pub fn new( name: String, @@ -118,7 +118,7 @@ impl RateLimitPolicy { hostnames: Vec, rules: Vec, ) -> Self { - RateLimitPolicy { + Policy { name, domain, service, @@ -168,7 +168,7 @@ pub enum FailureMode { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct PluginConfiguration { - pub rate_limit_policies: Vec, + pub rate_limit_policies: Vec, // Deny/Allow request when faced with an irrecoverable failure. pub failure_mode: FailureMode, } diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index a8491770..f93e6adc 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -1,5 +1,5 @@ use crate::configuration::{ - Condition, DataItem, DataType, FailureMode, FilterConfig, PatternExpression, RateLimitPolicy, + Condition, DataItem, DataType, FailureMode, FilterConfig, PatternExpression, Policy, Rule, }; use crate::envoy::{ @@ -60,7 +60,7 @@ impl Filter { } } - fn process_rate_limit_policy(&self, rlp: &RateLimitPolicy) -> Action { + fn process_rate_limit_policy(&self, rlp: &Policy) -> Action { let descriptors = self.build_descriptors(rlp); if descriptors.is_empty() { debug!( @@ -110,7 +110,7 @@ impl Filter { fn build_descriptors( &self, - rlp: &RateLimitPolicy, + rlp: &Policy, ) -> protobuf::RepeatedField { rlp.rules .iter() diff --git a/src/policy_index.rs b/src/policy_index.rs index a9d7319a..50a3e39f 100644 --- a/src/policy_index.rs +++ b/src/policy_index.rs @@ -1,9 +1,9 @@ use radix_trie::Trie; -use crate::configuration::RateLimitPolicy; +use crate::configuration::Policy; pub struct PolicyIndex { - raw_tree: Trie, + raw_tree: Trie, } impl PolicyIndex { @@ -13,12 +13,12 @@ impl PolicyIndex { } } - pub fn insert(&mut self, subdomain: &str, policy: RateLimitPolicy) { + pub fn insert(&mut self, subdomain: &str, policy: Policy) { let rev = Self::reverse_subdomain(subdomain); self.raw_tree.insert(rev, policy); } - pub fn get_longest_match_policy(&self, subdomain: &str) -> Option<&RateLimitPolicy> { + pub fn get_longest_match_policy(&self, subdomain: &str) -> Option<&Policy> { let rev = Self::reverse_subdomain(subdomain); self.raw_tree.get_ancestor_value(&rev) } @@ -37,11 +37,11 @@ impl PolicyIndex { #[cfg(test)] mod tests { - use crate::configuration::RateLimitPolicy; + use crate::configuration::Policy; use crate::policy_index::PolicyIndex; - fn build_ratelimit_policy(name: &str) -> RateLimitPolicy { - RateLimitPolicy::new( + fn build_ratelimit_policy(name: &str) -> Policy { + Policy::new( name.to_owned(), "".to_owned(), "".to_owned(), From 13ec938e75918ee0460380c97e032211d03be402 Mon Sep 17 00:00:00 2001 From: dd di cesare Date: Wed, 7 Aug 2024 11:14:10 +0200 Subject: [PATCH 2/2] [refactor] Delegating building descriptors to the Policy impl Signed-off-by: dd di cesare --- src/configuration.rs | 132 +++++++++++++++++++++++++++++++++++- src/filter.rs | 2 +- src/filter/http_context.rs | 135 +------------------------------------ 3 files changed, 135 insertions(+), 134 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index 6169bb8f..a5c0c214 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,6 +1,10 @@ +use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; +use crate::filter::http_context::Filter; use crate::glob::GlobPattern; use crate::policy_index::PolicyIndex; -use log::warn; +use crate::utils::tokenize_with_escaping; +use log::{debug, warn}; +use proxy_wasm::traits::Context; use serde::Deserialize; #[derive(Deserialize, Debug, Clone)] @@ -126,6 +130,132 @@ impl Policy { rules, } } + + pub fn build_descriptors( + &self, + filter: &Filter, + ) -> protobuf::RepeatedField { + self.rules + .iter() + .filter(|rule: &&Rule| self.filter_rule_by_conditions(filter, &rule.conditions)) + // Mapping 1 Rule -> 1 Descriptor + // Filter out empty descriptors + .filter_map(|rule| self.build_single_descriptor(filter, &rule.data)) + .collect() + } + + fn filter_rule_by_conditions(&self, filter: &Filter, conditions: &[Condition]) -> bool { + if conditions.is_empty() { + // no conditions means matching all the requests. + return true; + } + + conditions + .iter() + .any(|condition| self.condition_applies(filter, condition)) + } + + fn condition_applies(&self, filter: &Filter, condition: &Condition) -> bool { + condition + .all_of + .iter() + .all(|pattern_expression| self.pattern_expression_applies(filter, pattern_expression)) + } + + fn pattern_expression_applies(&self, filter: &Filter, p_e: &PatternExpression) -> bool { + let attribute_path = tokenize_with_escaping(&p_e.selector, '.', '\\'); + // convert a Vec to Vec<&str> + // attribute_path.iter().map(AsRef::as_ref).collect() + let attribute_value = match filter + .get_property(attribute_path.iter().map(AsRef::as_ref).collect()) + { + None => { + debug!( + "#{} pattern_expression_applies: selector not found: {}, defaulting to ``", + filter.context_id, p_e.selector + ); + "".to_string() + } + // TODO(eastizle): not all fields are strings + // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + Some(attribute_bytes) => match String::from_utf8(attribute_bytes) { + Err(e) => { + debug!( + "#{} pattern_expression_applies: failed to parse selector value: {}, error: {}", + filter.context_id, p_e.selector, e + ); + return false; + } + Ok(attribute_value) => attribute_value, + }, + }; + p_e.operator + .eval(p_e.value.as_str(), attribute_value.as_str()) + } + + fn build_single_descriptor( + &self, + filter: &Filter, + data_list: &[DataItem], + ) -> Option { + let mut entries = ::protobuf::RepeatedField::default(); + + // iterate over data items to allow any data item to skip the entire descriptor + for data in data_list.iter() { + match &data.item { + DataType::Static(static_item) => { + let mut descriptor_entry = RateLimitDescriptor_Entry::new(); + descriptor_entry.set_key(static_item.key.to_owned()); + descriptor_entry.set_value(static_item.value.to_owned()); + entries.push(descriptor_entry); + } + DataType::Selector(selector_item) => { + let descriptor_key = match &selector_item.key { + None => selector_item.selector.to_owned(), + Some(key) => key.to_owned(), + }; + + let attribute_path = tokenize_with_escaping(&selector_item.selector, '.', '\\'); + // convert a Vec to Vec<&str> + // attribute_path.iter().map(AsRef::as_ref).collect() + let value = match filter + .get_property(attribute_path.iter().map(AsRef::as_ref).collect()) + { + None => { + debug!( + "#{} build_single_descriptor: selector not found: {}", + filter.context_id, selector_item.selector + ); + match &selector_item.default { + None => return None, // skipping the entire descriptor + Some(default_value) => default_value.clone(), + } + } + // TODO(eastizle): not all fields are strings + // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + Some(attribute_bytes) => match String::from_utf8(attribute_bytes) { + Err(e) => { + debug!( + "#{} build_single_descriptor: failed to parse selector value: {}, error: {}", + filter.context_id, selector_item.selector, e + ); + return None; + } + Ok(attribute_value) => attribute_value, + }, + }; + let mut descriptor_entry = RateLimitDescriptor_Entry::new(); + descriptor_entry.set_key(descriptor_key); + descriptor_entry.set_value(value); + entries.push(descriptor_entry); + } + } + } + + let mut res = RateLimitDescriptor::new(); + res.set_entries(entries); + Some(res) + } } pub struct FilterConfig { diff --git a/src/filter.rs b/src/filter.rs index c87e086f..dffea274 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,4 +1,4 @@ -mod http_context; +pub(crate) mod http_context; mod root_context; #[cfg_attr( diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index f93e6adc..a7a47af0 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -1,13 +1,6 @@ -use crate::configuration::{ - Condition, DataItem, DataType, FailureMode, FilterConfig, PatternExpression, Policy, - Rule, -}; -use crate::envoy::{ - RateLimitDescriptor, RateLimitDescriptor_Entry, RateLimitRequest, RateLimitResponse, - RateLimitResponse_Code, -}; +use crate::configuration::{FailureMode, FilterConfig, Policy}; +use crate::envoy::{RateLimitRequest, RateLimitResponse, RateLimitResponse_Code}; use crate::filter::http_context::TracingHeader::{Baggage, Traceparent, Tracestate}; -use crate::utils::tokenize_with_escaping; use log::{debug, warn}; use protobuf::Message; use proxy_wasm::traits::{Context, HttpContext}; @@ -61,7 +54,7 @@ impl Filter { } fn process_rate_limit_policy(&self, rlp: &Policy) -> Action { - let descriptors = self.build_descriptors(rlp); + let descriptors = rlp.build_descriptors(self); if descriptors.is_empty() { debug!( "#{} process_rate_limit_policy: empty descriptors", @@ -108,128 +101,6 @@ impl Filter { } } - fn build_descriptors( - &self, - rlp: &Policy, - ) -> protobuf::RepeatedField { - rlp.rules - .iter() - .filter(|rule: &&Rule| self.filter_rule_by_conditions(&rule.conditions)) - // Mapping 1 Rule -> 1 Descriptor - // Filter out empty descriptors - .filter_map(|rule| self.build_single_descriptor(&rule.data)) - .collect() - } - - fn filter_rule_by_conditions(&self, conditions: &[Condition]) -> bool { - if conditions.is_empty() { - // no conditions is equivalent to matching all the requests. - return true; - } - - conditions - .iter() - .any(|condition| self.condition_applies(condition)) - } - - fn condition_applies(&self, condition: &Condition) -> bool { - condition - .all_of - .iter() - .all(|pattern_expression| self.pattern_expression_applies(pattern_expression)) - } - - fn pattern_expression_applies(&self, p_e: &PatternExpression) -> bool { - let attribute_path = tokenize_with_escaping(&p_e.selector, '.', '\\'); - // convert a Vec to Vec<&str> - // attribute_path.iter().map(AsRef::as_ref).collect() - let attribute_value = match self - .get_property(attribute_path.iter().map(AsRef::as_ref).collect()) - { - None => { - debug!( - "#{} pattern_expression_applies: selector not found: {}, defaulting to ``", - self.context_id, p_e.selector - ); - "".to_string() - } - // TODO(eastizle): not all fields are strings - // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - Some(attribute_bytes) => match String::from_utf8(attribute_bytes) { - Err(e) => { - debug!( - "#{} pattern_expression_applies: failed to parse selector value: {}, error: {}", - self.context_id, p_e.selector, e - ); - return false; - } - Ok(attribute_value) => attribute_value, - }, - }; - p_e.operator - .eval(p_e.value.as_str(), attribute_value.as_str()) - } - - fn build_single_descriptor(&self, data_list: &[DataItem]) -> Option { - let mut entries = ::protobuf::RepeatedField::default(); - - // iterate over data items to allow any data item to skip the entire descriptor - for data in data_list.iter() { - match &data.item { - DataType::Static(static_item) => { - let mut descriptor_entry = RateLimitDescriptor_Entry::new(); - descriptor_entry.set_key(static_item.key.to_owned()); - descriptor_entry.set_value(static_item.value.to_owned()); - entries.push(descriptor_entry); - } - DataType::Selector(selector_item) => { - let descriptor_key = match &selector_item.key { - None => selector_item.selector.to_owned(), - Some(key) => key.to_owned(), - }; - - let attribute_path = tokenize_with_escaping(&selector_item.selector, '.', '\\'); - // convert a Vec to Vec<&str> - // attribute_path.iter().map(AsRef::as_ref).collect() - let value = match self - .get_property(attribute_path.iter().map(AsRef::as_ref).collect()) - { - None => { - debug!( - "#{} build_single_descriptor: selector not found: {}", - self.context_id, selector_item.selector - ); - match &selector_item.default { - None => return None, // skipping the entire descriptor - Some(default_value) => default_value.clone(), - } - } - // TODO(eastizle): not all fields are strings - // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - Some(attribute_bytes) => match String::from_utf8(attribute_bytes) { - Err(e) => { - debug!( - "#{} build_single_descriptor: failed to parse selector value: {}, error: {}", - self.context_id, selector_item.selector, e - ); - return None; - } - Ok(attribute_value) => attribute_value, - }, - }; - let mut descriptor_entry = RateLimitDescriptor_Entry::new(); - descriptor_entry.set_key(descriptor_key); - descriptor_entry.set_value(value); - entries.push(descriptor_entry); - } - } - } - - let mut res = RateLimitDescriptor::new(); - res.set_entries(entries); - Some(res) - } - fn handle_error_on_grpc_response(&self) { match &self.config.failure_mode { FailureMode::Deny => {