diff --git a/src/configuration/action_set_index.rs b/src/action_set_index.rs similarity index 83% rename from src/configuration/action_set_index.rs rename to src/action_set_index.rs index 068fd96..703fa3c 100644 --- a/src/configuration/action_set_index.rs +++ b/src/action_set_index.rs @@ -1,9 +1,9 @@ -use crate::configuration::action_set::ActionSet; +use crate::runtime_action_set::RuntimeActionSet; use radix_trie::Trie; use std::rc::Rc; -pub struct ActionSetIndex { - raw_tree: Trie>>, +pub(crate) struct ActionSetIndex { + raw_tree: Trie>>, } impl ActionSetIndex { @@ -13,7 +13,7 @@ impl ActionSetIndex { } } - pub fn insert(&mut self, subdomain: &str, action_set: Rc) { + pub fn insert(&mut self, subdomain: &str, action_set: Rc) { let rev = Self::reverse_subdomain(subdomain); self.raw_tree.map_with_default( rev, @@ -24,7 +24,10 @@ impl ActionSetIndex { ); } - pub fn get_longest_match_action_sets(&self, subdomain: &str) -> Option<&Vec>> { + pub fn get_longest_match_action_sets( + &self, + subdomain: &str, + ) -> Option<&Vec>> { let rev = Self::reverse_subdomain(subdomain); self.raw_tree.get_ancestor_value(&rev) } @@ -43,12 +46,16 @@ impl ActionSetIndex { #[cfg(test)] mod tests { - use crate::configuration::action_set::ActionSet; - use crate::configuration::action_set_index::ActionSetIndex; + use crate::action_set_index::ActionSetIndex; + use crate::runtime_action_set::RuntimeActionSet; use std::rc::Rc; - fn build_ratelimit_action_set(name: &str) -> ActionSet { - ActionSet::new(name.to_owned(), Default::default(), Vec::new()) + fn build_ratelimit_action_set(name: &str) -> RuntimeActionSet { + RuntimeActionSet { + name: name.to_owned(), + route_rule_predicates: Default::default(), + runtime_actions: Vec::new(), + } } #[test] diff --git a/src/auth_action.rs b/src/auth_action.rs new file mode 100644 index 0000000..4031b62 --- /dev/null +++ b/src/auth_action.rs @@ -0,0 +1,82 @@ +use crate::configuration::{Action, FailureMode, Service}; +use crate::data::Predicate; +use crate::service::GrpcService; +use log::error; +use std::rc::Rc; + +#[derive(Debug)] +pub struct AuthAction { + grpc_service: Rc, + scope: String, + predicates: Vec, +} + +impl AuthAction { + pub fn new(action: &Action, service: &Service) -> Result { + let mut predicates = Vec::default(); + for predicate in &action.predicates { + predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?); + } + + Ok(AuthAction { + grpc_service: Rc::new(GrpcService::new(Rc::new(service.clone()))), + scope: action.scope.clone(), + predicates, + }) + } + + pub fn get_grpcservice(&self) -> Rc { + Rc::clone(&self.grpc_service) + } + + pub fn scope(&self) -> &str { + self.scope.as_str() + } + + pub fn conditions_apply(&self) -> bool { + let predicates = &self.predicates; + predicates.is_empty() + || predicates.iter().all(|predicate| match predicate.test() { + Ok(b) => b, + Err(err) => { + error!("Failed to evaluate {:?}: {}", predicate, err); + panic!("Err out of this!") + } + }) + } + + pub fn get_failure_mode(&self) -> FailureMode { + self.grpc_service.get_failure_mode() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::configuration::{Action, FailureMode, Service, ServiceType, Timeout}; + + fn build_auth_action_with_predicates(predicates: Vec) -> AuthAction { + let action = Action { + service: "some_service".into(), + scope: "some_scope".into(), + predicates, + data: Vec::default(), + }; + + let service = Service { + service_type: ServiceType::Auth, + endpoint: "some_endpoint".into(), + failure_mode: FailureMode::default(), + timeout: Timeout::default(), + }; + + AuthAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?") + } + + #[test] + fn empty_predicates_do_apply() { + let auth_action = build_auth_action_with_predicates(Vec::default()); + assert!(auth_action.conditions_apply()); + } +} diff --git a/src/configuration.rs b/src/configuration.rs index 7f9771f..5dfdea7 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,39 +1,58 @@ -use std::cell::OnceCell; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use std::rc::Rc; use std::sync::Arc; -use crate::configuration::action_set::ActionSet; -use crate::configuration::action_set_index::ActionSetIndex; -use crate::data; -use crate::data::Predicate; -use crate::service::GrpcService; use cel_interpreter::functions::time::duration; use cel_interpreter::Value; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer}; use std::time::Duration; -pub mod action; -pub mod action_set; -mod action_set_index; +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Action { + pub service: String, + pub scope: String, + #[serde(default)] + pub predicates: Vec, + #[serde(default)] + pub data: Vec, +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct RouteRuleConditions { + pub hostnames: Vec, + #[serde(default)] + pub predicates: Vec, +} + +#[derive(Default, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ActionSet { + pub name: String, + pub route_rule_conditions: RouteRuleConditions, + pub actions: Vec, +} + +impl ActionSet { + #[cfg(test)] + pub fn new( + name: String, + route_rule_conditions: RouteRuleConditions, + actions: Vec, + ) -> Self { + ActionSet { + name, + route_rule_conditions, + actions, + } + } +} #[derive(Deserialize, Debug, Clone)] pub struct ExpressionItem { pub key: String, pub value: String, - #[serde(skip_deserializing)] - pub compiled: OnceCell, -} - -impl ExpressionItem { - pub fn compile(&self) -> Result<(), String> { - self.compiled - .set(data::Expression::new(&self.value).map_err(|e| e.to_string())?) - .expect("Expression must not be compiled yet!"); - Ok(()) - } } #[derive(Deserialize, Debug, Clone)] @@ -50,15 +69,6 @@ pub enum DataType { Expression(ExpressionItem), } -impl DataType { - pub fn compile(&self) -> Result<(), String> { - match self { - DataType::Static(_) => Ok(()), - DataType::Expression(exp) => exp.compile(), - } - } -} - #[derive(Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] pub struct DataItem { @@ -66,69 +76,6 @@ pub struct DataItem { pub item: DataType, } -pub struct FilterConfig { - pub index: ActionSetIndex, - pub services: Rc>>, -} - -impl Default for FilterConfig { - fn default() -> Self { - Self { - index: ActionSetIndex::new(), - services: Rc::new(HashMap::new()), - } - } -} - -impl TryFrom for FilterConfig { - type Error = String; - - fn try_from(config: PluginConfiguration) -> Result { - let mut index = ActionSetIndex::new(); - for action_set in config.action_sets.iter() { - let mut predicates = Vec::default(); - for predicate in &action_set.route_rule_conditions.predicates { - predicates.push(Predicate::route_rule(predicate).map_err(|e| e.to_string())?); - } - action_set - .route_rule_conditions - .compiled_predicates - .set(predicates) - .expect("Predicates must not be compiled yet!"); - for action in &action_set.actions { - let mut predicates = Vec::default(); - for predicate in &action.predicates { - predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?); - } - action - .compiled_predicates - .set(predicates) - .expect("Predicates must not be compiled yet!"); - - for datum in &action.data { - datum.item.compile()?; - } - } - - for hostname in action_set.route_rule_conditions.hostnames.iter() { - index.insert(hostname, Rc::new(action_set.clone())); - } - } - - // configure grpc services from the services in config - let services = config - .services - .into_iter() - .map(|(name, ext)| (name, Rc::new(GrpcService::new(Rc::new(ext))))) - .collect(); - - Ok(Self { - index, - services: Rc::new(services), - }) - } -} - #[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq)] #[serde(rename_all = "lowercase")] pub enum FailureMode { @@ -277,10 +224,10 @@ mod test { } assert!(res.is_ok()); - let filter_config = res.expect("result is ok"); - assert_eq!(filter_config.action_sets.len(), 1); + let plugin_config = res.expect("result is ok"); + assert_eq!(plugin_config.action_sets.len(), 1); - let services = &filter_config.services; + let services = &plugin_config.services; assert_eq!(services.len(), 2); if let Some(auth_service) = services.get("authorino") { @@ -301,12 +248,12 @@ mod test { panic!() } - let predicates = &filter_config.action_sets[0] + let predicates = &plugin_config.action_sets[0] .route_rule_conditions .predicates; assert_eq!(predicates.len(), 3); - let actions = &filter_config.action_sets[0].actions; + let actions = &plugin_config.action_sets[0].actions; assert_eq!(actions.len(), 2); let auth_action = &actions[0]; @@ -326,17 +273,6 @@ mod test { let rl_predicates = &rl_action.predicates; assert_eq!(rl_predicates.len(), 1); - // TODO(eastizle): DataItem does not implement PartialEq, add it only for testing? - //assert_eq!( - // data_items[0], - // DataItem { - // item: DataType::Static(StaticItem { - // key: String::from("rlp-ns-A/rlp-name-A"), - // value: String::from("1") - // }) - // } - //); - if let DataType::Static(static_item) = &rl_data_items[0].item { assert_eq!(static_item.key, "rlp-ns-A/rlp-name-A"); assert_eq!(static_item.value, "1"); @@ -364,8 +300,8 @@ mod test { } assert!(res.is_ok()); - let filter_config = res.expect("result is ok"); - assert_eq!(filter_config.action_sets.len(), 0); + let plugin_config = res.expect("result is ok"); + assert_eq!(plugin_config.action_sets.len(), 0); } #[test] @@ -410,10 +346,10 @@ mod test { } assert!(res.is_ok()); - let filter_config = res.expect("result is ok"); - assert_eq!(filter_config.action_sets.len(), 1); + let plugin_config = res.expect("result is ok"); + assert_eq!(plugin_config.action_sets.len(), 1); - let services = &filter_config.services; + let services = &plugin_config.services; assert_eq!( services .get("limitador") @@ -422,12 +358,12 @@ mod test { Timeout(Duration::from_millis(20)) ); - let predicates = &filter_config.action_sets[0] + let predicates = &plugin_config.action_sets[0] .route_rule_conditions .predicates; assert_eq!(predicates.len(), 0); - let actions = &filter_config.action_sets[0].actions; + let actions = &plugin_config.action_sets[0].actions; assert_eq!(actions.len(), 1); let action_predicates = &actions[0].predicates; @@ -504,28 +440,4 @@ mod test { let res = serde_json::from_str::(bad_config); assert!(res.is_err()); } - - #[test] - fn filter_config_from_configuration() { - let res = serde_json::from_str::(CONFIG); - if let Err(ref e) = res { - eprintln!("{e}"); - } - assert!(res.is_ok()); - - let result = FilterConfig::try_from(res.expect("result is ok")); - let filter_config = result.expect("That didn't work"); - let rlp_option = filter_config - .index - .get_longest_match_action_sets("example.com"); - assert!(rlp_option.is_some()); - - let rlp_option = filter_config - .index - .get_longest_match_action_sets("test.toystore.com"); - assert!(rlp_option.is_some()); - - let rlp_option = filter_config.index.get_longest_match_action_sets("unknown"); - assert!(rlp_option.is_none()); - } } diff --git a/src/configuration/action.rs b/src/configuration/action.rs deleted file mode 100644 index 58e893f..0000000 --- a/src/configuration/action.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::configuration::{DataItem, DataType}; -use crate::data::Predicate; -use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; -use cel_interpreter::Value; -use log::error; -use protobuf::RepeatedField; -use serde::Deserialize; -use std::cell::OnceCell; - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Action { - pub service: String, - pub scope: String, - #[serde(default)] - pub predicates: Vec, - #[serde(skip_deserializing)] - pub compiled_predicates: OnceCell>, - #[serde(default)] - pub data: Vec, -} - -impl Action { - pub fn conditions_apply(&self) -> bool { - let predicates = self - .compiled_predicates - .get() - .expect("predicates must be compiled by now"); - predicates.is_empty() - || predicates - .iter() - .enumerate() - .all(|(pos, predicate)| match predicate.test() { - Ok(b) => b, - Err(err) => { - error!("Failed to evaluate {}: {}", self.predicates[pos], err); - panic!("Err out of this!") - } - }) - } - - pub fn build_descriptors(&self) -> RepeatedField { - let mut entries = RepeatedField::new(); - if let Some(desc) = self.build_single_descriptor() { - entries.push(desc); - } - entries - } - - fn build_single_descriptor(&self) -> Option { - let mut entries = RepeatedField::default(); - - // iterate over data items to allow any data item to skip the entire descriptor - for data in self.data.iter() { - let (key, value) = match &data.item { - DataType::Static(static_item) => { - (static_item.key.to_owned(), static_item.value.to_owned()) - } - DataType::Expression(cel) => ( - cel.key.clone(), - match cel - .compiled - .get() - .expect("Expression must be compiled by now") - .eval() - { - Ok(value) => match value { - Value::Int(n) => format!("{n}"), - Value::UInt(n) => format!("{n}"), - Value::Float(n) => format!("{n}"), - // todo this probably should be a proper string literal! - Value::String(s) => (*s).clone(), - Value::Bool(b) => format!("{b}"), - Value::Null => "null".to_owned(), - _ => panic!("Only scalar values can be sent as data"), - }, - Err(err) => { - error!("Failed to evaluate {}: {}", cel.value, err); - panic!("Err out of this!") - } - }, - ), - }; - let mut descriptor_entry = RateLimitDescriptor_Entry::new(); - descriptor_entry.set_key(key); - descriptor_entry.set_value(value); - entries.push(descriptor_entry); - } - let mut res = RateLimitDescriptor::new(); - res.set_entries(entries); - Some(res) - } -} - -#[cfg(test)] -mod test { - use crate::configuration::action::Action; - use std::cell::OnceCell; - - #[test] - fn empty_predicates_do_apply() { - let compiled_predicates = OnceCell::new(); - compiled_predicates - .set(Vec::default()) - .expect("predicates must not be compiled yet!"); - - let action = Action { - service: String::from("svc1"), - scope: String::from("sc1"), - predicates: vec![], - compiled_predicates, - data: vec![], - }; - - assert!(action.conditions_apply()) - } -} diff --git a/src/configuration/action_set.rs b/src/configuration/action_set.rs deleted file mode 100644 index 1e8a61a..0000000 --- a/src/configuration/action_set.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::configuration::action::Action; -use crate::data::Predicate; -use log::error; -use serde::Deserialize; -use std::cell::OnceCell; - -#[derive(Deserialize, Debug, Clone, Default)] -pub struct RouteRuleConditions { - pub hostnames: Vec, - #[serde(default)] - pub predicates: Vec, - #[serde(skip_deserializing)] - pub compiled_predicates: OnceCell>, -} - -#[derive(Default, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ActionSet { - pub name: String, - pub route_rule_conditions: RouteRuleConditions, - pub actions: Vec, -} - -impl ActionSet { - #[cfg(test)] - pub fn new( - name: String, - route_rule_conditions: RouteRuleConditions, - actions: Vec, - ) -> Self { - ActionSet { - name, - route_rule_conditions, - actions, - } - } - - pub fn conditions_apply(&self) -> bool { - let predicates = self - .route_rule_conditions - .compiled_predicates - .get() - .expect("predicates must be compiled by now"); - predicates.is_empty() - || predicates - .iter() - .enumerate() - .all(|(pos, predicate)| match predicate.test() { - Ok(b) => b, - Err(err) => { - error!( - "Failed to evaluate {}: {}", - self.route_rule_conditions.predicates[pos], err - ); - panic!("Err out of this!") - } - }) - } -} - -#[cfg(test)] -mod test { - use crate::configuration::action_set::ActionSet; - - fn build_action_set(name: &str) -> ActionSet { - ActionSet::new(name.to_owned(), Default::default(), Vec::new()) - } - - #[test] - fn empty_route_rule_conditions_do_apply() { - let action_set_1 = build_action_set("as_1"); - action_set_1 - .route_rule_conditions - .compiled_predicates - .set(Vec::default()) - .expect("Predicates must not be compiled yet!"); - - assert!(action_set_1.conditions_apply()) - } -} diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index e9204d1..e7fd0d7 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -1,8 +1,9 @@ -use crate::configuration::action_set::ActionSet; -use crate::configuration::{FailureMode, FilterConfig}; +use crate::configuration::FailureMode; #[cfg(feature = "debug-host-behaviour")] use crate::data; use crate::operation_dispatcher::{OperationDispatcher, OperationError}; +use crate::runtime_action_set::RuntimeActionSet; +use crate::runtime_config::RuntimeConfig; use crate::service::GrpcService; use log::{debug, warn}; use proxy_wasm::traits::{Context, HttpContext}; @@ -12,7 +13,7 @@ use std::rc::Rc; pub struct Filter { pub context_id: u32, - pub config: Rc, + pub config: Rc, pub response_headers_to_add: Vec<(String, String)>, pub operation_dispatcher: RefCell, } @@ -32,22 +33,13 @@ impl Filter { } #[allow(unknown_lints, clippy::manual_inspect)] - fn process_action_sets(&self, action_sets: &[Rc]) -> Action { - if let Some(action_set) = action_sets - .iter() - .find(|action_set| action_set.conditions_apply()) - { - debug!( - "#{} action_set selected {}", - self.context_id, action_set.name - ); - if let Err(op_err) = self - .operation_dispatcher + fn process_action_sets(&self, m_set_list: &[Rc]) -> Action { + if let Some(m_set) = m_set_list.iter().find(|m_set| m_set.conditions_apply()) { + debug!("#{} action_set selected {}", self.context_id, m_set.name); + //debug!("#{} runtime action_set {:#?}", self.context_id, m_set); + self.operation_dispatcher .borrow_mut() - .build_operations(&action_set.actions) - { - self.send_http_response(500, vec![], Some(format!("{op_err}").as_ref())); - } + .build_operations(&m_set.runtime_actions) } else { debug!( "#{} process_action_sets: no action_set with conditions applies", @@ -111,7 +103,7 @@ impl HttpContext for Filter { ); Action::Continue } - Some(action_sets) => self.process_action_sets(action_sets), + Some(m_sets) => self.process_action_sets(m_sets), } } diff --git a/src/filter/root_context.rs b/src/filter/root_context.rs index 01b29ff..4716387 100644 --- a/src/filter/root_context.rs +++ b/src/filter/root_context.rs @@ -1,12 +1,12 @@ -use crate::configuration::{FilterConfig, PluginConfiguration}; +use crate::configuration::PluginConfiguration; use crate::filter::http_context::Filter; use crate::operation_dispatcher::OperationDispatcher; -use crate::service::{GrpcServiceHandler, HeaderResolver}; +use crate::runtime_config::RuntimeConfig; +use crate::service::HeaderResolver; use const_format::formatcp; use log::{debug, error, info}; use proxy_wasm::traits::{Context, HttpContext, RootContext}; use proxy_wasm::types::ContextType; -use std::collections::HashMap; use std::rc::Rc; const WASM_SHIM_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -17,7 +17,7 @@ const WASM_SHIM_HEADER: &str = "Kuadrant wasm module"; pub struct FilterRoot { pub context_id: u32, - pub config: Rc, + pub config: Rc, } impl RootContext for FilterRoot { @@ -35,24 +35,12 @@ impl RootContext for FilterRoot { fn create_http_context(&self, context_id: u32) -> Option> { debug!("#{} create_http_context", context_id); - let mut service_handlers: HashMap> = HashMap::new(); let header_resolver = Rc::new(HeaderResolver::new()); - self.config - .services - .iter() - .for_each(|(service_name, grpc_service)| { - service_handlers - .entry(service_name.clone()) - .or_insert(Rc::from(GrpcServiceHandler::new( - Rc::clone(grpc_service), - Rc::clone(&header_resolver), - ))); - }); Some(Box::new(Filter { context_id, config: Rc::clone(&self.config), response_headers_to_add: Vec::default(), - operation_dispatcher: OperationDispatcher::new(service_handlers).into(), + operation_dispatcher: OperationDispatcher::new(header_resolver).into(), })) } @@ -65,15 +53,15 @@ impl RootContext for FilterRoot { match serde_json::from_slice::(&configuration) { Ok(config) => { info!("plugin config parsed: {:?}", config); - let filter_config = - match >::try_into(config) { + let runtime_config = + match >::try_into(config) { Ok(cfg) => cfg, Err(err) => { error!("failed to compile plugin config: {}", err); return false; } }; - self.config = Rc::new(filter_config); + self.config = Rc::new(runtime_config); } Err(e) => { error!("failed to parse plugin config: {}", e); diff --git a/src/lib.rs b/src/lib.rs index ffaf17a..2062ebd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod action_set_index; +mod auth_action; mod configuration; mod data; #[allow(renamed_and_removed_lints)] @@ -5,6 +7,10 @@ mod envoy; mod filter; mod glob; mod operation_dispatcher; +mod ratelimit_action; +mod runtime_action; +mod runtime_action_set; +mod runtime_config; mod service; #[cfg(test)] diff --git a/src/operation_dispatcher.rs b/src/operation_dispatcher.rs index 3b7779c..784a7ff 100644 --- a/src/operation_dispatcher.rs +++ b/src/operation_dispatcher.rs @@ -1,7 +1,9 @@ -use crate::configuration::action::Action; -use crate::configuration::{FailureMode, Service, ServiceType}; +use crate::configuration::{FailureMode, ServiceType}; +use crate::runtime_action::RuntimeAction; use crate::service::grpc_message::GrpcMessageRequest; -use crate::service::{GetMapValuesBytesFn, GrpcCallFn, GrpcMessageBuildFn, GrpcServiceHandler}; +use crate::service::{ + GetMapValuesBytesFn, GrpcCallFn, GrpcMessageBuildFn, GrpcServiceHandler, HeaderResolver, +}; use log::{debug, error}; use proxy_wasm::hostcalls; use proxy_wasm::types::{Bytes, MapType, Status}; @@ -32,13 +34,12 @@ impl State { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub(crate) struct Operation { state: RefCell, result: RefCell>, - service: Rc, - action: Action, - service_handler: Rc, + action: Rc, + service_handler: GrpcServiceHandler, grpc_call_fn: GrpcCallFn, get_map_values_bytes_fn: GetMapValuesBytesFn, grpc_message_build_fn: GrpcMessageBuildFn, @@ -46,15 +47,10 @@ pub(crate) struct Operation { } impl Operation { - pub fn new( - service: Rc, - action: Action, - service_handler: Rc, - ) -> Self { + pub fn new(action: Rc, service_handler: GrpcServiceHandler) -> Self { Self { state: RefCell::new(State::Pending), result: RefCell::new(Ok(0)), // Heuristics: zero represents that it's not been triggered, following `hostcalls` example - service, action, service_handler, grpc_call_fn, @@ -65,12 +61,12 @@ impl Operation { } fn trigger(&self) -> Result { - if let Some(message) = (self.grpc_message_build_fn)(self.get_service_type(), &self.action) { + if let Some(message) = (self.grpc_message_build_fn)(&self.action) { let res = self.service_handler.send( self.get_map_values_bytes_fn, self.grpc_call_fn, message, - self.service.timeout.0, + self.action.get_timeout(), ); match res { Ok(token_id) => self.set_result(Ok(token_id)), @@ -106,14 +102,15 @@ impl Operation { *self.result.borrow_mut() = result; } - pub fn get_service_type(&self) -> &ServiceType { - &self.service.service_type + pub fn get_service_type(&self) -> ServiceType { + self.action.get_service_type() } pub fn get_failure_mode(&self) -> FailureMode { - self.service.failure_mode + self.action.get_failure_mode() } } + #[derive(Copy, Clone, Debug, PartialEq)] pub struct OperationError { pub status: Status, @@ -145,15 +142,15 @@ impl fmt::Display for OperationError { pub struct OperationDispatcher { operations: Vec>, waiting_operations: HashMap>, - service_handlers: HashMap>, + header_resolver: Rc, } impl OperationDispatcher { - pub fn new(service_handlers: HashMap>) -> Self { + pub fn new(header_resolver: Rc) -> Self { Self { - service_handlers, operations: vec![], waiting_operations: HashMap::new(), + header_resolver: Rc::clone(&header_resolver), } } @@ -171,25 +168,18 @@ impl OperationDispatcher { } } - pub fn build_operations(&mut self, actions: &[Action]) -> Result<(), OperationError> { + pub fn build_operations(&mut self, actions: &[Rc]) { let mut operations: Vec> = vec![]; for action in actions.iter() { - if let Some(service) = self.service_handlers.get(&action.service) { - operations.push(Rc::new(Operation::new( - service.get_service(), - action.clone(), - Rc::clone(service), - ))) - } else { - error!("Unknown service: {}", action.service); - return Err(OperationError::new( - Status::ParseFailure, - Default::default(), - )); - } + operations.push(Rc::new(Operation::new( + Rc::clone(action), + GrpcServiceHandler::new( + Rc::clone(&action.grpc_service()), + Rc::clone(&self.header_resolver), + ), + ))); } self.push_operations(operations); - Ok(()) } pub fn push_operations(&mut self, operations: Vec>) { @@ -249,9 +239,10 @@ impl OperationDispatcher { OperationDispatcher { operations: vec![], waiting_operations: HashMap::default(), - service_handlers: HashMap::default(), + header_resolver: Rc::new(HeaderResolver::default()), } } + #[cfg(test)] pub fn get_current_operation_state(&self) -> Option { self.operations @@ -282,26 +273,25 @@ fn get_map_values_bytes_fn(map_type: MapType, key: &str) -> Result hostcalls::get_map_value_bytes(map_type, key) } -fn grpc_message_build_fn( - extension_type: &ServiceType, - action: &Action, -) -> Option { - GrpcMessageRequest::new(extension_type, action) +fn grpc_message_build_fn(action: &RuntimeAction) -> Option { + GrpcMessageRequest::new(action) } -type ConditionsApplyFn = fn(action: &Action) -> bool; +type ConditionsApplyFn = fn(action: &RuntimeAction) -> bool; -fn conditions_apply_fn(action: &Action) -> bool { +fn conditions_apply_fn(action: &RuntimeAction) -> bool { action.conditions_apply() } #[cfg(test)] mod tests { use super::*; - use crate::configuration::Timeout; + use crate::auth_action::AuthAction; + use crate::configuration::{Action, Service, Timeout}; use crate::envoy::RateLimitRequest; + use crate::ratelimit_action::RateLimitAction; use protobuf::RepeatedField; - use std::cell::OnceCell; + use std::rc::Rc; use std::time::Duration; fn default_grpc_call_fn_stub( @@ -322,18 +312,15 @@ mod tests { Ok(Some(Vec::new())) } - fn grpc_message_build_fn_stub( - _extension_type: &ServiceType, - _action: &Action, - ) -> Option { + fn grpc_message_build_fn_stub(_action: &RuntimeAction) -> Option { Some(GrpcMessageRequest::RateLimit(build_message())) } fn build_grpc_service_handler() -> GrpcServiceHandler { - GrpcServiceHandler::new(Rc::new(Default::default()), Rc::new(Default::default())) + GrpcServiceHandler::new(Rc::new(Default::default()), Default::default()) } - fn conditions_apply_fn_stub(_action: &Action) -> bool { + fn conditions_apply_fn_stub(_action: &RuntimeAction) -> bool { true } @@ -347,27 +334,48 @@ mod tests { } } - fn build_operation( - grpc_call_fn_stub: GrpcCallFn, - extension_type: ServiceType, - ) -> Rc { + fn build_auth_grpc_action() -> RuntimeAction { + let service = Service { + service_type: ServiceType::Auth, + endpoint: "local".to_string(), + failure_mode: FailureMode::Deny, + timeout: Timeout(Duration::from_millis(42)), + }; + let action = Action { + service: "local".to_string(), + scope: "".to_string(), + predicates: vec![], + data: vec![], + }; + RuntimeAction::Auth( + AuthAction::new(&action, &service).expect("empty predicates should compile!"), + ) + } + + fn build_rate_limit_grpc_action() -> RuntimeAction { + let service = Service { + service_type: ServiceType::RateLimit, + endpoint: "local".to_string(), + failure_mode: FailureMode::Deny, + timeout: Timeout(Duration::from_millis(42)), + }; + let action = Action { + service: "local".to_string(), + scope: "".to_string(), + predicates: vec![], + data: vec![], + }; + RuntimeAction::RateLimit( + RateLimitAction::new(&action, &service).expect("empty predicates should compile!"), + ) + } + + fn build_operation(grpc_call_fn_stub: GrpcCallFn, action: RuntimeAction) -> Rc { Rc::new(Operation { state: RefCell::from(State::Pending), result: RefCell::new(Ok(0)), - service: Rc::new(Service { - service_type: extension_type, - endpoint: "local".to_string(), - failure_mode: FailureMode::Deny, - timeout: Timeout(Duration::from_millis(42)), - }), - action: Action { - service: "local".to_string(), - scope: "".to_string(), - predicates: vec![], - compiled_predicates: OnceCell::default(), - data: vec![], - }, - service_handler: Rc::new(build_grpc_service_handler()), + action: Rc::new(action), + service_handler: build_grpc_service_handler(), grpc_call_fn: grpc_call_fn_stub, get_map_values_bytes_fn: get_map_values_bytes_fn_stub, grpc_message_build_fn: grpc_message_build_fn_stub, @@ -377,17 +385,17 @@ mod tests { #[test] fn operation_getters() { - let operation = build_operation(default_grpc_call_fn_stub, ServiceType::RateLimit); + let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action()); assert_eq!(operation.get_state(), State::Pending); - assert_eq!(*operation.get_service_type(), ServiceType::RateLimit); + assert_eq!(operation.get_service_type(), ServiceType::RateLimit); assert_eq!(operation.get_failure_mode(), FailureMode::Deny); assert_eq!(operation.get_result(), Ok(0)); } #[test] fn operation_transition() { - let operation = build_operation(default_grpc_call_fn_stub, ServiceType::RateLimit); + let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action()); assert_eq!(operation.get_result(), Ok(0)); assert_eq!(operation.get_state(), State::Pending); let mut res = operation.trigger(); @@ -404,10 +412,8 @@ mod tests { let mut operation_dispatcher = OperationDispatcher::default(); assert_eq!(operation_dispatcher.operations.len(), 0); - operation_dispatcher.push_operations(vec![build_operation( - default_grpc_call_fn_stub, - ServiceType::RateLimit, - )]); + let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action()); + operation_dispatcher.push_operations(vec![operation]); assert_eq!(operation_dispatcher.operations.len(), 1); } @@ -415,10 +421,8 @@ mod tests { #[test] fn operation_dispatcher_get_current_action_state() { let mut operation_dispatcher = OperationDispatcher::default(); - operation_dispatcher.push_operations(vec![build_operation( - default_grpc_call_fn_stub, - ServiceType::RateLimit, - )]); + let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action()); + operation_dispatcher.push_operations(vec![operation]); assert_eq!( operation_dispatcher.get_current_operation_state(), Some(State::Pending) @@ -452,8 +456,8 @@ mod tests { } operation_dispatcher.push_operations(vec![ - build_operation(grpc_call_fn_stub_66, ServiceType::RateLimit), - build_operation(grpc_call_fn_stub_77, ServiceType::Auth), + build_operation(grpc_call_fn_stub_66, build_rate_limit_grpc_action()), + build_operation(grpc_call_fn_stub_77, build_auth_grpc_action()), ]); assert_eq!( @@ -471,7 +475,7 @@ mod tests { Ok(66) ); assert_eq!( - *op.clone() + op.clone() .expect("ok result") .expect("operation is some") .get_service_type(), @@ -509,7 +513,7 @@ mod tests { Ok(77) ); assert_eq!( - *op.clone() + op.clone() .expect("ok result") .expect("operation is some") .get_service_type(), diff --git a/src/ratelimit_action.rs b/src/ratelimit_action.rs new file mode 100644 index 0000000..62aef21 --- /dev/null +++ b/src/ratelimit_action.rs @@ -0,0 +1,249 @@ +use crate::configuration::{Action, DataType, FailureMode, Service}; +use crate::data::Expression; +use crate::data::Predicate; +use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; +use crate::service::GrpcService; +use cel_interpreter::Value; +use log::error; +use protobuf::RepeatedField; +use std::rc::Rc; + +#[derive(Debug)] +struct DescriptorEntryBuilder { + pub key: String, + pub expression: Expression, +} + +impl DescriptorEntryBuilder { + pub fn new(data_type: &DataType) -> Result { + match data_type { + DataType::Static(static_item) => Ok(DescriptorEntryBuilder { + key: static_item.key.clone(), + expression: Expression::new(format!("'{}'", static_item.value).as_str()) + .map_err(|e| e.to_string())?, + }), + DataType::Expression(exp_item) => Ok(DescriptorEntryBuilder { + key: exp_item.key.clone(), + expression: Expression::new(&exp_item.value).map_err(|e| e.to_string())?, + }), + } + } + + pub fn evaluate(&self) -> RateLimitDescriptor_Entry { + let (key, value) = ( + self.key.clone(), + match self.expression.eval() { + Ok(value) => match value { + Value::Int(n) => format!("{n}"), + Value::UInt(n) => format!("{n}"), + Value::Float(n) => format!("{n}"), + // todo this probably should be a proper string literal! + Value::String(s) => (*s).clone(), + Value::Bool(b) => format!("{b}"), + Value::Null => "null".to_owned(), + _ => panic!("Only scalar values can be sent as data"), + }, + Err(err) => { + error!("Failed to evaluate {:?}: {}", self.expression, err); + panic!("Err out of this!") + } + }, + ); + let mut descriptor_entry = RateLimitDescriptor_Entry::new(); + descriptor_entry.set_key(key); + descriptor_entry.set_value(value); + descriptor_entry + } +} + +#[derive(Debug)] +struct ConditionalData { + pub data: Vec, + pub predicates: Vec, +} + +impl ConditionalData { + pub fn new(action: &Action) -> Result { + let mut predicates = Vec::default(); + for predicate in &action.predicates { + predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?); + } + + let mut data = Vec::default(); + for datum in &action.data { + data.push(DescriptorEntryBuilder::new(&datum.item)?); + } + Ok(ConditionalData { data, predicates }) + } + + fn predicates_apply(&self) -> bool { + let predicates = &self.predicates; + predicates.is_empty() + || predicates.iter().all(|predicate| match predicate.test() { + Ok(b) => b, + Err(err) => { + error!("Failed to evaluate {:?}: {}", predicates, err); + panic!("Err out of this!") + } + }) + } + + pub fn entries(&self) -> RepeatedField { + if !self.predicates_apply() { + return RepeatedField::default(); + } + + let mut entries = RepeatedField::default(); + for entry_builder in self.data.iter() { + entries.push(entry_builder.evaluate()); + } + + entries + } +} + +#[derive(Debug)] +pub struct RateLimitAction { + grpc_service: Rc, + scope: String, + conditional_data_sets: Vec, +} + +impl RateLimitAction { + pub fn new(action: &Action, service: &Service) -> Result { + Ok(Self { + grpc_service: Rc::new(GrpcService::new(Rc::new(service.clone()))), + scope: action.scope.clone(), + conditional_data_sets: vec![ConditionalData::new(action)?], + }) + } + + pub fn build_descriptor(&self) -> RateLimitDescriptor { + let mut entries = RepeatedField::default(); + + for conditional_data in self.conditional_data_sets.iter() { + entries.extend(conditional_data.entries()); + } + + let mut res = RateLimitDescriptor::new(); + res.set_entries(entries); + res + } + + pub fn get_grpcservice(&self) -> Rc { + Rc::clone(&self.grpc_service) + } + + pub fn scope(&self) -> &str { + self.scope.as_str() + } + + pub fn conditions_apply(&self) -> bool { + // For RateLimitAction conditions always apply. + // It is when building the descriptor that it may be empty because predicates do not + // evaluate to true. + true + } + + pub fn get_failure_mode(&self) -> FailureMode { + self.grpc_service.get_failure_mode() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::configuration::{ + Action, DataItem, DataType, ExpressionItem, FailureMode, Service, ServiceType, StaticItem, + Timeout, + }; + + fn build_service() -> Service { + Service { + service_type: ServiceType::RateLimit, + endpoint: "some_endpoint".into(), + failure_mode: FailureMode::default(), + timeout: Timeout::default(), + } + } + + fn build_action(predicates: Vec, data: Vec) -> Action { + Action { + service: "some_service".into(), + scope: "some_scope".into(), + predicates, + data, + } + } + + #[test] + fn empty_predicates_do_apply() { + let action = build_action(Vec::default(), Vec::default()); + let service = build_service(); + let rl_action = RateLimitAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?"); + assert!(rl_action.conditions_apply()); + } + + #[test] + fn empty_data_generates_empty_descriptor() { + let action = build_action(Vec::default(), Vec::default()); + let service = build_service(); + let rl_action = RateLimitAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?"); + assert_eq!(rl_action.build_descriptor(), RateLimitDescriptor::default()); + } + + #[test] + fn descriptor_entry_from_expression() { + let data = vec![DataItem { + item: DataType::Expression(ExpressionItem { + key: "key_1".into(), + value: "'value_1'".into(), + }), + }]; + let action = build_action(Vec::default(), data); + let service = build_service(); + let rl_action = RateLimitAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?"); + let descriptor = rl_action.build_descriptor(); + assert_eq!(descriptor.get_entries().len(), 1); + assert_eq!(descriptor.get_entries()[0].key, String::from("key_1")); + assert_eq!(descriptor.get_entries()[0].value, String::from("value_1")); + } + + #[test] + fn descriptor_entry_from_static() { + let data = vec![DataItem { + item: DataType::Static(StaticItem { + key: "key_1".into(), + value: "value_1".into(), + }), + }]; + let action = build_action(Vec::default(), data); + let service = build_service(); + let rl_action = RateLimitAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?"); + let descriptor = rl_action.build_descriptor(); + assert_eq!(descriptor.get_entries().len(), 1); + assert_eq!(descriptor.get_entries()[0].key, String::from("key_1")); + assert_eq!(descriptor.get_entries()[0].value, String::from("value_1")); + } + + #[test] + fn descriptor_entries_not_generated_when_predicates_evaluate_to_false() { + let data = vec![DataItem { + item: DataType::Expression(ExpressionItem { + key: "key_1".into(), + value: "'value_1'".into(), + }), + }]; + + let predicates = vec!["false".into(), "true".into()]; + let action = build_action(predicates, data); + let service = build_service(); + let rl_action = RateLimitAction::new(&action, &service) + .expect("action building failed. Maybe predicates compilation?"); + assert_eq!(rl_action.build_descriptor(), RateLimitDescriptor::default()); + } +} diff --git a/src/runtime_action.rs b/src/runtime_action.rs new file mode 100644 index 0000000..fa14b19 --- /dev/null +++ b/src/runtime_action.rs @@ -0,0 +1,55 @@ +use crate::auth_action::AuthAction; +use crate::configuration::{Action, FailureMode, Service, ServiceType}; +use crate::ratelimit_action::RateLimitAction; +use crate::service::GrpcService; +use std::collections::HashMap; +use std::rc::Rc; +use std::time::Duration; + +#[derive(Debug)] +pub enum RuntimeAction { + Auth(AuthAction), + RateLimit(RateLimitAction), +} + +impl RuntimeAction { + pub fn new(action: &Action, services: &HashMap) -> Result { + let service = services + .get(&action.service) + .ok_or(format!("Unknown service: {}", action.service))?; + + match service.service_type { + ServiceType::RateLimit => Ok(Self::RateLimit(RateLimitAction::new(action, service)?)), + ServiceType::Auth => Ok(Self::Auth(AuthAction::new(action, service)?)), + } + } + + pub fn grpc_service(&self) -> Rc { + match self { + Self::Auth(auth_action) => auth_action.get_grpcservice(), + Self::RateLimit(rl_action) => rl_action.get_grpcservice(), + } + } + + pub fn conditions_apply(&self) -> bool { + match self { + Self::Auth(auth_action) => auth_action.conditions_apply(), + Self::RateLimit(rl_action) => rl_action.conditions_apply(), + } + } + + pub fn get_failure_mode(&self) -> FailureMode { + match self { + Self::Auth(auth_action) => auth_action.get_failure_mode(), + Self::RateLimit(rl_action) => rl_action.get_failure_mode(), + } + } + + pub fn get_timeout(&self) -> Duration { + self.grpc_service().get_timeout() + } + + pub fn get_service_type(&self) -> ServiceType { + self.grpc_service().get_service_type() + } +} diff --git a/src/runtime_action_set.rs b/src/runtime_action_set.rs new file mode 100644 index 0000000..03dbb03 --- /dev/null +++ b/src/runtime_action_set.rs @@ -0,0 +1,67 @@ +use crate::configuration::{ActionSet, Service}; +use crate::data::Predicate; +use crate::runtime_action::RuntimeAction; +use log::error; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct RuntimeActionSet { + pub name: String, + pub route_rule_predicates: Vec, + pub runtime_actions: Vec>, +} + +impl RuntimeActionSet { + pub fn new( + action_set: &ActionSet, + services: &HashMap, + ) -> Result { + // route predicates + let mut route_rule_predicates = Vec::default(); + for predicate in &action_set.route_rule_conditions.predicates { + route_rule_predicates + .push(Predicate::route_rule(predicate).map_err(|e| e.to_string())?); + } + + // actions + let mut runtime_actions = Vec::default(); + for action in &action_set.actions { + runtime_actions.push(Rc::new(RuntimeAction::new(action, services)?)); + } + + Ok(Self { + name: action_set.name.clone(), + route_rule_predicates, + runtime_actions, + }) + } + + pub fn conditions_apply(&self) -> bool { + let predicates = &self.route_rule_predicates; + predicates.is_empty() + || predicates.iter().all(|predicate| match predicate.test() { + Ok(b) => b, + Err(err) => { + error!("Failed to evaluate {:?}: {}", predicate, err); + panic!("Err out of this!") + } + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::configuration::ActionSet; + + #[test] + fn empty_route_rule_predicates_do_apply() { + let action_set = ActionSet::new("some_name".to_owned(), Default::default(), Vec::new()); + + let runtime_action_set = RuntimeActionSet::new(&action_set, &HashMap::default()) + .expect("should not happen from an empty set of actions"); + + assert!(runtime_action_set.conditions_apply()) + } +} diff --git a/src/runtime_config.rs b/src/runtime_config.rs new file mode 100644 index 0000000..25c63cb --- /dev/null +++ b/src/runtime_config.rs @@ -0,0 +1,157 @@ +use crate::action_set_index::ActionSetIndex; +use crate::configuration::PluginConfiguration; +use crate::runtime_action_set::RuntimeActionSet; +use std::rc::Rc; + +pub(crate) struct RuntimeConfig { + pub index: ActionSetIndex, +} + +impl TryFrom for RuntimeConfig { + type Error = String; + + fn try_from(config: PluginConfiguration) -> Result { + let mut index = ActionSetIndex::new(); + for action_set in config.action_sets.iter() { + let runtime_action_set = Rc::new(RuntimeActionSet::new(action_set, &config.services)?); + for hostname in action_set.route_rule_conditions.hostnames.iter() { + index.insert(hostname, Rc::clone(&runtime_action_set)); + } + } + + Ok(Self { index }) + } +} + +impl Default for RuntimeConfig { + fn default() -> Self { + Self { + index: ActionSetIndex::new(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + const CONFIG: &str = r#"{ + "services": { + "authorino": { + "type": "auth", + "endpoint": "authorino-cluster", + "failureMode": "deny", + "timeout": "24ms" + }, + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "allow", + "timeout": "42ms" + } + }, + "actionSets": [ + { + "name": "rlp-ns-A/rlp-name-A", + "routeRuleConditions": { + "hostnames": ["*.toystore.com", "example.com"], + "predicates": [ + "request.path == '/admin/toy'", + "request.method == 'POST'", + "request.host == 'cars.toystore.com'" + ] + }, + "actions": [ + { + "service": "authorino", + "scope": "authconfig-A" + }, + { + "service": "limitador", + "scope": "rlp-ns-A/rlp-name-A", + "predicates": [ + "auth.metadata.username == 'alice'" + ], + "data": [ + { + "static": { + "key": "rlp-ns-A/rlp-name-A", + "value": "1" + } + }, + { + "expression": { + "key": "username", + "value": "auth.metadata.username" + } + }] + }] + }] + }"#; + + #[test] + fn runtime_config_from_configuration() { + let res = serde_json::from_str::(CONFIG); + if let Err(ref e) = res { + eprintln!("{e}"); + } + assert!(res.is_ok()); + + let result = RuntimeConfig::try_from(res.unwrap()); + let runtime_config = result.expect("That didn't work"); + let rlp_option = runtime_config + .index + .get_longest_match_action_sets("example.com"); + assert!(rlp_option.is_some()); + + let rlp_option = runtime_config + .index + .get_longest_match_action_sets("test.toystore.com"); + assert!(rlp_option.is_some()); + + let rlp_option = runtime_config + .index + .get_longest_match_action_sets("unknown"); + assert!(rlp_option.is_none()); + } + + #[test] + fn runtime_config_raises_error_when_action_service_does_not_exist_in_services() { + let config = r#"{ + "services": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador", + "failureMode": "allow" + } + }, + "actionSets": [ + { + "name": "some-name", + "routeRuleConditions": { + "hostnames": ["*.example.com"] + }, + "actions": [ + { + "service": "unknown", + "scope": "some-scope", + "data": [ + { + "expression": { + "key": "a", + "value": "1" + } + }] + }] + }] + }"#; + let serde_res = serde_json::from_str::(config); + if let Err(ref e) = serde_res { + eprintln!("{e}"); + } + assert!(serde_res.is_ok()); + + let result = RuntimeConfig::try_from(serde_res.expect("That didn't work")); + assert_eq!(result.err(), Some("Unknown service: unknown".into())); + } +} diff --git a/src/service.rs b/src/service.rs index a981add..461ecc2 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,10 +2,10 @@ pub(crate) mod auth; pub(crate) mod grpc_message; pub(crate) mod rate_limit; -use crate::configuration::action::Action; use crate::configuration::{FailureMode, Service, ServiceType}; use crate::envoy::StatusCode; use crate::operation_dispatcher::Operation; +use crate::runtime_action::RuntimeAction; use crate::service::auth::{AuthService, AUTH_METHOD_NAME, AUTH_SERVICE_NAME}; use crate::service::grpc_message::{GrpcMessageRequest, GrpcMessageResponse}; use crate::service::rate_limit::{RateLimitService, RATELIMIT_METHOD_NAME, RATELIMIT_SERVICE_NAME}; @@ -42,6 +42,18 @@ impl GrpcService { } } + pub fn get_timeout(&self) -> Duration { + self.service.timeout.0 + } + + pub fn get_service_type(&self) -> ServiceType { + self.service.service_type.clone() + } + + pub fn get_failure_mode(&self) -> FailureMode { + self.service.failure_mode + } + fn endpoint(&self) -> &str { &self.service.endpoint } @@ -60,7 +72,7 @@ impl GrpcService { if let Ok(Some(res_body_bytes)) = hostcalls::get_buffer(BufferType::GrpcReceiveBuffer, 0, resp_size) { - match GrpcMessageResponse::new(operation.get_service_type(), &res_body_bytes) { + match GrpcMessageResponse::new(&operation.get_service_type(), &res_body_bytes) { Ok(res) => match operation.get_service_type() { ServiceType::Auth => AuthService::process_auth_grpc_response(res, failure_mode), ServiceType::RateLimit => { @@ -120,8 +132,8 @@ pub type GrpcCallFn = fn( pub type GetMapValuesBytesFn = fn(map_type: MapType, key: &str) -> Result, Status>; -pub type GrpcMessageBuildFn = - fn(service_type: &ServiceType, action: &Action) -> Option; +pub type GrpcMessageBuildFn = fn(action: &RuntimeAction) -> Option; + #[derive(Debug)] pub struct GrpcServiceHandler { grpc_service: Rc, @@ -163,11 +175,8 @@ impl GrpcServiceHandler { timeout, ) } - - pub fn get_service(&self) -> Rc { - Rc::clone(&self.grpc_service.service) - } } + #[derive(Debug)] pub struct HeaderResolver { headers: OnceCell>, diff --git a/src/service/grpc_message.rs b/src/service/grpc_message.rs index 68daffd..0014831 100644 --- a/src/service/grpc_message.rs +++ b/src/service/grpc_message.rs @@ -1,6 +1,6 @@ -use crate::configuration::action::Action; use crate::configuration::ServiceType; use crate::envoy::{CheckRequest, CheckResponse, RateLimitRequest, RateLimitResponse}; +use crate::runtime_action::RuntimeAction; use crate::service::auth::AuthService; use crate::service::rate_limit::RateLimitService; use log::debug; @@ -124,22 +124,25 @@ impl Message for GrpcMessageRequest { impl GrpcMessageRequest { // Using domain as ce_host for the time being, we might pass a DataType in the future. - pub fn new(service_type: &ServiceType, action: &Action) -> Option { - match service_type { - ServiceType::RateLimit => { - let descriptors = action.build_descriptors(); - if descriptors.is_empty() { + pub fn new(action: &RuntimeAction) -> Option { + match action { + RuntimeAction::RateLimit(rl_action) => { + let descriptor = rl_action.build_descriptor(); + if descriptor.entries.is_empty() { debug!("grpc_message_request: empty descriptors"); None } else { Some(GrpcMessageRequest::RateLimit( - RateLimitService::request_message(action.scope.clone(), descriptors), + RateLimitService::request_message( + String::from(rl_action.scope()), + vec![descriptor].into(), + ), )) } } - ServiceType::Auth => Some(GrpcMessageRequest::Auth(AuthService::request_message( - action.scope.clone(), - ))), + RuntimeAction::Auth(auth_action) => Some(GrpcMessageRequest::Auth( + AuthService::request_message(String::from(auth_action.scope())), + )), } } } diff --git a/tests/auth.rs b/tests/auth.rs index 38b6081..ea1cfe6 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -182,10 +182,10 @@ fn it_auths() { 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8, 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84, 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, - 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7, - 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97, - 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, - 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, + 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, + 58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, + 113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, + 104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 34, 10, 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, @@ -379,10 +379,10 @@ fn it_denies() { 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8, 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84, 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, - 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7, - 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97, - 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, - 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, + 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, + 58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, + 113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, + 104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 34, 10, 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, diff --git a/tests/multi.rs b/tests/multi.rs index bc5b6e4..22d1a0e 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -198,15 +198,15 @@ fn it_performs_authenticated_rate_limiting() { 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8, 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84, - 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, - 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7, - 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97, - 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, - 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, - 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, - 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, - 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, - 117, 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10, + 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101, + 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116, + 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101, + 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47, + 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, + 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, + 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, + 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, ]), Some(5000), ) @@ -412,15 +412,15 @@ fn unauthenticated_does_not_ratelimit() { 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8, 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84, - 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, - 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7, - 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97, - 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, - 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, - 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, - 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, - 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, - 117, 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10, + 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101, + 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116, + 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101, + 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47, + 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, + 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, + 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, + 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, ]), Some(5000), ) @@ -686,14 +686,14 @@ fn authenticated_one_ratelimit_action_matches() { 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8, 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84, - 26, 38, 10, 5, 58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, - 47, 114, 101, 113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, - 112, 97, 116, 104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, - 84, 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, - 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 34, 10, 47, 97, - 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, 121, - 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, 72, - 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, + 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10, + 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101, + 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116, + 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101, + 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47, + 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, + 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, + 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, ]), Some(5000), @@ -728,7 +728,7 @@ fn authenticated_one_ratelimit_action_matches() { .returning(Some("1.2.3.4:80".as_bytes())) .expect_log( Some(LogLevel::Debug), - Some("actions conditions do not apply, skipping"), + Some("grpc_message_request: empty descriptors"), ) .expect_log( Some(LogLevel::Debug), diff --git a/tests/rate_limited.rs b/tests/rate_limited.rs index b88daa6..bf6321b 100644 --- a/tests/rate_limited.rs +++ b/tests/rate_limited.rs @@ -577,7 +577,7 @@ fn it_does_not_rate_limits_when_predicates_does_not_match() { .returning(Some(data::request::path::ADMIN)) .expect_log( Some(LogLevel::Debug), - Some("actions conditions do not apply, skipping"), + Some("grpc_message_request: empty descriptors"), ) .execute_and_expect(ReturnType::Action(Action::Continue)) .unwrap();