diff --git a/src/configuration.rs b/src/configuration.rs index 9732a6d0..7b5201f2 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,10 +1,11 @@ -use crate::glob::GlobPattern; use crate::policy_index::PolicyIndex; -use cel_interpreter::Expression; -use log::warn; +use cel_interpreter::objects::ValueType; +use cel_interpreter::{Context, Expression, Value}; +use cel_parser::{Atom, RelationOp}; use serde::Deserialize; use std::cell::OnceCell; use std::fmt::{Display, Formatter}; +use std::sync::Arc; #[derive(Deserialize, Debug, Clone)] pub struct SelectorItem { @@ -131,28 +132,6 @@ pub enum WhenConditionOperator { Matches, } -impl WhenConditionOperator { - pub fn eval(&self, value: &str, attr_value: &str) -> bool { - match *self { - WhenConditionOperator::Equal => value.eq(attr_value), - WhenConditionOperator::NotEqual => !value.eq(attr_value), - WhenConditionOperator::StartsWith => attr_value.starts_with(value), - WhenConditionOperator::EndsWith => attr_value.ends_with(value), - WhenConditionOperator::Matches => match GlobPattern::try_from(value) { - // TODO(eastizle): regexp being compiled and validated at request time. - // Validations and possibly regexp compilation should happen at boot time instead. - // In addition, if the regexp is not valid, the only consequence is that - // the current condition would not apply - Ok(glob_pattern) => glob_pattern.is_match(attr_value), - Err(e) => { - warn!("failed to parse regexp: {value}, error: {e:?}"); - false - } - }, - } - } -} - #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct PatternExpression { @@ -185,6 +164,161 @@ impl PatternExpression { .get() .expect("PatternExpression wasn't previously compiled!") } + + pub fn eval(&self, attribute_value: String) -> bool { + let cel_type = type_of(&self.selector); + let value = match cel_type { + ValueType::String => Value::String(attribute_value.into()), + ValueType::Int | ValueType::UInt => Value::Int(attribute_value.parse::().unwrap()), + _ => unimplemented!("Need support for {}", cel_type), + }; + let mut ctx = Context::default(); + ctx.add_variable("attribute", value).unwrap(); + Value::resolve(self.compiled.get().unwrap(), &ctx) + .map(|v| { + if let Value::Bool(result) = v { + result + } else { + false + } + }) + .unwrap_or(false) + } +} + +impl TryFrom<&PatternExpression> for Expression { + type Error = (); + + fn try_from(expression: &PatternExpression) -> Result { + let cel_type = type_of(&expression.selector); + + let value = match cel_type { + ValueType::Map => match expression.operator { + WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => { + match cel_parser::parse(&expression.value) { + Ok(exp) => { + if let Expression::Map(data) = exp { + Ok(Expression::Map(data)) + } else { + Err(()) + } + } + Err(_) => Err(()), + } + } + _ => Err(()), + }, + ValueType::Int | ValueType::UInt => match expression.operator { + WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => { + match cel_parser::parse(&expression.value) { + Ok(exp) => { + if let Expression::Atom(atom) = &exp { + match atom { + Atom::Int(_) | Atom::UInt(_) | Atom::Float(_) => Ok(exp), + _ => Err(()), + } + } else { + Err(()) + } + } + Err(_) => Err(()), + } + } + _ => Err(()), + }, + ValueType::String => match expression.operator { + WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => Ok( + Expression::Atom(Atom::String(Arc::new(expression.value.clone()))), + ), + // WhenConditionOperator::Matches => {} + _ => Ok(Expression::Atom(Atom::String(Arc::new( + expression.value.clone(), + )))), + }, + // ValueType::Bytes => {} + // ValueType::Bool => {} + // ValueType::Timestamp => {} + _ => todo!("Still needs support for values of type `{cel_type}`"), + }?; + + match expression.operator { + WhenConditionOperator::Equal => Ok(Expression::Relation( + Expression::Ident(Arc::new("attribute".to_string())).into(), + RelationOp::Equals, + value.into(), + )), + WhenConditionOperator::NotEqual => Ok(Expression::Relation( + Expression::Ident(Arc::new("attribute".to_string())).into(), + RelationOp::NotEquals, + value.into(), + )), + WhenConditionOperator::StartsWith => Ok(Expression::FunctionCall( + Expression::Ident(Arc::new("startsWith".to_string())).into(), + Some(Expression::Ident("attribute".to_string().into()).into()), + [value].to_vec(), + )), + WhenConditionOperator::EndsWith => Ok(Expression::FunctionCall( + Expression::Ident(Arc::new("endsWith".to_string())).into(), + Some(Expression::Ident("attribute".to_string().into()).into()), + [value].to_vec(), + )), + WhenConditionOperator::Matches => Ok(Expression::FunctionCall( + Expression::Ident(Arc::new("matches".to_string())).into(), + Some(Expression::Ident("attribute".to_string().into()).into()), + [value].to_vec(), + )), + } + } +} + +pub fn type_of(path: &str) -> ValueType { + match path { + "request.time" => ValueType::Timestamp, + "request.id" => ValueType::String, + "request.protocol" => ValueType::String, + "request.scheme" => ValueType::String, + "request.host" => ValueType::String, + "request.method" => ValueType::String, + "request.path" => ValueType::String, + "request.url_path" => ValueType::String, + "request.query" => ValueType::String, + "request.referer" => ValueType::String, + "request.useragent" => ValueType::String, + "request.body" => ValueType::String, + "source.address" => ValueType::String, + "source.service" => ValueType::String, + "source.principal" => ValueType::String, + "source.certificate" => ValueType::String, + "destination.address" => ValueType::String, + "destination.service" => ValueType::String, + "destination.principal" => ValueType::String, + "destination.certificate" => ValueType::String, + "connection.requested_server_name" => ValueType::String, + "connection.tls_session.sni" => ValueType::String, + "connection.tls_version" => ValueType::String, + "connection.subject_local_certificate" => ValueType::String, + "connection.subject_peer_certificate" => ValueType::String, + "connection.dns_san_local_certificate" => ValueType::String, + "connection.dns_san_peer_certificate" => ValueType::String, + "connection.uri_san_local_certificate" => ValueType::String, + "connection.uri_san_peer_certificate" => ValueType::String, + "connection.sha256_peer_certificate_digest" => ValueType::String, + "ratelimit.domain" => ValueType::String, + "request.size" => ValueType::Int, + "source.port" => ValueType::Int, + "destination.port" => ValueType::Int, + "connection.id" => ValueType::Int, + "ratelimit.hits_addend" => ValueType::Int, + "request.headers" => ValueType::Map, + "request.context_extensions" => ValueType::Map, + "source.labels" => ValueType::Map, + "destination.labels" => ValueType::Map, + "filter_state" => ValueType::Map, + "connection.mtls" => ValueType::Bool, + "request.raw_body" => ValueType::Bytes, + "auth.identity" => ValueType::Bytes, + _ => ValueType::Bytes, + } } #[derive(Deserialize, Debug, Clone)] diff --git a/src/envoy/mod.rs b/src/envoy/mod.rs index 73ac0d4d..68e18d60 100644 --- a/src/envoy/mod.rs +++ b/src/envoy/mod.rs @@ -30,8 +30,6 @@ mod timestamp; mod token_bucket; mod value; -pub mod properties; - pub use { ratelimit::{RateLimitDescriptor, RateLimitDescriptor_Entry}, rls::{RateLimitRequest, RateLimitResponse, RateLimitResponse_Code}, diff --git a/src/filter.rs b/src/filter.rs index 703826be..b7f94e3c 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,5 +1,3 @@ -use crate::envoy::properties::EnvoyTypeMapper; - mod http_context; mod root_context; @@ -25,7 +23,6 @@ extern "C" fn start() { use proxy_wasm::traits::RootContext; use proxy_wasm::types::LogLevel; use root_context::FilterRoot; - use std::rc::Rc; proxy_wasm::set_log_level(LogLevel::Trace); std::panic::set_hook(Box::new(|panic_info| { @@ -36,7 +33,6 @@ extern "C" fn start() { Box::new(FilterRoot { context_id, config: Default::default(), - property_mapper: Rc::new(EnvoyTypeMapper::new()), }) }); } diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index f9988348..864def12 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -2,7 +2,6 @@ use crate::configuration::{ Condition, DataItem, DataType, FailureMode, FilterConfig, PatternExpression, RateLimitPolicy, Rule, }; -use crate::envoy::properties::EnvoyTypeMapper; use crate::envoy::{ RateLimitDescriptor, RateLimitDescriptor_Entry, RateLimitRequest, RateLimitResponse, RateLimitResponse_Code, @@ -43,7 +42,6 @@ pub struct Filter { pub context_id: u32, pub config: Rc, pub response_headers_to_add: Vec<(String, String)>, - pub property_mapper: Rc, pub tracing_headers: Vec<(TracingHeader, Bytes)>, } @@ -136,32 +134,28 @@ impl Filter { fn pattern_expression_applies(&self, p_e: &PatternExpression) -> bool { let attribute_path = p_e.path(); - // 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 => { + let attribute_value = match self.get_property(attribute_path) { + None => { + debug!( + "[context_id: {}]: 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!( - "[context_id: {}]: selector not found: {}, defaulting to ``", - self.context_id, p_e.selector + "[context_id: {}]: failed to parse selector value: {}, error: {}", + self.context_id, p_e.selector, e ); - "".to_string() + return false; } - // 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!( - "[context_id: {}]: 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()) + Ok(attribute_value) => attribute_value, + }, + }; + p_e.eval(attribute_value) } fn build_single_descriptor(&self, data_list: &[DataItem]) -> Option { diff --git a/src/filter/root_context.rs b/src/filter/root_context.rs index 8d6bd25d..859b129b 100644 --- a/src/filter/root_context.rs +++ b/src/filter/root_context.rs @@ -1,5 +1,4 @@ use crate::configuration::{FilterConfig, PluginConfiguration}; -use crate::envoy::properties::EnvoyTypeMapper; use crate::filter::http_context::Filter; use const_format::formatcp; use log::{info, warn}; @@ -16,7 +15,6 @@ const WASM_SHIM_HEADER: &str = "Kuadrant wasm module"; pub struct FilterRoot { pub context_id: u32, pub config: Rc, - pub property_mapper: Rc, } impl RootContext for FilterRoot { @@ -42,7 +40,6 @@ impl RootContext for FilterRoot { context_id, config: Rc::clone(&self.config), response_headers_to_add: Vec::default(), - property_mapper: Rc::clone(&self.property_mapper), tracing_headers: Vec::default(), })) } diff --git a/src/lib.rs b/src/lib.rs index 7c19f8a5..2cde111f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ mod envoy; mod filter; mod glob; mod policy_index; -mod typing; #[cfg(test)] mod tests { diff --git a/src/typing.rs b/src/typing.rs index cea896e1..e69de29b 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,229 +0,0 @@ -use crate::configuration::{PatternExpression, WhenConditionOperator}; -use cel_interpreter::objects::{ValueType as CelValueType, ValueType}; -use cel_parser::{Atom, Expression, RelationOp}; -use chrono::{DateTime, Utc}; -use std::fmt::Write; -use std::ops::Add; -use std::sync::Arc; -use std::time::{Duration, SystemTime}; - -pub enum TypedProperty { - String(String), - Integer(i64), - Timestamp(SystemTime), - Bytes(Vec), -} - -impl TypedProperty { - pub fn string(bytes: Vec) -> Self { - match String::from_utf8(bytes) { - Ok(string) => TypedProperty::String(string), - Err(err) => TypedProperty::bytes(err.into_bytes()), - } - } - - pub fn integer(bytes: Vec) -> Self { - if bytes.len() == 8 { - TypedProperty::Integer(i64::from_le_bytes( - bytes[..8].try_into().expect("This has to be 8 bytes long!"), - )) - } else { - TypedProperty::bytes(bytes) - } - } - - pub fn timestamp(bytes: Vec) -> Self { - if bytes.len() == 8 { - TypedProperty::Timestamp(SystemTime::UNIX_EPOCH.add(Duration::from_nanos( - u64::from_le_bytes(bytes[..8].try_into().expect("This has to be 8 bytes long!")), - ))) - } else { - TypedProperty::bytes(bytes) - } - } - - pub fn string_map(bytes: Vec) -> Self { - TypedProperty::Bytes(bytes.to_vec()) - } - - pub fn boolean(bytes: Vec) -> Self { - TypedProperty::Bytes(bytes.to_vec()) - } - - pub fn bytes(bytes: Vec) -> Self { - TypedProperty::Bytes(bytes) - } -} - -impl TypedProperty { - pub fn as_literal(&self) -> String { - match self { - TypedProperty::String(str) => { - format!("\"{}\"", str.replace('\\', "\\\\").replace('"', "\\\"")) - } - TypedProperty::Integer(int) => int.to_string(), - TypedProperty::Bytes(bytes) => { - let len = 5 * bytes.len(); - let mut str = String::with_capacity(len + 1); - write!(str, "[").unwrap(); - for byte in bytes { - write!(str, "\\x{:02x},", byte).unwrap(); - } - str.replace_range(len..len + 1, "]"); - str - } - TypedProperty::Timestamp(ts) => { - let ts: DateTime = (*ts).into(); - ts.to_rfc3339() - } - } - } -} - -impl PartialEq for TypedProperty { - fn eq(&self, other: &str) -> bool { - match self { - TypedProperty::String(str) => str == other, - TypedProperty::Integer(int) => int.to_string().as_str() == other, - _ => self.as_literal() == other, - } - } -} - -impl TryFrom<&PatternExpression> for Expression { - type Error = (); - - fn try_from(expression: &PatternExpression) -> Result { - let cel_type = type_of(&expression.selector); - - let value = match cel_type { - ValueType::Map => match expression.operator { - WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => { - match cel_parser::parse(&expression.value) { - Ok(exp) => { - if let Expression::Map(data) = exp { - Ok(Expression::Map(data)) - } else { - Err(()) - } - } - Err(_) => Err(()), - } - } - _ => Err(()), - }, - ValueType::Int | ValueType::UInt => match expression.operator { - WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => { - match cel_parser::parse(&expression.value) { - Ok(exp) => { - if let Expression::Atom(atom) = &exp { - match atom { - Atom::Int(_) | Atom::UInt(_) | Atom::Float(_) => Ok(exp), - _ => Err(()), - } - } else { - Err(()) - } - } - Err(_) => Err(()), - } - } - _ => Err(()), - }, - ValueType::String => match expression.operator { - WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => { - Ok(Expression::Atom(Atom::String(Arc::new(expression.value.clone())))) - } - // WhenConditionOperator::Matches => {} - _ => Ok(Expression::Atom(Atom::String( - Arc::new(expression.value.clone()), - ))), - }, - // ValueType::Bytes => {} - // ValueType::Bool => {} - // ValueType::Timestamp => {} - _ => todo!("Still needs support for values of type `{cel_type}`"), - }?; - - match expression.operator { - WhenConditionOperator::Equal => Ok(Expression::Relation(Expression::Ident(Arc::new("attribute".to_string())).into(), RelationOp::Equals, value.into())), - WhenConditionOperator::NotEqual => Ok(Expression::Relation(Expression::Ident(Arc::new("attribute".to_string())).into(), RelationOp::NotEquals, value.into())), - WhenConditionOperator::StartsWith => Ok(Expression::FunctionCall(Expression::Ident(Arc::new("startsWith".to_string())).into(), Some(Expression::Ident("attribute".to_string().into()).into()), [value].to_vec())), - WhenConditionOperator::EndsWith => Ok(Expression::FunctionCall(Expression::Ident(Arc::new("endsWith".to_string())).into(), Some(Expression::Ident("attribute".to_string().into()).into()), [value].to_vec())), - WhenConditionOperator::Matches => Ok(Expression::FunctionCall(Expression::Ident(Arc::new("matches".to_string())).into(), Some(Expression::Ident("attribute".to_string().into()).into()), [value].to_vec())), - } - } -} - -fn type_of(path: &str) -> CelValueType { - match path { - "request.time" => CelValueType::Timestamp, - "request.id" => CelValueType::String, - "request.protocol" => CelValueType::String, - "request.scheme" => CelValueType::String, - "request.host" => CelValueType::String, - "request.method" => CelValueType::String, - "request.path" => CelValueType::String, - "request.url_path" => CelValueType::String, - "request.query" => CelValueType::String, - "request.referer" => CelValueType::String, - "request.useragent" => CelValueType::String, - "request.body" => CelValueType::String, - "source.address" => CelValueType::String, - "source.service" => CelValueType::String, - "source.principal" => CelValueType::String, - "source.certificate" => CelValueType::String, - "destination.address" => CelValueType::String, - "destination.service" => CelValueType::String, - "destination.principal" => CelValueType::String, - "destination.certificate" => CelValueType::String, - "connection.requested_server_name" => CelValueType::String, - "connection.tls_session.sni" => CelValueType::String, - "connection.tls_version" => CelValueType::String, - "connection.subject_local_certificate" => CelValueType::String, - "connection.subject_peer_certificate" => CelValueType::String, - "connection.dns_san_local_certificate" => CelValueType::String, - "connection.dns_san_peer_certificate" => CelValueType::String, - "connection.uri_san_local_certificate" => CelValueType::String, - "connection.uri_san_peer_certificate" => CelValueType::String, - "connection.sha256_peer_certificate_digest" => CelValueType::String, - "ratelimit.domain" => CelValueType::String, - "request.size" => CelValueType::Int, - "source.port" => CelValueType::Int, - "destination.port" => CelValueType::Int, - "connection.id" => CelValueType::Int, - "ratelimit.hits_addend" => CelValueType::Int, - "request.headers" => CelValueType::Map, - "request.context_extensions" => CelValueType::Map, - "source.labels" => CelValueType::Map, - "destination.labels" => CelValueType::Map, - "filter_state" => CelValueType::Map, - "connection.mtls" => CelValueType::Bool, - "request.raw_body" => CelValueType::Bytes, - "auth.identity" => CelValueType::Bytes, - _ => CelValueType::Bytes, - } -} - -#[cfg(test)] -mod tests { - use crate::typing::TypedProperty; - - #[test] - fn literal_bytes() { - let prop = TypedProperty::bytes(vec![0xb2, 0x42, 0x01]); - assert_eq!("[\\xb2,\\x42,\\x01]", prop.as_literal()); - } - - #[test] - fn literal_strings() { - let prop = TypedProperty::String("Foobar".to_string()); - assert_eq!("\"Foobar\"", prop.as_literal()); - let prop = TypedProperty::String("Foo\\bar".to_string()); - assert_eq!("\"Foo\\\\bar\"", prop.as_literal()); - let prop = TypedProperty::String("Foo\"bar\"".to_string()); - assert_eq!("\"Foo\\\"bar\\\"\"", prop.as_literal()); - let prop = TypedProperty::String("\\Foo\"bar\"".to_string()); - assert_eq!("\"\\\\Foo\\\"bar\\\"\"", prop.as_literal()); - } -}