From 271b464b1f4cc2c3f27eff8f9b62c579f5e0431c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sat, 19 Oct 2024 07:27:59 -0400 Subject: [PATCH 01/26] Refactored Attribute to data::AttributeValue Signed-off-by: Alex Snaps --- src/configuration.rs | 16 ++++++++-------- src/configuration/action.rs | 4 ++-- src/{ => data}/attribute.rs | 28 ++++++++++++++-------------- src/data/mod.rs | 5 +++++ src/filter/http_context.rs | 2 +- src/lib.rs | 2 +- src/service/auth.rs | 2 +- 7 files changed, 32 insertions(+), 27 deletions(-) rename src/{ => data}/attribute.rs (95%) create mode 100644 src/data/mod.rs diff --git a/src/configuration.rs b/src/configuration.rs index fb239820..d5328045 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -4,7 +4,7 @@ use std::fmt::{Debug, Formatter}; use std::rc::Rc; use std::sync::Arc; -use crate::attribute::Attribute; +use crate::data::AttributeValue; use crate::configuration::action_set::ActionSet; use crate::configuration::action_set_index::ActionSetIndex; use crate::property_path::Path; @@ -131,13 +131,13 @@ impl PatternExpression { pub fn eval(&self, raw_attribute: Vec) -> Result { let cel_type = &self.compiled.get().unwrap().cel_type; let value = match cel_type { - ValueType::String => Value::String(Arc::new(Attribute::parse(raw_attribute)?)), - ValueType::Int => Value::Int(Attribute::parse(raw_attribute)?), - ValueType::UInt => Value::UInt(Attribute::parse(raw_attribute)?), - ValueType::Float => Value::Float(Attribute::parse(raw_attribute)?), - ValueType::Bytes => Value::Bytes(Arc::new(Attribute::parse(raw_attribute)?)), - ValueType::Bool => Value::Bool(Attribute::parse(raw_attribute)?), - ValueType::Timestamp => Value::Timestamp(Attribute::parse(raw_attribute)?), + ValueType::String => Value::String(Arc::new(AttributeValue::parse(raw_attribute)?)), + ValueType::Int => Value::Int(AttributeValue::parse(raw_attribute)?), + ValueType::UInt => Value::UInt(AttributeValue::parse(raw_attribute)?), + ValueType::Float => Value::Float(AttributeValue::parse(raw_attribute)?), + ValueType::Bytes => Value::Bytes(Arc::new(AttributeValue::parse(raw_attribute)?)), + ValueType::Bool => Value::Bool(AttributeValue::parse(raw_attribute)?), + ValueType::Timestamp => Value::Timestamp(AttributeValue::parse(raw_attribute)?), // todo: Impl support for parsing these two types… Tho List/Map of what? // ValueType::List => {} // ValueType::Map => {} diff --git a/src/configuration/action.rs b/src/configuration/action.rs index 32f538d9..39094ad8 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,4 +1,4 @@ -use crate::attribute::Attribute; +use crate::data::AttributeValue; use crate::configuration::{DataItem, DataType, PatternExpression}; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; use log::debug; @@ -64,7 +64,7 @@ impl Action { } // TODO(eastizle): not all fields are strings // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - Some(attribute_bytes) => match Attribute::parse(attribute_bytes) { + Some(attribute_bytes) => match AttributeValue::parse(attribute_bytes) { Ok(attr_str) => attr_str, Err(e) => { debug!("build_single_descriptor: failed to parse selector value: {}, error: {}", diff --git a/src/attribute.rs b/src/data/attribute.rs similarity index 95% rename from src/attribute.rs rename to src/data/attribute.rs index 130af5f0..b246c572 100644 --- a/src/attribute.rs +++ b/src/data/attribute.rs @@ -1,18 +1,18 @@ -use crate::property_path::Path; -use chrono::{DateTime, FixedOffset}; +use proxy_wasm::hostcalls; use log::{debug, error}; use protobuf::well_known_types::Struct; -use proxy_wasm::hostcalls; +use chrono::{DateTime, FixedOffset}; +use crate::property_path::Path; pub const KUADRANT_NAMESPACE: &str = "kuadrant"; -pub trait Attribute { +pub trait AttributeValue { fn parse(raw_attribute: Vec) -> Result where Self: Sized; } -impl Attribute for String { +impl AttributeValue for String { fn parse(raw_attribute: Vec) -> Result { String::from_utf8(raw_attribute).map_err(|err| { format!( @@ -23,7 +23,7 @@ impl Attribute for String { } } -impl Attribute for i64 { +impl AttributeValue for i64 { fn parse(raw_attribute: Vec) -> Result { if raw_attribute.len() != 8 { return Err(format!( @@ -39,7 +39,7 @@ impl Attribute for i64 { } } -impl Attribute for u64 { +impl AttributeValue for u64 { fn parse(raw_attribute: Vec) -> Result { if raw_attribute.len() != 8 { return Err(format!( @@ -55,7 +55,7 @@ impl Attribute for u64 { } } -impl Attribute for f64 { +impl AttributeValue for f64 { fn parse(raw_attribute: Vec) -> Result { if raw_attribute.len() != 8 { return Err(format!( @@ -71,13 +71,13 @@ impl Attribute for f64 { } } -impl Attribute for Vec { +impl AttributeValue for Vec { fn parse(raw_attribute: Vec) -> Result { Ok(raw_attribute) } } -impl Attribute for bool { +impl AttributeValue for bool { fn parse(raw_attribute: Vec) -> Result { if raw_attribute.len() != 1 { return Err(format!( @@ -89,7 +89,7 @@ impl Attribute for bool { } } -impl Attribute for DateTime { +impl AttributeValue for DateTime { fn parse(raw_attribute: Vec) -> Result { if raw_attribute.len() != 8 { return Err(format!( @@ -109,7 +109,7 @@ impl Attribute for DateTime { pub fn get_attribute(attr: &str) -> Result where - T: Attribute, + T: AttributeValue, { match crate::property::get_property(Path::from(attr).tokens()) { Ok(Some(attribute_bytes)) => T::parse(attribute_bytes), @@ -155,7 +155,7 @@ fn process_metadata(s: &Struct, prefix: String) -> Vec<(String, String)> { #[cfg(test)] mod tests { - use crate::attribute::process_metadata; + use crate::data::attribute::process_metadata; use protobuf::well_known_types::{Struct, Value, Value_oneof_kind}; use std::collections::HashMap; @@ -238,4 +238,4 @@ mod tests { assert!(output.contains(&("identity\\.userid".to_string(), "bob".to_string()))); assert!(output.contains(&("other_data".to_string(), "other_value".to_string()))); } -} +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 00000000..1680acbb --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,5 @@ +mod attribute; + +pub use attribute::AttributeValue; +pub use attribute::get_attribute; +pub use attribute::store_metadata; diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index a4ab0aec..9b425c93 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -29,7 +29,7 @@ impl Filter { } } - #[allow(clippy::manual_inspect)] + #[allow(unknown_lints, clippy::manual_inspect)] fn process_action_sets(&self, action_sets: &[Rc]) -> Action { if let Some(action_set) = action_sets .iter() diff --git a/src/lib.rs b/src/lib.rs index f4ef7822..e03e2b47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -mod attribute; mod configuration; +mod data; #[allow(renamed_and_removed_lints)] mod envoy; mod filter; diff --git a/src/service/auth.rs b/src/service/auth.rs index 7a177f43..e2a07fe7 100644 --- a/src/service/auth.rs +++ b/src/service/auth.rs @@ -1,4 +1,3 @@ -use crate::attribute::{get_attribute, store_metadata}; use crate::configuration::FailureMode; use crate::envoy::{ Address, AttributeContext, AttributeContext_HttpRequest, AttributeContext_Peer, @@ -14,6 +13,7 @@ use protobuf::Message; use proxy_wasm::hostcalls; use proxy_wasm::types::{Bytes, MapType}; use std::collections::HashMap; +use crate::data::{get_attribute, store_metadata}; pub const AUTH_SERVICE_NAME: &str = "envoy.service.auth.v3.Authorization"; pub const AUTH_METHOD_NAME: &str = "Check"; From 762091d281928f6176c52796d0698e81541916dc Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sat, 19 Oct 2024 07:31:23 -0400 Subject: [PATCH 02/26] Refactored property stuff to data::property Signed-off-by: Alex Snaps --- src/configuration.rs | 12 +++---- src/configuration/action.rs | 6 ++-- src/data/attribute.rs | 10 +++--- src/data/mod.rs | 6 +++- src/{property_path.rs => data/property.rs} | 42 ++++++++++++++++++++-- src/lib.rs | 2 -- src/property.rs | 38 -------------------- src/service/auth.rs | 2 +- 8 files changed, 58 insertions(+), 60 deletions(-) rename src/{property_path.rs => data/property.rs} (60%) delete mode 100644 src/property.rs diff --git a/src/configuration.rs b/src/configuration.rs index d5328045..937da986 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -4,10 +4,10 @@ use std::fmt::{Debug, Formatter}; use std::rc::Rc; use std::sync::Arc; -use crate::data::AttributeValue; use crate::configuration::action_set::ActionSet; use crate::configuration::action_set_index::ActionSetIndex; -use crate::property_path::Path; +use crate::data::AttributeValue; +use crate::data::PropertyPath; use crate::service::GrpcService; use cel_interpreter::functions::duration; use cel_interpreter::objects::ValueType; @@ -38,7 +38,7 @@ pub struct SelectorItem { pub default: Option, #[serde(skip_deserializing)] - path: OnceCell, + path: OnceCell, } impl SelectorItem { @@ -48,7 +48,7 @@ impl SelectorItem { .map_err(|p| format!("Err on {p:?}")) } - pub fn path(&self) -> &Path { + pub fn path(&self) -> &PropertyPath { self.path .get() .expect("SelectorItem wasn't previously compiled!") @@ -107,7 +107,7 @@ pub struct PatternExpression { pub value: String, #[serde(skip_deserializing)] - path: OnceCell, + path: OnceCell, #[serde(skip_deserializing)] compiled: OnceCell, } @@ -158,7 +158,7 @@ impl PatternExpression { fn applies(&self) -> bool { let attribute_path = self.path(); - let attribute_value = match crate::property::get_property(attribute_path).unwrap() { + let attribute_value = match crate::data::get_property(attribute_path).unwrap() { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( diff --git a/src/configuration/action.rs b/src/configuration/action.rs index 39094ad8..0388cf53 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,5 +1,5 @@ -use crate::data::AttributeValue; use crate::configuration::{DataItem, DataType, PatternExpression}; +use crate::data::AttributeValue; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; use log::debug; use protobuf::RepeatedField; @@ -48,9 +48,7 @@ impl Action { }; let attribute_path = selector_item.path(); - let value = match crate::property::get_property(attribute_path.tokens()) - .unwrap() - { + let value = match crate::data::get_property(attribute_path.tokens()).unwrap() { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( diff --git a/src/data/attribute.rs b/src/data/attribute.rs index b246c572..e6aeef92 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -1,8 +1,8 @@ -use proxy_wasm::hostcalls; +use crate::data::property::Path; +use chrono::{DateTime, FixedOffset}; use log::{debug, error}; use protobuf::well_known_types::Struct; -use chrono::{DateTime, FixedOffset}; -use crate::property_path::Path; +use proxy_wasm::hostcalls; pub const KUADRANT_NAMESPACE: &str = "kuadrant"; @@ -111,7 +111,7 @@ pub fn get_attribute(attr: &str) -> Result where T: AttributeValue, { - match crate::property::get_property(Path::from(attr).tokens()) { + match crate::data::property::get_property(Path::from(attr).tokens()) { Ok(Some(attribute_bytes)) => T::parse(attribute_bytes), Ok(None) => Err(format!("get_attribute: not found or null: {attr}")), Err(e) => Err(format!("get_attribute: error: {e:?}")), @@ -238,4 +238,4 @@ mod tests { assert!(output.contains(&("identity\\.userid".to_string(), "bob".to_string()))); assert!(output.contains(&("other_data".to_string(), "other_value".to_string()))); } -} \ No newline at end of file +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 1680acbb..7f693763 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,5 +1,9 @@ mod attribute; +mod property; -pub use attribute::AttributeValue; pub use attribute::get_attribute; pub use attribute::store_metadata; +pub use attribute::AttributeValue; + +pub use property::get_property; +pub use property::Path as PropertyPath; diff --git a/src/property_path.rs b/src/data/property.rs similarity index 60% rename from src/property_path.rs rename to src/data/property.rs index c2a5e049..7dd165ad 100644 --- a/src/property_path.rs +++ b/src/data/property.rs @@ -1,4 +1,41 @@ -use std::fmt::{Debug, Display, Formatter}; +use log::debug; +use log::warn; +use proxy_wasm::hostcalls; +use proxy_wasm::types::Status; +use std::fmt::{Display, Formatter}; + +fn remote_address() -> Result>, Status> { + // Ref https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for + // Envoy sets source.address to the trusted client address AND port. + match host_get_property(Path::from("source.address").tokens())? { + None => { + warn!("source.address property not found"); + Err(Status::BadArgument) + } + Some(host_vec) => match String::from_utf8(host_vec) { + Err(e) => { + warn!("source.address property value not string: {}", e); + Err(Status::BadArgument) + } + Ok(source_address) => { + let split_address = source_address.split(':').collect::>(); + Ok(Some(split_address[0].as_bytes().to_vec())) + } + }, + } +} + +fn host_get_property(path: Vec<&str>) -> Result>, Status> { + debug!("get_property: path: {:?}", path); + hostcalls::get_property(path) +} + +pub fn get_property(path: Vec<&str>) -> Result>, Status> { + match path[..] { + ["source", "remote_address"] => remote_address(), + _ => host_get_property(path), + } +} #[derive(Debug, Clone)] pub struct Path { @@ -52,8 +89,7 @@ impl Path { #[cfg(test)] mod test { - use super::*; - + use crate::data::property::Path; #[test] fn path_tokenizes_with_escaping_basic() { let path: Path = r"one\.two..three\\\\.four\\\.\five.".into(); diff --git a/src/lib.rs b/src/lib.rs index e03e2b47..9f065b3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,6 @@ mod envoy; mod filter; mod glob; mod operation_dispatcher; -mod property; -mod property_path; mod service; #[cfg(test)] diff --git a/src/property.rs b/src/property.rs deleted file mode 100644 index a063b0ef..00000000 --- a/src/property.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::property_path::Path; -use log::debug; -use log::warn; -use proxy_wasm::hostcalls; -use proxy_wasm::types::Status; - -fn remote_address() -> Result>, Status> { - // Ref https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for - // Envoy sets source.address to the trusted client address AND port. - match host_get_property(Path::from("source.address").tokens())? { - None => { - warn!("source.address property not found"); - Err(Status::BadArgument) - } - Some(host_vec) => match String::from_utf8(host_vec) { - Err(e) => { - warn!("source.address property value not string: {}", e); - Err(Status::BadArgument) - } - Ok(source_address) => { - let split_address = source_address.split(':').collect::>(); - Ok(Some(split_address[0].as_bytes().to_vec())) - } - }, - } -} - -fn host_get_property(path: Vec<&str>) -> Result>, Status> { - debug!("get_property: path: {:?}", path); - hostcalls::get_property(path) -} - -pub fn get_property(path: Vec<&str>) -> Result>, Status> { - match path[..] { - ["source", "remote_address"] => remote_address(), - _ => host_get_property(path), - } -} diff --git a/src/service/auth.rs b/src/service/auth.rs index e2a07fe7..71f3876a 100644 --- a/src/service/auth.rs +++ b/src/service/auth.rs @@ -1,4 +1,5 @@ use crate::configuration::FailureMode; +use crate::data::{get_attribute, store_metadata}; use crate::envoy::{ Address, AttributeContext, AttributeContext_HttpRequest, AttributeContext_Peer, AttributeContext_Request, CheckRequest, CheckResponse_oneof_http_response, Metadata, @@ -13,7 +14,6 @@ use protobuf::Message; use proxy_wasm::hostcalls; use proxy_wasm::types::{Bytes, MapType}; use std::collections::HashMap; -use crate::data::{get_attribute, store_metadata}; pub const AUTH_SERVICE_NAME: &str = "envoy.service.auth.v3.Authorization"; pub const AUTH_METHOD_NAME: &str = "Check"; From 78d5eb0d0e1d41a3a0a2e7f35e9d56f8de023b31 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sat, 19 Oct 2024 07:47:44 -0400 Subject: [PATCH 03/26] Refactored using PropertyPath everywhere Signed-off-by: Alex Snaps --- src/configuration.rs | 6 ++---- src/configuration/action.rs | 7 +++---- src/data/attribute.rs | 2 +- src/data/property.rs | 10 +++++----- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index 937da986..ba06ce77 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -121,11 +121,10 @@ impl PatternExpression { .set(self.try_into()?) .map_err(|_| "Ooops".to_string()) } - pub fn path(&self) -> Vec<&str> { + pub fn path(&self) -> &PropertyPath { self.path .get() .expect("PatternExpression wasn't previously compiled!") - .tokens() } pub fn eval(&self, raw_attribute: Vec) -> Result { @@ -157,8 +156,7 @@ impl PatternExpression { } fn applies(&self) -> bool { - let attribute_path = self.path(); - let attribute_value = match crate::data::get_property(attribute_path).unwrap() { + let attribute_value = match crate::data::get_property(self.path()).unwrap() { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( diff --git a/src/configuration/action.rs b/src/configuration/action.rs index 0388cf53..cec205fd 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -47,13 +47,12 @@ impl Action { Some(key) => key.to_owned(), }; - let attribute_path = selector_item.path(); - let value = match crate::data::get_property(attribute_path.tokens()).unwrap() { + let value = match crate::data::get_property(selector_item.path()).unwrap() { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( "build_single_descriptor: selector not found: {}", - attribute_path + selector_item.path() ); match &selector_item.default { None => return None, // skipping the entire descriptor @@ -66,7 +65,7 @@ impl Action { Ok(attr_str) => attr_str, Err(e) => { debug!("build_single_descriptor: failed to parse selector value: {}, error: {}", - attribute_path, e); + selector_item.path(), e); return None; } }, diff --git a/src/data/attribute.rs b/src/data/attribute.rs index e6aeef92..13620aec 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -111,7 +111,7 @@ pub fn get_attribute(attr: &str) -> Result where T: AttributeValue, { - match crate::data::property::get_property(Path::from(attr).tokens()) { + match crate::data::property::get_property(&attr.into()) { Ok(Some(attribute_bytes)) => T::parse(attribute_bytes), Ok(None) => Err(format!("get_attribute: not found or null: {attr}")), Err(e) => Err(format!("get_attribute: error: {e:?}")), diff --git a/src/data/property.rs b/src/data/property.rs index 7dd165ad..864e6690 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -7,7 +7,7 @@ use std::fmt::{Display, Formatter}; fn remote_address() -> Result>, Status> { // Ref https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for // Envoy sets source.address to the trusted client address AND port. - match host_get_property(Path::from("source.address").tokens())? { + match host_get_property(&"source.address".into())? { None => { warn!("source.address property not found"); Err(Status::BadArgument) @@ -25,13 +25,13 @@ fn remote_address() -> Result>, Status> { } } -fn host_get_property(path: Vec<&str>) -> Result>, Status> { +fn host_get_property(path: &Path) -> Result>, Status> { debug!("get_property: path: {:?}", path); - hostcalls::get_property(path) + hostcalls::get_property(path.tokens()) } -pub fn get_property(path: Vec<&str>) -> Result>, Status> { - match path[..] { +pub fn get_property(path: &Path) -> Result>, Status> { + match path.tokens()[..] { ["source", "remote_address"] => remote_address(), _ => host_get_property(path), } From 7624900f5c30aadc29c73ec164900d8af83d1678 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sat, 19 Oct 2024 07:54:21 -0400 Subject: [PATCH 04/26] Refactored using AttributeValue everywhere Signed-off-by: Alex Snaps --- src/configuration/action.rs | 14 +++----- src/data/attribute.rs | 9 ++--- src/service/auth.rs | 67 +++++++++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/configuration/action.rs b/src/configuration/action.rs index cec205fd..b6bfda8e 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,5 +1,4 @@ use crate::configuration::{DataItem, DataType, PatternExpression}; -use crate::data::AttributeValue; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; use log::debug; use protobuf::RepeatedField; @@ -47,7 +46,9 @@ impl Action { Some(key) => key.to_owned(), }; - let value = match crate::data::get_property(selector_item.path()).unwrap() { + let value = match crate::data::get_attribute::(selector_item.path()) + .expect("Error!") + { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( @@ -61,14 +62,7 @@ impl Action { } // TODO(eastizle): not all fields are strings // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - Some(attribute_bytes) => match AttributeValue::parse(attribute_bytes) { - Ok(attr_str) => attr_str, - Err(e) => { - debug!("build_single_descriptor: failed to parse selector value: {}, error: {}", - selector_item.path(), e); - return None; - } - }, + Some(attr_str) => attr_str, // Alternative implementation (for rust >= 1.76) // Attribute::parse(attribute_bytes) // .inspect_err(|e| debug!("#{} build_single_descriptor: failed to parse selector value: {}, error: {}", diff --git a/src/data/attribute.rs b/src/data/attribute.rs index 13620aec..6dafb681 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -1,4 +1,5 @@ use crate::data::property::Path; +use crate::data::PropertyPath; use chrono::{DateTime, FixedOffset}; use log::{debug, error}; use protobuf::well_known_types::Struct; @@ -107,13 +108,13 @@ impl AttributeValue for DateTime { } } -pub fn get_attribute(attr: &str) -> Result +pub fn get_attribute(path: &PropertyPath) -> Result, String> where T: AttributeValue, { - match crate::data::property::get_property(&attr.into()) { - Ok(Some(attribute_bytes)) => T::parse(attribute_bytes), - Ok(None) => Err(format!("get_attribute: not found or null: {attr}")), + match crate::data::property::get_property(path) { + Ok(Some(attribute_bytes)) => Ok(Some(T::parse(attribute_bytes)?)), + Ok(None) => Ok(None), Err(e) => Err(format!("get_attribute: error: {e:?}")), } } diff --git a/src/service/auth.rs b/src/service/auth.rs index 71f3876a..c8e4a4d2 100644 --- a/src/service/auth.rs +++ b/src/service/auth.rs @@ -37,12 +37,20 @@ impl AuthService { let mut attr = AttributeContext::default(); attr.set_request(AuthService::build_request()); attr.set_destination(AuthService::build_peer( - get_attribute::("destination.address").unwrap_or_default(), - get_attribute::("destination.port").unwrap_or_default() as u32, + get_attribute::(&"destination.address".into()) + .expect("Error!") + .unwrap_or_default(), + get_attribute::(&"destination.port".into()) + .expect("Error!") + .unwrap_or_default() as u32, )); attr.set_source(AuthService::build_peer( - get_attribute::("source.address").unwrap_or_default(), - get_attribute::("source.port").unwrap_or_default() as u32, + get_attribute::(&"source.address".into()) + .expect("Error!") + .unwrap_or_default(), + get_attribute::(&"source.port".into()) + .expect("Error!") + .unwrap_or_default() as u32, )); // the ce_host is the identifier for authorino to determine which authconfig to use let context_extensions = HashMap::from([("host".to_string(), ce_host)]); @@ -60,22 +68,45 @@ impl AuthService { .into_iter() .collect(); - http.set_host(get_attribute::("request.host").unwrap_or_default()); - http.set_method(get_attribute::("request.method").unwrap_or_default()); - http.set_scheme(get_attribute::("request.scheme").unwrap_or_default()); - http.set_path(get_attribute::("request.path").unwrap_or_default()); - http.set_protocol(get_attribute::("request.protocol").unwrap_or_default()); + http.set_host( + get_attribute::(&"request.host".into()) + .expect("Error!") + .unwrap_or_default(), + ); + http.set_method( + get_attribute::(&"request.method".into()) + .expect("Error!") + .unwrap_or_default(), + ); + http.set_scheme( + get_attribute::(&"request.scheme".into()) + .expect("Error!") + .unwrap_or_default(), + ); + http.set_path( + get_attribute::(&"request.path".into()) + .expect("Error!") + .unwrap_or_default(), + ); + http.set_protocol( + get_attribute::(&"request.protocol".into()) + .expect("Error!") + .unwrap_or_default(), + ); http.set_headers(headers); - request.set_time(get_attribute("request.time").map_or( - Timestamp::new(), - |date_time: DateTime| Timestamp { - nanos: date_time.nanosecond() as i32, - seconds: date_time.second() as i64, - unknown_fields: Default::default(), - cached_size: Default::default(), - }, - )); + request.set_time( + get_attribute(&"request.time".into()) + .expect("Error!") + .map_or(Timestamp::new(), |date_time: DateTime| { + Timestamp { + nanos: date_time.nanosecond() as i32, + seconds: date_time.second() as i64, + unknown_fields: Default::default(), + cached_size: Default::default(), + } + }), + ); request.set_http(http); request } From 54ad63235f69ad6673fa7c729a6d60eae6cdb13b Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sat, 19 Oct 2024 08:46:06 -0400 Subject: [PATCH 05/26] ports are u64, not strings... this err'ed into Default Signed-off-by: Alex Snaps --- src/data/property.rs | 12 +++++-- tests/auth.rs | 56 ++++++++++++++--------------- tests/multi.rs | 84 ++++++++++++++++++++++---------------------- 3 files changed, 79 insertions(+), 73 deletions(-) diff --git a/src/data/property.rs b/src/data/property.rs index 864e6690..ace6db17 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -2,7 +2,7 @@ use log::debug; use log::warn; use proxy_wasm::hostcalls; use proxy_wasm::types::Status; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; fn remote_address() -> Result>, Status> { // Ref https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for @@ -26,7 +26,7 @@ fn remote_address() -> Result>, Status> { } fn host_get_property(path: &Path) -> Result>, Status> { - debug!("get_property: path: {:?}", path); + debug!("get_property: {:?}", path); hostcalls::get_property(path.tokens()) } @@ -37,7 +37,7 @@ pub fn get_property(path: &Path) -> Result>, Status> { } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Path { tokens: Vec, } @@ -56,6 +56,12 @@ impl Display for Path { } } +impl Debug for Path { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "path: {:?}", self.tokens) + } +} + impl From<&str> for Path { fn from(value: &str) -> Self { let mut token = String::new(); diff --git a/tests/auth.rs b/tests/auth.rs index 93d1c318..910d1ce4 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -160,7 +160,7 @@ fn it_auths() { Some("get_property: path: [\"destination\", \"port\"]"), ) .expect_get_property(Some(vec!["destination", "port"])) - .returning(Some("8000".as_bytes())) + .returning(Some(&8000u64.to_le_bytes())) .expect_log( Some(LogLevel::Debug), Some("get_property: path: [\"source\", \"address\"]"), @@ -172,7 +172,7 @@ fn it_auths() { Some("get_property: path: [\"source\", \"port\"]"), ) .expect_get_property(Some(vec!["source", "port"])) - .returning(Some("45000".as_bytes())) + .returning(Some(&45000u64.to_le_bytes())) // retrieving tracing headers .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent")) .returning(None) @@ -186,18 +186,18 @@ fn it_auths() { Some("Check"), Some(&[0, 0, 0, 0]), Some(&[ - 10, 217, 1, 10, 23, 10, 21, 10, 19, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, - 52, 53, 48, 48, 48, 24, 0, 18, 22, 10, 20, 10, 18, 18, 14, 49, 50, 55, 46, 48, 46, - 48, 46, 49, 58, 56, 48, 48, 48, 24, 0, 34, 141, 1, 10, 0, 18, 136, 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, - 4, 72, 84, 84, 80, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, - 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 10, 220, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, + 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, 141, 1, 10, 0, 18, + 136, 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, 4, 72, 84, 84, 80, 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), ) @@ -355,7 +355,7 @@ fn it_denies() { Some("get_property: path: [\"destination\", \"port\"]"), ) .expect_get_property(Some(vec!["destination", "port"])) - .returning(Some("8000".as_bytes())) + .returning(Some(&8000u64.to_le_bytes())) .expect_log( Some(LogLevel::Debug), Some("get_property: path: [\"source\", \"address\"]"), @@ -367,7 +367,7 @@ fn it_denies() { Some("get_property: path: [\"source\", \"port\"]"), ) .expect_get_property(Some(vec!["source", "port"])) - .returning(Some("45000".as_bytes())) + .returning(Some(&45000u64.to_le_bytes())) // retrieving tracing headers .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent")) .returning(None) @@ -381,18 +381,18 @@ fn it_denies() { Some("Check"), Some(&[0, 0, 0, 0]), Some(&[ - 10, 217, 1, 10, 23, 10, 21, 10, 19, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, - 52, 53, 48, 48, 48, 24, 0, 18, 22, 10, 20, 10, 18, 18, 14, 49, 50, 55, 46, 48, 46, - 48, 46, 49, 58, 56, 48, 48, 48, 24, 0, 34, 141, 1, 10, 0, 18, 136, 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, - 4, 72, 84, 84, 80, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, - 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 10, 220, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, + 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, 141, 1, 10, 0, 18, + 136, 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, 4, 72, 84, 84, 80, 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), ) diff --git a/tests/multi.rs b/tests/multi.rs index 14785218..df419437 100644 --- a/tests/multi.rs +++ b/tests/multi.rs @@ -178,7 +178,7 @@ fn it_performs_authenticated_rate_limiting() { Some("get_property: path: [\"destination\", \"port\"]"), ) .expect_get_property(Some(vec!["destination", "port"])) - .returning(Some("8000".as_bytes())) + .returning(Some(&8000u64.to_le_bytes())) .expect_log( Some(LogLevel::Debug), Some("get_property: path: [\"source\", \"address\"]"), @@ -190,7 +190,7 @@ fn it_performs_authenticated_rate_limiting() { Some("get_property: path: [\"source\", \"port\"]"), ) .expect_get_property(Some(vec!["source", "port"])) - .returning(Some("45000".as_bytes())) + .returning(Some(&45000u64.to_le_bytes())) // retrieving tracing headers .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent")) .returning(None) @@ -204,18 +204,18 @@ fn it_performs_authenticated_rate_limiting() { Some("Check"), Some(&[0, 0, 0, 0]), Some(&[ - 10, 217, 1, 10, 23, 10, 21, 10, 19, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, - 52, 53, 48, 48, 48, 24, 0, 18, 22, 10, 20, 10, 18, 18, 14, 49, 50, 55, 46, 48, 46, - 48, 46, 49, 58, 56, 48, 48, 48, 24, 0, 34, 141, 1, 10, 0, 18, 136, 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, - 4, 72, 84, 84, 80, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, - 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 10, 220, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, + 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, 141, 1, 10, 0, 18, + 136, 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, 4, 72, 84, 84, 80, 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), ) @@ -391,7 +391,7 @@ fn unauthenticated_does_not_ratelimit() { Some("get_property: path: [\"destination\", \"port\"]"), ) .expect_get_property(Some(vec!["destination", "port"])) - .returning(Some("8000".as_bytes())) + .returning(Some(&8000u64.to_le_bytes())) .expect_log( Some(LogLevel::Debug), Some("get_property: path: [\"source\", \"address\"]"), @@ -403,7 +403,7 @@ fn unauthenticated_does_not_ratelimit() { Some("get_property: path: [\"source\", \"port\"]"), ) .expect_get_property(Some(vec!["source", "port"])) - .returning(Some("45000".as_bytes())) + .returning(Some(&45000u64.to_le_bytes())) // retrieving tracing headers .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent")) .returning(None) @@ -417,18 +417,18 @@ fn unauthenticated_does_not_ratelimit() { Some("Check"), Some(&[0, 0, 0, 0]), Some(&[ - 10, 217, 1, 10, 23, 10, 21, 10, 19, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, - 52, 53, 48, 48, 48, 24, 0, 18, 22, 10, 20, 10, 18, 18, 14, 49, 50, 55, 46, 48, 46, - 48, 46, 49, 58, 56, 48, 48, 48, 24, 0, 34, 141, 1, 10, 0, 18, 136, 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, - 4, 72, 84, 84, 80, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, - 99, 111, 110, 102, 105, 103, 45, 65, 90, 0, + 10, 220, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, + 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, 141, 1, 10, 0, 18, + 136, 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, 4, 72, 84, 84, 80, 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), ) @@ -681,7 +681,7 @@ fn authenticated_one_ratelimit_action_matches() { Some("get_property: path: [\"destination\", \"port\"]"), ) .expect_get_property(Some(vec!["destination", "port"])) - .returning(Some("8000".as_bytes())) + .returning(Some(&8000u64.to_le_bytes())) .expect_log( Some(LogLevel::Debug), Some("get_property: path: [\"source\", \"address\"]"), @@ -693,7 +693,7 @@ fn authenticated_one_ratelimit_action_matches() { Some("get_property: path: [\"source\", \"port\"]"), ) .expect_get_property(Some(vec!["source", "port"])) - .returning(Some("45000".as_bytes())) + .returning(Some(&45000u64.to_le_bytes())) // retrieving tracing headers .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent")) .returning(None) @@ -707,18 +707,18 @@ fn authenticated_one_ratelimit_action_matches() { Some("Check"), Some(&[0, 0, 0, 0]), Some(&[ - 10, 212, 1, 10, 18, 10, 16, 10, 14, 18, 10, 49, 46, 50, 46, 51, 46, 52, 58, 56, 48, - 24, 0, 18, 22, 10, 20, 10, 18, 18, 14, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 56, - 48, 48, 48, 24, 0, 34, 141, 1, 10, 0, 18, 136, 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, 4, 72, 84, 84, - 80, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117, 116, 104, 99, 111, 110, - 102, 105, 103, 45, 65, 90, 0, + 10, 215, 1, 10, 20, 10, 18, 10, 16, 18, 10, 49, 46, 50, 46, 51, 46, 52, 58, 56, 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, 141, 1, 10, 0, 18, 136, 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, + 4, 72, 84, 84, 80, 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), ) From eede19a20ea5cec69965e2160a87f879f0525a58 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sun, 20 Oct 2024 10:49:26 -0400 Subject: [PATCH 06/26] Slowly getting there Signed-off-by: Alex Snaps --- src/data/cel.rs | 265 +++++++++++++++++++++++++++++++++++++++++++ src/data/mod.rs | 7 ++ src/data/property.rs | 7 +- 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/data/cel.rs diff --git a/src/data/cel.rs b/src/data/cel.rs new file mode 100644 index 00000000..8fd305d9 --- /dev/null +++ b/src/data/cel.rs @@ -0,0 +1,265 @@ +use crate::data::get_attribute; +use crate::data::property::Path; +use cel_interpreter::objects::{Key, ValueType}; +use cel_interpreter::{Context, Value}; +use chrono::{DateTime, FixedOffset}; +use std::collections::HashMap; +use std::sync::OnceLock; +use cel_parser::{parse, Expression as CelExpression, ParseError, Member}; + +pub struct Expression { + attributes: Vec, + expression: CelExpression, +} + +impl Expression { + pub fn new(expression: &str) -> Result { + let expression = parse(expression)?; + + let mut props = Vec::with_capacity(5); + properties(&expression, &mut props, &mut Vec::default()); + + Ok(Self { + attributes: props.into_iter().map(|tokens| know_attribute_for(&Path::new(tokens)).expect("Unknown attribute")).collect(), + expression, + }) + } + + pub fn eval(&self) -> Value { + let _data = self.build_data_map(); + let ctx = Context::default(); + // ctx.add_variable_from_value::<_, Map>("request", data.remove("request").unwrap_or_default().into()); + // ctx.add_variable_from_value::<_, Value::Map>("metadata", data.remove("metadata").unwrap_or_default()); + // ctx.add_variable_from_value::<_, Value::Map>("source", data.remove("source").unwrap_or_default()); + // ctx.add_variable_from_value::<_, Value::Map>("destination", data.remove("destination").unwrap_or_default().into()); + // ctx.add_variable_from_value::<_, Value::Map>("auth", data.remove("auth").unwrap_or_default().into()); + Value::resolve(&self.expression, &ctx).expect("Cel expression couldn't be evaluated") + } + + fn build_data_map(&self) -> HashMap> { + HashMap::default() + } +} + +pub struct Predicate { + expression: Expression, +} + +impl Predicate { + pub fn test(&self) -> bool { + match self.expression.eval() { + Value::Bool(result) => result, + _ => false, + } + } +} + +pub struct Attribute { + path: Path, + cel_type: ValueType, +} + +impl Attribute { + pub fn get(&self) -> Value { + match self.cel_type { + ValueType::String => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(|v| Value::String(v.into())) + .unwrap_or(Value::Null), + ValueType::Int => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Int) + .unwrap_or(Value::Null), + ValueType::UInt => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::UInt) + .unwrap_or(Value::Null), + ValueType::Float => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Float) + .unwrap_or(Value::Null), + ValueType::Bool => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Bool) + .unwrap_or(Value::Null), + ValueType::Bytes => get_attribute::>(&self.path) + .expect("Failed getting to known attribute") + .map(|v| Value::Bytes(v.into())) + .unwrap_or(Value::Null), + ValueType::Timestamp => get_attribute::>(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Timestamp) + .unwrap_or(Value::Null), + _ => todo!("Need support for `{}`s!", self.cel_type), + } + } +} + +pub fn know_attribute_for(path: &Path) -> Option { + static WELL_KNOWN_ATTTRIBUTES: OnceLock> = OnceLock::new(); + WELL_KNOWN_ATTTRIBUTES + .get_or_init(new_well_known_attribute_map) + .get(path) + .map(|t| Attribute { + path: path.clone(), + cel_type: copy(t), + }) +} + +fn copy(value_type: &ValueType) -> ValueType { + match value_type { + ValueType::List => ValueType::List, + ValueType::Map => ValueType::Map, + ValueType::Function => ValueType::Function, + ValueType::Int => ValueType::Int, + ValueType::UInt => ValueType::UInt, + ValueType::Float => ValueType::Float, + ValueType::String => ValueType::String, + ValueType::Bytes => ValueType::Bytes, + ValueType::Bool => ValueType::Bool, + ValueType::Duration => ValueType::Duration, + ValueType::Timestamp => ValueType::Timestamp, + ValueType::Null => ValueType::Null, + } +} + +fn new_well_known_attribute_map() -> HashMap { + HashMap::from([ + ("request.time".into(), ValueType::String), + ("request.id".into(), ValueType::String), + ("request.protocol".into(), ValueType::String), + ("request.scheme".into(), ValueType::String), + ("request.host".into(), ValueType::String), + ("request.method".into(), ValueType::String), + ("request.path".into(), ValueType::String), + ("request.url_path".into(), ValueType::String), + ("request.query".into(), ValueType::String), + ("request.referer".into(), ValueType::String), + ("request.size".into(), ValueType::Int), + ("request.useragent".into(), ValueType::String), + ("request.body".into(), ValueType::String), + ("source.address".into(), ValueType::String), + ("source.remote_address".into(), ValueType::String), + ("source.port".into(), ValueType::Int), + ("source.service".into(), ValueType::String), + ("source.principal".into(), ValueType::String), + ("source.certificate".into(), ValueType::String), + ("destination.address".into(), ValueType::String), + ("destination.port".into(), ValueType::Int), + ("destination.service".into(), ValueType::String), + ("destination.principal".into(), ValueType::String), + ("destination.certificate".into(), ValueType::String), + ("connection.requested_server_name".into(), ValueType::String), + ("connection.tls_session.sni".into(), ValueType::String), + ("connection.tls_version".into(), ValueType::String), + ( + "connection.subject_local_certificate".into(), + ValueType::String, + ), + ( + "connection.subject_peer_certificate".into(), + ValueType::String, + ), + ( + "connection.dns_san_local_certificate".into(), + ValueType::String, + ), + ( + "connection.dns_san_peer_certificate".into(), + ValueType::String, + ), + ( + "connection.uri_san_local_certificate".into(), + ValueType::String, + ), + ( + "connection.uri_san_peer_certificate".into(), + ValueType::String, + ), + ( + "connection.sha256_peer_certificate_digest".into(), + ValueType::String, + ), + ("ratelimit.domain".into(), ValueType::String), + ("connection.id".into(), ValueType::Int), + ("ratelimit.hits_addend".into(), ValueType::Int), + ("request.headers".into(), ValueType::Map), + ("request.context_extensions".into(), ValueType::Map), + ("source.labels".into(), ValueType::Map), + ("destination.labels".into(), ValueType::Map), + ("filter_state".into(), ValueType::Map), + ("connection.mtls".into(), ValueType::Bool), + ("request.raw_body".into(), ValueType::Bytes), + ("auth.identity".into(), ValueType::Bytes), + ]) +} + +fn properties<'e>( + exp: &'e CelExpression, + all: &mut Vec>, + path: &mut Vec<&'e str>, +) { + match exp { + CelExpression::Arithmetic(e1, _, e2) + | CelExpression::Relation(e1, _, e2) + | CelExpression::Ternary(e1, _, e2) + | CelExpression::Or(e1, e2) + | CelExpression::And(e1, e2) => { + properties(e1, all, path); + properties(e2, all, path); + } + CelExpression::Unary(_, e) => { + properties(e, all, path); + } + CelExpression::Member(e, a) => { + if let Member::Attribute(attr) = &**a { + path.insert(0, attr.as_str()) + } + properties(e, all, path); + } + CelExpression::FunctionCall(_, target, args) => { + if let Some(target) = target { + properties(target, all, path); + } + for e in args { + properties(e, all, path); + } + } + CelExpression::List(e) => { + for e in e { + properties(e, all, path); + } + } + CelExpression::Map(v) => { + for (e1, e2) in v { + properties(e1, all, path); + properties(e2, all, path); + } + } + CelExpression::Atom(_) => {} + CelExpression::Ident(v) => { + if !path.is_empty() { + path.insert(0, v.as_str()); + all.push(path.clone()); + path.clear(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cel_interpreter::objects::ValueType; + + #[test] + fn finds_known_attributes() { + let path = "request.method".into(); + let attr = know_attribute_for(&path).expect("Must be a hit!"); + assert_eq!(attr.path, path); + match attr.cel_type { + ValueType::String => {} + _ => assert!(false), + } + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 7f693763..3fd26430 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,9 +1,16 @@ mod attribute; +#[allow(dead_code)] +mod cel; mod property; pub use attribute::get_attribute; pub use attribute::store_metadata; pub use attribute::AttributeValue; +#[allow(unused_imports)] +pub use cel::know_attribute_for; +#[allow(unused_imports)] +pub use cel::Attribute; + pub use property::get_property; pub use property::Path as PropertyPath; diff --git a/src/data/property.rs b/src/data/property.rs index ace6db17..55d43f57 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -37,7 +37,7 @@ pub fn get_property(path: &Path) -> Result>, Status> { } } -#[derive(Clone)] +#[derive(Clone, Hash, PartialEq, Eq)] pub struct Path { tokens: Vec, } @@ -88,6 +88,11 @@ impl From<&str> for Path { } impl Path { + pub fn new>(tokens: Vec) -> Self { + Self { + tokens: tokens.into_iter().map(|i| i.into()).collect(), + } + } pub fn tokens(&self) -> Vec<&str> { self.tokens.iter().map(String::as_str).collect() } From 222a7cf5bbe1b85eb2a32f5a6a292f9b2425425c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Sun, 20 Oct 2024 19:58:42 -0400 Subject: [PATCH 07/26] Wired the base Signed-off-by: Alex Snaps --- src/data/cel.rs | 181 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 18 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index 8fd305d9..dafd7616 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -1,11 +1,12 @@ use crate::data::get_attribute; use crate::data::property::Path; -use cel_interpreter::objects::{Key, ValueType}; +use cel_interpreter::objects::{Map, ValueType}; use cel_interpreter::{Context, Value}; +use cel_parser::{parse, Expression as CelExpression, Member, ParseError}; use chrono::{DateTime, FixedOffset}; use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; use std::sync::OnceLock; -use cel_parser::{parse, Expression as CelExpression, ParseError, Member}; pub struct Expression { attributes: Vec, @@ -19,25 +20,32 @@ impl Expression { let mut props = Vec::with_capacity(5); properties(&expression, &mut props, &mut Vec::default()); + let attributes = props + .into_iter() + .map(|tokens| know_attribute_for(&Path::new(tokens)).expect("Unknown attribute")) + .collect(); + Ok(Self { - attributes: props.into_iter().map(|tokens| know_attribute_for(&Path::new(tokens)).expect("Unknown attribute")).collect(), + attributes, expression, }) } pub fn eval(&self) -> Value { - let _data = self.build_data_map(); - let ctx = Context::default(); - // ctx.add_variable_from_value::<_, Map>("request", data.remove("request").unwrap_or_default().into()); - // ctx.add_variable_from_value::<_, Value::Map>("metadata", data.remove("metadata").unwrap_or_default()); - // ctx.add_variable_from_value::<_, Value::Map>("source", data.remove("source").unwrap_or_default()); - // ctx.add_variable_from_value::<_, Value::Map>("destination", data.remove("destination").unwrap_or_default().into()); - // ctx.add_variable_from_value::<_, Value::Map>("auth", data.remove("auth").unwrap_or_default().into()); + let mut ctx = Context::default(); + let Map { map } = self.build_data_map(); + + for binding in ["request", "metadata", "source", "destination", "auth"] { + ctx.add_variable_from_value( + binding, + map.get(&binding.into()).cloned().unwrap_or(Value::Null), + ); + } Value::resolve(&self.expression, &ctx).expect("Cel expression couldn't be evaluated") } - fn build_data_map(&self) -> HashMap> { - HashMap::default() + fn build_data_map(&self) -> Map { + data::AttributeMap::new(self.attributes.clone()).into() } } @@ -59,6 +67,21 @@ pub struct Attribute { cel_type: ValueType, } +impl Debug for Attribute { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Attribute {{ {:?} }}", self.path) + } +} + +impl Clone for Attribute { + fn clone(&self) -> Self { + Attribute { + path: self.path.clone(), + cel_type: copy(&self.cel_type), + } + } +} + impl Attribute { pub fn get(&self) -> Value { match self.cel_type { @@ -194,11 +217,7 @@ fn new_well_known_attribute_map() -> HashMap { ]) } -fn properties<'e>( - exp: &'e CelExpression, - all: &mut Vec>, - path: &mut Vec<&'e str>, -) { +fn properties<'e>(exp: &'e CelExpression, all: &mut Vec>, path: &mut Vec<&'e str>) { match exp { CelExpression::Arithmetic(e1, _, e2) | CelExpression::Relation(e1, _, e2) @@ -247,9 +266,135 @@ fn properties<'e>( } } +pub mod data { + use crate::data::Attribute; + use cel_interpreter::objects::{Key, Map}; + use cel_interpreter::Value; + use std::collections::HashMap; + use std::sync::Arc; + + #[derive(Debug)] + enum Token { + Node(HashMap), + Value(Attribute), + } + + pub struct AttributeMap { + data: HashMap, + } + + impl AttributeMap { + pub fn new(attributes: Vec) -> Self { + let mut root = HashMap::default(); + for attr in attributes { + let mut node = &mut root; + let mut it = attr.path.tokens().into_iter(); + while let Some(token) = it.next() { + if it.len() != 0 { + node = match node + .entry(token.to_string()) + .or_insert_with(|| Token::Node(HashMap::default())) + { + Token::Node(node) => node, + Token::Value(_) => unreachable!(), // that's a bit of a lie! + }; + } else { + node.insert(token.to_string(), Token::Value(attr.clone())); + } + } + } + Self { data: root } + } + } + + impl From for Map { + fn from(value: AttributeMap) -> Self { + map_to_value(value.data) + } + } + + fn map_to_value(map: HashMap) -> Map { + let mut out: HashMap = HashMap::default(); + for (key, value) in map { + let k = key.into(); + let v = match value { + Token::Value(v) => v.get(), + Token::Node(map) => Value::Map(map_to_value(map)), + }; + out.insert(k, v); + } + Map { map: Arc::new(out) } + } + + #[cfg(test)] + mod tests { + use crate::data::cel::data::{AttributeMap, Token}; + use crate::data::know_attribute_for; + + #[test] + fn it_works() { + let map = AttributeMap::new( + [ + know_attribute_for(&"request.method".into()).unwrap(), + know_attribute_for(&"request.referer".into()).unwrap(), + know_attribute_for(&"source.address".into()).unwrap(), + know_attribute_for(&"destination.port".into()).unwrap(), + ] + .into(), + ); + + println!("{:#?}", map.data); + + assert_eq!(3, map.data.len()); + assert!(map.data.get("source").is_some()); + assert!(map.data.get("destination").is_some()); + assert!(map.data.get("request").is_some()); + + match map.data.get("source").unwrap() { + Token::Node(map) => { + assert_eq!(map.len(), 1); + match map.get("address").unwrap() { + Token::Node(_) => assert!(false), + Token::Value(v) => assert_eq!(v.path, "source.address".into()), + } + } + Token::Value(_) => assert!(false), + } + + match map.data.get("destination").unwrap() { + Token::Node(map) => { + assert_eq!(map.len(), 1); + match map.get("port").unwrap() { + Token::Node(_) => assert!(false), + Token::Value(v) => assert_eq!(v.path, "destination.port".into()), + } + } + Token::Value(_) => assert!(false), + } + + match map.data.get("request").unwrap() { + Token::Node(map) => { + assert_eq!(map.len(), 2); + assert!(map.get("method").is_some()); + match map.get("method").unwrap() { + Token::Node(_) => assert!(false), + Token::Value(v) => assert_eq!(v.path, "request.method".into()), + } + assert!(map.get("referer").is_some()); + match map.get("referer").unwrap() { + Token::Node(_) => assert!(false), + Token::Value(v) => assert_eq!(v.path, "request.referer".into()), + } + } + Token::Value(_) => assert!(false), + } + } + } +} + #[cfg(test)] mod tests { - use super::*; + use crate::data::know_attribute_for; use cel_interpreter::objects::ValueType; #[test] From f4fd18fbb55555eaa7a05e04ebf526eb93eb39ea Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 10:34:09 -0400 Subject: [PATCH 08/26] Clippy fixes Signed-off-by: Alex Snaps --- src/data/cel.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index dafd7616..953a25f6 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -346,30 +346,30 @@ pub mod data { println!("{:#?}", map.data); assert_eq!(3, map.data.len()); - assert!(map.data.get("source").is_some()); - assert!(map.data.get("destination").is_some()); - assert!(map.data.get("request").is_some()); + assert!(map.data.contains_key("source")); + assert!(map.data.contains_key("destination")); + assert!(map.data.contains_key("request")); match map.data.get("source").unwrap() { Token::Node(map) => { assert_eq!(map.len(), 1); match map.get("address").unwrap() { - Token::Node(_) => assert!(false), + Token::Node(_) => panic!("Not supposed to get here!"), Token::Value(v) => assert_eq!(v.path, "source.address".into()), } } - Token::Value(_) => assert!(false), + Token::Value(_) => panic!("Not supposed to get here!"), } match map.data.get("destination").unwrap() { Token::Node(map) => { assert_eq!(map.len(), 1); match map.get("port").unwrap() { - Token::Node(_) => assert!(false), + Token::Node(_) => panic!("Not supposed to get here!"), Token::Value(v) => assert_eq!(v.path, "destination.port".into()), } } - Token::Value(_) => assert!(false), + Token::Value(_) => panic!("Not supposed to get here!"), } match map.data.get("request").unwrap() { @@ -377,16 +377,16 @@ pub mod data { assert_eq!(map.len(), 2); assert!(map.get("method").is_some()); match map.get("method").unwrap() { - Token::Node(_) => assert!(false), + Token::Node(_) => panic!("Not supposed to get here!"), Token::Value(v) => assert_eq!(v.path, "request.method".into()), } assert!(map.get("referer").is_some()); match map.get("referer").unwrap() { - Token::Node(_) => assert!(false), + Token::Node(_) => panic!("Not supposed to get here!"), Token::Value(v) => assert_eq!(v.path, "request.referer".into()), } } - Token::Value(_) => assert!(false), + Token::Value(_) => panic!("Not supposed to get here!"), } } } @@ -404,7 +404,7 @@ mod tests { assert_eq!(attr.path, path); match attr.cel_type { ValueType::String => {} - _ => assert!(false), + _ => panic!("Not supposed to get here!"), } } } From 34c70506a492c6aab56c141706b6c0e5a3800f50 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 10:31:07 -0400 Subject: [PATCH 09/26] Notes for auth/json integration Signed-off-by: Alex Snaps --- src/configuration.rs | 5 +++++ src/data/attribute.rs | 35 +++++++++++++++++++++++++++++++++++ src/data/cel.rs | 16 +++++++++++++++- src/data/property.rs | 1 + 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/configuration.rs b/src/configuration.rs index ba06ce77..ac24f2fb 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -167,6 +167,11 @@ impl PatternExpression { } Some(attribute_bytes) => attribute_bytes, }; + + // if someone would have the P_E be: + // selector: auth.identity.anonymous + // operator: eq + // value: \""true"\" self.eval(attribute_value).unwrap_or_else(|e| { debug!("pattern_expression_applies failed: {}", e); false diff --git a/src/data/attribute.rs b/src/data/attribute.rs index 6dafb681..faa25356 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -130,7 +130,42 @@ pub fn store_metadata(metastruct: &Struct) { let metadata = process_metadata(metastruct, String::new()); for (key, value) in metadata { let attr = format!("{KUADRANT_NAMESPACE}\\.{key}"); + // stored into host_property: wasm.kuadrant.{key} + // example: wasm.kuadrant.identity.anonymous is how it's stored! + // but users would write the predicate: !auth.identity.anonymous + // two problems: + // - 1/ auth.identity doesn't resolve + // - 2/ the value is the string "true" + + // struct User { + // foo: bool, + // bar: float, // 1 != 1.0 + // name: String, + // } + // + // Admin can store this: + // authorino: export auth.identity.user.foo = expression("auth.user != null") // {"foo": true, "bar": 123, "name": "dd"} + // Or that: + // authorino: export auth.identity.foo = expression("auth.user.long.ass.path.to.some.member.within.foo") + // authorino: export auth.identity.user.bar = 123 + // authorino: export auth.identity.user.name = "dd" + + // predicate: !auth.identity.user.foo && auth.identity.user.bar != 443.0 + // => properties [["auth", "identity", "user", "foo"], ["destination", "port"]] + // known part ["auth", "identity"] + "user", resolves the key wasm.kuadrant.identity.user ? to lookup the value + // value is a string, "{"foo": true, "bar": 123, "name": "dd"}" + // value is json literal... string "true" is the Bool(true), we need to unmarshal that value form json + // then evaluate the rest of the path against that structure and resolve the type from the value itself + // e.g. : user.foo, .foo is that part that we don't know about, .foo => what's the value? + // value is true, i.e. a boolean, so the value is cel_interpreter.Value::Bool(true) + // within the value, we need to access the member "foo" + // + // Split the work in 2: + // - deal with scalar json values in the attribute: bool, number, string, null + // - deal with: list, map, object + // e.g. support auth.identity.groups[0] == "foo" debug!("set_attribute: {attr} = {value}"); + // value is actually a json literal, e.g. 'true' != '"true"' set_attribute(attr.as_str(), value.into_bytes().as_slice()); } } diff --git a/src/data/cel.rs b/src/data/cel.rs index 953a25f6..0813f49b 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -22,7 +22,16 @@ impl Expression { let attributes = props .into_iter() - .map(|tokens| know_attribute_for(&Path::new(tokens)).expect("Unknown attribute")) + .map(|tokens| { + know_attribute_for(&Path::new(tokens)) + // resolve to known root, and then inspect proper location + // path = ["auth", "identity", "anonymous", ...] + // UnknownAttribute { known_root: Path, Path } + // + // e.g. known part: ["auth", "identity"] => map it proper location + // ...anonymous + .expect("Unknown attribute") + }) .collect(); Ok(Self { @@ -35,6 +44,10 @@ impl Expression { let mut ctx = Context::default(); let Map { map } = self.build_data_map(); + // if expression was "auth.identity.anonymous", + // { + // "auth": { "identity": { "anonymous": true } } + // } for binding in ["request", "metadata", "source", "destination", "auth"] { ctx.add_variable_from_value( binding, @@ -214,6 +227,7 @@ fn new_well_known_attribute_map() -> HashMap { ("connection.mtls".into(), ValueType::Bool), ("request.raw_body".into(), ValueType::Bytes), ("auth.identity".into(), ValueType::Bytes), + ("auth.identity.anonymous".into(), ValueType::Bytes), ]) } diff --git a/src/data/property.rs b/src/data/property.rs index 55d43f57..f8692629 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -33,6 +33,7 @@ fn host_get_property(path: &Path) -> Result>, Status> { pub fn get_property(path: &Path) -> Result>, Status> { match path.tokens()[..] { ["source", "remote_address"] => remote_address(), + // for auth stuff => resolve_host_props() => json string literal as Bytes _ => host_get_property(path), } } From df48e111d2b10c916d299bc4cec99c98b090ee08 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 17:23:56 -0400 Subject: [PATCH 10/26] Typos and test infra for hostcalls::get_property Signed-off-by: Alex Snaps --- src/data/cel.rs | 34 +++++++++++++++++++++++----------- src/data/mod.rs | 2 +- src/data/property.rs | 18 +++++++++++++++--- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index 0813f49b..6523053d 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -23,7 +23,7 @@ impl Expression { let attributes = props .into_iter() .map(|tokens| { - know_attribute_for(&Path::new(tokens)) + known_attribute_for(&Path::new(tokens)) // resolve to known root, and then inspect proper location // path = ["auth", "identity", "anonymous", ...] // UnknownAttribute { known_root: Path, Path } @@ -131,9 +131,9 @@ impl Attribute { } } -pub fn know_attribute_for(path: &Path) -> Option { - static WELL_KNOWN_ATTTRIBUTES: OnceLock> = OnceLock::new(); - WELL_KNOWN_ATTTRIBUTES +pub fn known_attribute_for(path: &Path) -> Option { + static WELL_KNOWN_ATTRIBUTES: OnceLock> = OnceLock::new(); + WELL_KNOWN_ATTRIBUTES .get_or_init(new_well_known_attribute_map) .get(path) .map(|t| Attribute { @@ -343,16 +343,16 @@ pub mod data { #[cfg(test)] mod tests { use crate::data::cel::data::{AttributeMap, Token}; - use crate::data::know_attribute_for; + use crate::data::known_attribute_for; #[test] fn it_works() { let map = AttributeMap::new( [ - know_attribute_for(&"request.method".into()).unwrap(), - know_attribute_for(&"request.referer".into()).unwrap(), - know_attribute_for(&"source.address".into()).unwrap(), - know_attribute_for(&"destination.port".into()).unwrap(), + known_attribute_for(&"request.method".into()).unwrap(), + known_attribute_for(&"request.referer".into()).unwrap(), + known_attribute_for(&"source.address".into()).unwrap(), + known_attribute_for(&"destination.port".into()).unwrap(), ] .into(), ); @@ -408,13 +408,25 @@ pub mod data { #[cfg(test)] mod tests { - use crate::data::know_attribute_for; + use crate::data::known_attribute_for; use cel_interpreter::objects::ValueType; + #[test] + fn attribute_resolve() { + super::super::property::test::TEST_PROPERTY_VALUE.set(Some(80_i64.to_le_bytes().into())); + let value = known_attribute_for(&"destination.port".into()) + .unwrap() + .get(); + assert_eq!(value, 80.into()); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("GET".bytes().collect())); + let value = known_attribute_for(&"request.method".into()).unwrap().get(); + assert_eq!(value, "GET".into()); + } + #[test] fn finds_known_attributes() { let path = "request.method".into(); - let attr = know_attribute_for(&path).expect("Must be a hit!"); + let attr = known_attribute_for(&path).expect("Must be a hit!"); assert_eq!(attr.path, path); match attr.cel_type { ValueType::String => {} diff --git a/src/data/mod.rs b/src/data/mod.rs index 3fd26430..db471171 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -8,7 +8,7 @@ pub use attribute::store_metadata; pub use attribute::AttributeValue; #[allow(unused_imports)] -pub use cel::know_attribute_for; +pub use cel::known_attribute_for; #[allow(unused_imports)] pub use cel::Attribute; diff --git a/src/data/property.rs b/src/data/property.rs index f8692629..1199676c 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -1,6 +1,5 @@ use log::debug; use log::warn; -use proxy_wasm::hostcalls; use proxy_wasm::types::Status; use std::fmt::{Debug, Display, Formatter}; @@ -25,9 +24,16 @@ fn remote_address() -> Result>, Status> { } } +#[cfg(test)] fn host_get_property(path: &Path) -> Result>, Status> { debug!("get_property: {:?}", path); - hostcalls::get_property(path.tokens()) + Ok(test::TEST_PROPERTY_VALUE.take()) +} + +#[cfg(not(test))] +fn host_get_property(path: &Path) -> Result>, Status> { + debug!("get_property: {:?}", path); + proxy_wasm::hostcalls::get_property(path.tokens()) } pub fn get_property(path: &Path) -> Result>, Status> { @@ -100,8 +106,14 @@ impl Path { } #[cfg(test)] -mod test { +pub mod test { use crate::data::property::Path; + use std::cell::Cell; + + thread_local!( + pub static TEST_PROPERTY_VALUE: Cell>> = Cell::new(None); + ); + #[test] fn path_tokenizes_with_escaping_basic() { let path: Path = r"one\.two..three\\\\.four\\\.\five.".into(); From 8c001f32c2c30c4ca4b0b3c2331a936b2ed3c3d8 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 17:37:30 -0400 Subject: [PATCH 11/26] Predicate test Signed-off-by: Alex Snaps --- src/data/cel.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/data/cel.rs b/src/data/cel.rs index 6523053d..4306b5a9 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -67,6 +67,12 @@ pub struct Predicate { } impl Predicate { + pub fn new(predicate: &str) -> Result { + Ok(Self { + expression: Expression::new(predicate)?, + }) + } + pub fn test(&self) -> bool { match self.expression.eval() { Value::Bool(result) => result, @@ -408,9 +414,17 @@ pub mod data { #[cfg(test)] mod tests { + use crate::data::cel::Predicate; use crate::data::known_attribute_for; use cel_interpreter::objects::ValueType; + #[test] + fn predicates() { + let predicate = Predicate::new("source.port == 65432").expect("This is valid CEL!"); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some(65432_i64.to_le_bytes().into())); + assert!(predicate.test()); + } + #[test] fn attribute_resolve() { super::super::property::test::TEST_PROPERTY_VALUE.set(Some(80_i64.to_le_bytes().into())); From ce49eb2bc6490ee60e1ace1981a8220f61df591c Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 18:54:23 -0400 Subject: [PATCH 12/26] Transparently replace PatternExpression with CEL Predicate Signed-off-by: Alex Snaps --- src/configuration.rs | 20 +++++++++++++++++++- src/configuration/action.rs | 16 +++++++++++++++- src/configuration/action_set.rs | 27 +++++++++++++++++++++------ src/data/cel.rs | 9 +++++---- src/data/mod.rs | 5 +---- src/operation_dispatcher.rs | 3 +++ tests/rate_limited.rs | 21 +++++---------------- 7 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index ac24f2fb..ca0337f3 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use crate::configuration::action_set::ActionSet; use crate::configuration::action_set_index::ActionSetIndex; -use crate::data::AttributeValue; use crate::data::PropertyPath; +use crate::data::{AttributeValue, Predicate}; use crate::service::GrpcService; use cel_interpreter::functions::duration; use cel_interpreter::objects::ValueType; @@ -446,6 +446,15 @@ impl TryFrom for FilterConfig { return Err(result.err().unwrap()); } } + let mut predicates = Vec::default(); + for predicate in &action_set.route_rule_conditions.predicates { + predicates.push(Predicate::new(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 { for condition in &action.conditions { let result = condition.compile(); @@ -453,6 +462,15 @@ impl TryFrom for FilterConfig { return Err(result.err().unwrap()); } } + 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 { let result = datum.item.compile(); if result.is_err() { diff --git a/src/configuration/action.rs b/src/configuration/action.rs index b6bfda8e..a1600dde 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,8 +1,10 @@ use crate::configuration::{DataItem, DataType, PatternExpression}; +use crate::data::Predicate; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; use log::debug; use protobuf::RepeatedField; use serde::Deserialize; +use std::cell::OnceCell; #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -12,12 +14,24 @@ pub struct Action { #[serde(default)] pub conditions: Vec, #[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 { - self.conditions.is_empty() || self.conditions.iter().all(|m| m.applies()) + let predicates = self + .compiled_predicates + .get() + .expect("predicates must be compiled by now"); + if predicates.is_empty() { + self.conditions.is_empty() || self.conditions.iter().all(PatternExpression::applies) + } else { + predicates.iter().all(Predicate::test) + } } pub fn build_descriptors(&self) -> RepeatedField { diff --git a/src/configuration/action_set.rs b/src/configuration/action_set.rs index 6448dadb..c2ebba8d 100644 --- a/src/configuration/action_set.rs +++ b/src/configuration/action_set.rs @@ -1,12 +1,18 @@ use crate::configuration::action::Action; use crate::configuration::PatternExpression; +use crate::data::Predicate; use serde::Deserialize; +use std::cell::OnceCell; #[derive(Deserialize, Debug, Clone, Default)] pub struct RouteRuleConditions { pub hostnames: Vec, #[serde(default)] pub matches: Vec, + #[serde(default)] + pub predicates: Vec, + #[serde(skip_deserializing)] + pub compiled_predicates: OnceCell>, } #[derive(Default, Deserialize, Debug, Clone)] @@ -32,11 +38,20 @@ impl ActionSet { } pub fn conditions_apply(&self) -> bool { - self.route_rule_conditions.matches.is_empty() - || self - .route_rule_conditions - .matches - .iter() - .all(|m| m.applies()) + let predicates = self + .route_rule_conditions + .compiled_predicates + .get() + .expect("predicates must be compiled by now"); + if predicates.is_empty() { + self.route_rule_conditions.matches.is_empty() + || self + .route_rule_conditions + .matches + .iter() + .all(|m| m.applies()) + } else { + predicates.iter().all(Predicate::test) + } } } diff --git a/src/data/cel.rs b/src/data/cel.rs index 4306b5a9..8c96bcf0 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::sync::OnceLock; +#[derive(Clone, Debug)] pub struct Expression { attributes: Vec, expression: CelExpression, @@ -62,6 +63,7 @@ impl Expression { } } +#[derive(Clone, Debug)] pub struct Predicate { expression: Expression, } @@ -287,7 +289,7 @@ fn properties<'e>(exp: &'e CelExpression, all: &mut Vec>, path: &mu } pub mod data { - use crate::data::Attribute; + use crate::data::cel::Attribute; use cel_interpreter::objects::{Key, Map}; use cel_interpreter::Value; use std::collections::HashMap; @@ -349,7 +351,7 @@ pub mod data { #[cfg(test)] mod tests { use crate::data::cel::data::{AttributeMap, Token}; - use crate::data::known_attribute_for; + use crate::data::cel::known_attribute_for; #[test] fn it_works() { @@ -414,8 +416,7 @@ pub mod data { #[cfg(test)] mod tests { - use crate::data::cel::Predicate; - use crate::data::known_attribute_for; + use crate::data::cel::{known_attribute_for, Predicate}; use cel_interpreter::objects::ValueType; #[test] diff --git a/src/data/mod.rs b/src/data/mod.rs index db471171..db0e2ba7 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -7,10 +7,7 @@ pub use attribute::get_attribute; pub use attribute::store_metadata; pub use attribute::AttributeValue; -#[allow(unused_imports)] -pub use cel::known_attribute_for; -#[allow(unused_imports)] -pub use cel::Attribute; +pub use cel::Predicate; pub use property::get_property; pub use property::Path as PropertyPath; diff --git a/src/operation_dispatcher.rs b/src/operation_dispatcher.rs index 07de507b..4a02886d 100644 --- a/src/operation_dispatcher.rs +++ b/src/operation_dispatcher.rs @@ -248,6 +248,7 @@ fn conditions_apply_fn(action: &Action) -> bool { #[cfg(test)] mod tests { + use std::cell::OnceCell; use super::*; use crate::configuration::Timeout; use crate::envoy::RateLimitRequest; @@ -314,6 +315,8 @@ mod tests { service: "local".to_string(), scope: "".to_string(), conditions: vec![], + predicates: vec![], + compiled_predicates: OnceCell::default(), data: vec![], }, service_handler: Rc::new(build_grpc_service_handler()), diff --git a/tests/rate_limited.rs b/tests/rate_limited.rs index 16b0c625..bbc07322 100644 --- a/tests/rate_limited.rs +++ b/tests/rate_limited.rs @@ -253,22 +253,11 @@ fn it_passes_additional_headers() { "name": "some-name", "routeRuleConditions": { "hostnames": ["*.toystore.com", "example.com"], - "matches": [ - { - "selector": "request.url_path", - "operator": "startswith", - "value": "/admin/toy" - }, - { - "selector": "request.host", - "operator": "eq", - "value": "cars.toystore.com" - }, - { - "selector": "request.method", - "operator": "eq", - "value": "POST" - }] + "predicates": [ + "request.url_path.startsWith('/admin/toy')", + "request.host == 'cars.toystore.com'", + "request.method == 'POST'" + ] }, "actions": [ { From 60820e0b8b3565309ded9d9e2786926f9eee848d Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Oct 2024 18:58:32 -0400 Subject: [PATCH 13/26] Moar clippy Signed-off-by: Alex Snaps --- src/data/property.rs | 2 +- src/operation_dispatcher.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/property.rs b/src/data/property.rs index 1199676c..5c1584b0 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -111,7 +111,7 @@ pub mod test { use std::cell::Cell; thread_local!( - pub static TEST_PROPERTY_VALUE: Cell>> = Cell::new(None); + pub static TEST_PROPERTY_VALUE: Cell>> = const { Cell::new(None) }; ); #[test] diff --git a/src/operation_dispatcher.rs b/src/operation_dispatcher.rs index 4a02886d..618203d0 100644 --- a/src/operation_dispatcher.rs +++ b/src/operation_dispatcher.rs @@ -248,11 +248,11 @@ fn conditions_apply_fn(action: &Action) -> bool { #[cfg(test)] mod tests { - use std::cell::OnceCell; use super::*; use crate::configuration::Timeout; use crate::envoy::RateLimitRequest; use protobuf::RepeatedField; + use std::cell::OnceCell; use std::time::Duration; fn default_grpc_call_fn_stub( From 067aaf2e4b344a0f9a3e277fa3e7de61b3a5e4fe Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 22 Oct 2024 16:20:20 -0400 Subject: [PATCH 14/26] Deal with unknown attributes as JSON literals Signed-off-by: Alex Snaps --- src/data/cel.rs | 130 ++++++++++++++++++++++++++++--------------- src/data/property.rs | 20 ++++++- 2 files changed, 103 insertions(+), 47 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index 8c96bcf0..318f5ab9 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -4,6 +4,7 @@ use cel_interpreter::objects::{Map, ValueType}; use cel_interpreter::{Context, Value}; use cel_parser::{parse, Expression as CelExpression, Member, ParseError}; use chrono::{DateTime, FixedOffset}; +use serde_json::Value as JsonValue; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::sync::OnceLock; @@ -24,14 +25,11 @@ impl Expression { let attributes = props .into_iter() .map(|tokens| { - known_attribute_for(&Path::new(tokens)) - // resolve to known root, and then inspect proper location - // path = ["auth", "identity", "anonymous", ...] - // UnknownAttribute { known_root: Path, Path } - // - // e.g. known part: ["auth", "identity"] => map it proper location - // ...anonymous - .expect("Unknown attribute") + let path = Path::new(tokens); + known_attribute_for(&path).unwrap_or(Attribute { + path, + cel_type: None, + }) }) .collect(); @@ -85,7 +83,7 @@ impl Predicate { pub struct Attribute { path: Path, - cel_type: ValueType, + cel_type: Option, } impl Debug for Attribute { @@ -98,43 +96,49 @@ impl Clone for Attribute { fn clone(&self) -> Self { Attribute { path: self.path.clone(), - cel_type: copy(&self.cel_type), + cel_type: self.cel_type.as_ref().map(copy), } } } impl Attribute { pub fn get(&self) -> Value { - match self.cel_type { - ValueType::String => get_attribute::(&self.path) - .expect("Failed getting to known attribute") - .map(|v| Value::String(v.into())) - .unwrap_or(Value::Null), - ValueType::Int => get_attribute::(&self.path) - .expect("Failed getting to known attribute") - .map(Value::Int) - .unwrap_or(Value::Null), - ValueType::UInt => get_attribute::(&self.path) - .expect("Failed getting to known attribute") - .map(Value::UInt) - .unwrap_or(Value::Null), - ValueType::Float => get_attribute::(&self.path) - .expect("Failed getting to known attribute") - .map(Value::Float) - .unwrap_or(Value::Null), - ValueType::Bool => get_attribute::(&self.path) - .expect("Failed getting to known attribute") - .map(Value::Bool) - .unwrap_or(Value::Null), - ValueType::Bytes => get_attribute::>(&self.path) - .expect("Failed getting to known attribute") - .map(|v| Value::Bytes(v.into())) - .unwrap_or(Value::Null), - ValueType::Timestamp => get_attribute::>(&self.path) - .expect("Failed getting to known attribute") - .map(Value::Timestamp) - .unwrap_or(Value::Null), - _ => todo!("Need support for `{}`s!", self.cel_type), + match &self.cel_type { + Some(t) => match t { + ValueType::String => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(|v| Value::String(v.into())) + .unwrap_or(Value::Null), + ValueType::Int => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Int) + .unwrap_or(Value::Null), + ValueType::UInt => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::UInt) + .unwrap_or(Value::Null), + ValueType::Float => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Float) + .unwrap_or(Value::Null), + ValueType::Bool => get_attribute::(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Bool) + .unwrap_or(Value::Null), + ValueType::Bytes => get_attribute::>(&self.path) + .expect("Failed getting to known attribute") + .map(|v| Value::Bytes(v.into())) + .unwrap_or(Value::Null), + ValueType::Timestamp => get_attribute::>(&self.path) + .expect("Failed getting to known attribute") + .map(Value::Timestamp) + .unwrap_or(Value::Null), + _ => todo!("Need support for `{t}`s!"), + }, + None => match get_attribute::(&self.path).expect("Path must resolve!") { + None => Value::Null, + Some(json) => json_to_cel(&json), + }, } } } @@ -146,10 +150,29 @@ pub fn known_attribute_for(path: &Path) -> Option { .get(path) .map(|t| Attribute { path: path.clone(), - cel_type: copy(t), + cel_type: Some(copy(t)), }) } +fn json_to_cel(json: &str) -> Value { + let json_value: JsonValue = serde_json::from_str(json).expect("json value must parse!"); + match json_value { + JsonValue::Null => Value::Null, + JsonValue::Bool(b) => b.into(), + JsonValue::Number(n) => { + if n.is_u64() { + n.as_u64().unwrap().into() + } else if n.is_i64() { + n.as_i64().unwrap().into() + } else { + n.as_f64().unwrap().into() + } + } + JsonValue::String(str) => str.into(), + _ => todo!("Need support for more Json!"), + } +} + fn copy(value_type: &ValueType) -> ValueType { match value_type { ValueType::List => ValueType::List, @@ -234,8 +257,6 @@ fn new_well_known_attribute_map() -> HashMap { ("filter_state".into(), ValueType::Map), ("connection.mtls".into(), ValueType::Bool), ("request.raw_body".into(), ValueType::Bytes), - ("auth.identity".into(), ValueType::Bytes), - ("auth.identity.anonymous".into(), ValueType::Bytes), ]) } @@ -416,7 +437,7 @@ pub mod data { #[cfg(test)] mod tests { - use crate::data::cel::{known_attribute_for, Predicate}; + use crate::data::cel::{known_attribute_for, Expression, Predicate}; use cel_interpreter::objects::ValueType; #[test] @@ -426,6 +447,25 @@ mod tests { assert!(predicate.test()); } + #[test] + fn expressions_to_json_resolve() { + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("true".bytes().collect())); + let value = Expression::new("auth.identity.anonymous").unwrap().eval(); + assert_eq!(value, true.into()); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("42".bytes().collect())); + let value = Expression::new("auth.identity.age").unwrap().eval(); + assert_eq!(value, 42.into()); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("42.3".bytes().collect())); + let value = Expression::new("auth.identity.age").unwrap().eval(); + assert_eq!(value, 42.3.into()); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("\"John\"".bytes().collect())); + let value = Expression::new("auth.identity.name").unwrap().eval(); + assert_eq!(value, "John".into()); + super::super::property::test::TEST_PROPERTY_VALUE.set(Some("-42".bytes().collect())); + let value = Expression::new("auth.identity.age").unwrap().eval(); + assert_eq!(value, (-42).into()); + } + #[test] fn attribute_resolve() { super::super::property::test::TEST_PROPERTY_VALUE.set(Some(80_i64.to_le_bytes().into())); @@ -444,7 +484,7 @@ mod tests { let attr = known_attribute_for(&path).expect("Must be a hit!"); assert_eq!(attr.path, path); match attr.cel_type { - ValueType::String => {} + Some(ValueType::String) => {} _ => panic!("Not supposed to get here!"), } } diff --git a/src/data/property.rs b/src/data/property.rs index 5c1584b0..5a06d25b 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -1,3 +1,4 @@ +use crate::data::attribute::KUADRANT_NAMESPACE; use log::debug; use log::warn; use proxy_wasm::types::Status; @@ -24,6 +25,12 @@ fn remote_address() -> Result>, Status> { } } +fn wasm_prop(tokens: &[&str]) -> Path { + let mut flat_attr = format!("wasm\\.{KUADRANT_NAMESPACE}\\."); + flat_attr.push_str(tokens.join("\\.").as_str()); + flat_attr.as_str().into() +} + #[cfg(test)] fn host_get_property(path: &Path) -> Result>, Status> { debug!("get_property: {:?}", path); @@ -37,8 +44,10 @@ fn host_get_property(path: &Path) -> Result>, Status> { } pub fn get_property(path: &Path) -> Result>, Status> { - match path.tokens()[..] { + match *path.tokens() { ["source", "remote_address"] => remote_address(), + // todo, unsure whether to drop the "auth" part here... + ["auth", ..] => host_get_property(&wasm_prop(path.tokens().as_slice())), // for auth stuff => resolve_host_props() => json string literal as Bytes _ => host_get_property(path), } @@ -107,7 +116,7 @@ impl Path { #[cfg(test)] pub mod test { - use crate::data::property::Path; + use super::*; use std::cell::Cell; thread_local!( @@ -146,4 +155,11 @@ pub mod test { let path: Path = r"\one".into(); assert_eq!(path.tokens(), vec!["one"]); } + + #[test] + fn flat_wasm_prop() { + let path = wasm_prop(&["auth", "identity", "anonymous"]); + assert_eq!(path.tokens().len(), 1); + assert_eq!(path.tokens()[0], "wasm.kuadrant.auth.identity.anonymous"); + } } From 06000e0f439d671307c1efed6d3b4ea06a7d4103 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 22 Oct 2024 18:18:35 -0400 Subject: [PATCH 15/26] Arguable: but lets fall back to string on bad json Signed-off-by: Alex Snaps --- src/data/cel.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index 318f5ab9..93fb9531 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -155,21 +155,24 @@ pub fn known_attribute_for(path: &Path) -> Option { } fn json_to_cel(json: &str) -> Value { - let json_value: JsonValue = serde_json::from_str(json).expect("json value must parse!"); + let json_value: Result = serde_json::from_str(json); match json_value { - JsonValue::Null => Value::Null, - JsonValue::Bool(b) => b.into(), - JsonValue::Number(n) => { - if n.is_u64() { - n.as_u64().unwrap().into() - } else if n.is_i64() { - n.as_i64().unwrap().into() - } else { - n.as_f64().unwrap().into() + Ok(json) => match json { + JsonValue::Null => Value::Null, + JsonValue::Bool(b) => b.into(), + JsonValue::Number(n) => { + if n.is_u64() { + n.as_u64().unwrap().into() + } else if n.is_i64() { + n.as_i64().unwrap().into() + } else { + n.as_f64().unwrap().into() + } } - } - JsonValue::String(str) => str.into(), - _ => todo!("Need support for more Json!"), + JsonValue::String(str) => str.into(), + _ => todo!("Need support for more Json!"), + }, + _ => json.into(), } } @@ -464,6 +467,11 @@ mod tests { super::super::property::test::TEST_PROPERTY_VALUE.set(Some("-42".bytes().collect())); let value = Expression::new("auth.identity.age").unwrap().eval(); assert_eq!(value, (-42).into()); + // let's fall back to strings, as that's what we read and set in store_metadata + super::super::property::test::TEST_PROPERTY_VALUE + .set(Some("some random crap".bytes().collect())); + let value = Expression::new("auth.identity.age").unwrap().eval(); + assert_eq!(value, "some random crap".into()); } #[test] From 7dc7ac9012a0f4cbb3d96d7257565d73549d77ef Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 11:02:20 -0400 Subject: [PATCH 16/26] wasm props are in the filter_state Signed-off-by: Alex Snaps --- src/data/property.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/property.rs b/src/data/property.rs index 5a06d25b..e19edced 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -26,7 +26,7 @@ fn remote_address() -> Result>, Status> { } fn wasm_prop(tokens: &[&str]) -> Path { - let mut flat_attr = format!("wasm\\.{KUADRANT_NAMESPACE}\\."); + let mut flat_attr = format!("filter_state.wasm\\.{KUADRANT_NAMESPACE}\\."); flat_attr.push_str(tokens.join("\\.").as_str()); flat_attr.as_str().into() } From 611a900e4e49fc2ad3e7abac3ec9a5cfcf1923c1 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 11:51:22 -0400 Subject: [PATCH 17/26] Deal with more types & json encode values Signed-off-by: Alex Snaps --- src/data/attribute.rs | 31 +++++++++++++++++++++++-------- src/data/property.rs | 7 +++++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/data/attribute.rs b/src/data/attribute.rs index faa25356..f111dd1f 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -4,6 +4,7 @@ use chrono::{DateTime, FixedOffset}; use log::{debug, error}; use protobuf::well_known_types::Struct; use proxy_wasm::hostcalls; +use serde_json::Value; pub const KUADRANT_NAMESPACE: &str = "kuadrant"; @@ -179,11 +180,23 @@ fn process_metadata(s: &Struct, prefix: String) -> Vec<(String, String)> { format!("{prefix}\\.{key}") }; - if value.has_string_value() { - result.push((current_prefix, value.get_string_value().to_string())); - } else if value.has_struct_value() { + let json: Option = if value.has_string_value() { + Some(value.get_string_value().into()) + } else if value.has_bool_value() { + Some(value.get_bool_value().into()) + } else if value.has_null_value() { + Some(Value::Null) + } else if value.has_number_value() { + Some(value.get_number_value().into()) + } else { + None + }; + + if value.has_struct_value() { let nested_struct = value.get_struct_value(); result.extend(process_metadata(nested_struct, current_prefix)); + } else if let Some(v) = json { + result.push((current_prefix, serde_json::to_string(&v).unwrap())); } } result @@ -235,7 +248,7 @@ mod tests { assert_eq!(output.len(), 1); assert_eq!( output, - vec![("identity\\.userid".to_string(), "bob".to_string())] + vec![("identity\\.userid".to_string(), "\"bob\"".to_string())] ); } @@ -250,8 +263,9 @@ mod tests { )]); let output = process_metadata(&metadata, String::new()); assert_eq!(output.len(), 2); - assert!(output.contains(&("identity\\.userid".to_string(), "bob".to_string()))); - assert!(output.contains(&("identity\\.type".to_string(), "test".to_string()))); + println!("{output:#?}"); + assert!(output.contains(&("identity\\.userid".to_string(), "\"bob\"".to_string()))); + assert!(output.contains(&("identity\\.type".to_string(), "\"test\"".to_string()))); } #[test] @@ -270,8 +284,9 @@ mod tests { ), ]); let output = process_metadata(&metadata, String::new()); + println!("{output:#?}"); assert_eq!(output.len(), 2); - assert!(output.contains(&("identity\\.userid".to_string(), "bob".to_string()))); - assert!(output.contains(&("other_data".to_string(), "other_value".to_string()))); + assert!(output.contains(&("identity\\.userid".to_string(), "\"bob\"".to_string()))); + assert!(output.contains(&("other_data".to_string(), "\"other_value\"".to_string()))); } } diff --git a/src/data/property.rs b/src/data/property.rs index e19edced..d15373b9 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -159,7 +159,10 @@ pub mod test { #[test] fn flat_wasm_prop() { let path = wasm_prop(&["auth", "identity", "anonymous"]); - assert_eq!(path.tokens().len(), 1); - assert_eq!(path.tokens()[0], "wasm.kuadrant.auth.identity.anonymous"); + assert_eq!(path.tokens().len(), 2); + assert_eq!( + *path.tokens(), + ["filter_state", "wasm.kuadrant.auth.identity.anonymous"] + ); } } From f911798d47611897d1c4b49412e36803529e09d6 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 14:09:55 -0400 Subject: [PATCH 18/26] Have auth in wasm prop and log on unexpected type Signed-off-by: Alex Snaps --- src/data/attribute.rs | 43 ++++++------------------------------------- src/data/property.rs | 1 - 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/src/data/attribute.rs b/src/data/attribute.rs index f111dd1f..22156851 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -1,7 +1,7 @@ use crate::data::property::Path; use crate::data::PropertyPath; use chrono::{DateTime, FixedOffset}; -use log::{debug, error}; +use log::{debug, error, warn}; use protobuf::well_known_types::Struct; use proxy_wasm::hostcalls; use serde_json::Value; @@ -130,43 +130,8 @@ pub fn set_attribute(attr: &str, value: &[u8]) { pub fn store_metadata(metastruct: &Struct) { let metadata = process_metadata(metastruct, String::new()); for (key, value) in metadata { - let attr = format!("{KUADRANT_NAMESPACE}\\.{key}"); - // stored into host_property: wasm.kuadrant.{key} - // example: wasm.kuadrant.identity.anonymous is how it's stored! - // but users would write the predicate: !auth.identity.anonymous - // two problems: - // - 1/ auth.identity doesn't resolve - // - 2/ the value is the string "true" - - // struct User { - // foo: bool, - // bar: float, // 1 != 1.0 - // name: String, - // } - // - // Admin can store this: - // authorino: export auth.identity.user.foo = expression("auth.user != null") // {"foo": true, "bar": 123, "name": "dd"} - // Or that: - // authorino: export auth.identity.foo = expression("auth.user.long.ass.path.to.some.member.within.foo") - // authorino: export auth.identity.user.bar = 123 - // authorino: export auth.identity.user.name = "dd" - - // predicate: !auth.identity.user.foo && auth.identity.user.bar != 443.0 - // => properties [["auth", "identity", "user", "foo"], ["destination", "port"]] - // known part ["auth", "identity"] + "user", resolves the key wasm.kuadrant.identity.user ? to lookup the value - // value is a string, "{"foo": true, "bar": 123, "name": "dd"}" - // value is json literal... string "true" is the Bool(true), we need to unmarshal that value form json - // then evaluate the rest of the path against that structure and resolve the type from the value itself - // e.g. : user.foo, .foo is that part that we don't know about, .foo => what's the value? - // value is true, i.e. a boolean, so the value is cel_interpreter.Value::Bool(true) - // within the value, we need to access the member "foo" - // - // Split the work in 2: - // - deal with scalar json values in the attribute: bool, number, string, null - // - deal with: list, map, object - // e.g. support auth.identity.groups[0] == "foo" + let attr = format!("{KUADRANT_NAMESPACE}\\.auth\\.{key}"); debug!("set_attribute: {attr} = {value}"); - // value is actually a json literal, e.g. 'true' != '"true"' set_attribute(attr.as_str(), value.into_bytes().as_slice()); } } @@ -189,6 +154,10 @@ fn process_metadata(s: &Struct, prefix: String) -> Vec<(String, String)> { } else if value.has_number_value() { Some(value.get_number_value().into()) } else { + warn!( + "Don't know how to store Struct field `{}` of kind {:?}", + key, value.kind + ); None }; diff --git a/src/data/property.rs b/src/data/property.rs index d15373b9..af5d9740 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -46,7 +46,6 @@ fn host_get_property(path: &Path) -> Result>, Status> { pub fn get_property(path: &Path) -> Result>, Status> { match *path.tokens() { ["source", "remote_address"] => remote_address(), - // todo, unsure whether to drop the "auth" part here... ["auth", ..] => host_get_property(&wasm_prop(path.tokens().as_slice())), // for auth stuff => resolve_host_props() => json string literal as Bytes _ => host_get_property(path), From dbdf7e10a7f3f9286143d3ad4ad655255f70e9fc Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 14:29:28 -0400 Subject: [PATCH 19/26] Deal with previously resolved values on path digging into these Signed-off-by: Alex Snaps --- src/data/cel.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index 93fb9531..a1b221f9 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -22,7 +22,7 @@ impl Expression { let mut props = Vec::with_capacity(5); properties(&expression, &mut props, &mut Vec::default()); - let attributes = props + let mut attributes: Vec = props .into_iter() .map(|tokens| { let path = Path::new(tokens); @@ -33,6 +33,8 @@ impl Expression { }) .collect(); + attributes.sort_by(|a, b| a.path.tokens().len().cmp(&b.path.tokens().len())); + Ok(Self { attributes, expression, @@ -342,7 +344,9 @@ pub mod data { .or_insert_with(|| Token::Node(HashMap::default())) { Token::Node(node) => node, - Token::Value(_) => unreachable!(), // that's a bit of a lie! + // a value was installed, on this path... + // so that value should resolve from there on + Token::Value(_) => break, }; } else { node.insert(token.to_string(), Token::Value(attr.clone())); @@ -450,6 +454,16 @@ mod tests { assert!(predicate.test()); } + #[test] + fn expressions_sort_properties() { + let value = Expression::new( + "auth.identity.anonymous && auth.identity != null && auth.identity.foo > 3", + ) + .unwrap(); + assert_eq!(value.attributes.len(), 3); + assert_eq!(value.attributes[0].path, "auth.identity".into()); + } + #[test] fn expressions_to_json_resolve() { super::super::property::test::TEST_PROPERTY_VALUE.set(Some("true".bytes().collect())); From 5deae3b353fbddb1eb13b4e487024d10f463c3f3 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 14:31:29 -0400 Subject: [PATCH 20/26] Don't warn on structs when storing values as json Signed-off-by: Alex Snaps --- src/data/attribute.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/data/attribute.rs b/src/data/attribute.rs index 22156851..37625dcd 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -154,10 +154,12 @@ fn process_metadata(s: &Struct, prefix: String) -> Vec<(String, String)> { } else if value.has_number_value() { Some(value.get_number_value().into()) } else { - warn!( - "Don't know how to store Struct field `{}` of kind {:?}", - key, value.kind - ); + if !value.has_struct_value() { + warn!( + "Don't know how to store Struct field `{}` of kind {:?}", + key, value.kind + ); + } None }; From a786e943df9d73c819b111d8e27e874b6748d764 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 15:26:09 -0400 Subject: [PATCH 21/26] Support headers in CEL Signed-off-by: Alex Snaps --- src/data/attribute.rs | 3 +-- src/data/cel.rs | 6 +++++- src/data/property.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/data/attribute.rs b/src/data/attribute.rs index 37625dcd..9e65caa9 100644 --- a/src/data/attribute.rs +++ b/src/data/attribute.rs @@ -1,4 +1,3 @@ -use crate::data::property::Path; use crate::data::PropertyPath; use chrono::{DateTime, FixedOffset}; use log::{debug, error, warn}; @@ -121,7 +120,7 @@ where } pub fn set_attribute(attr: &str, value: &[u8]) { - match hostcalls::set_property(Path::from(attr).tokens(), Some(value)) { + match hostcalls::set_property(PropertyPath::from(attr).tokens(), Some(value)) { Ok(_) => (), Err(_) => error!("set_attribute: failed to set property {attr}"), }; diff --git a/src/data/cel.rs b/src/data/cel.rs index a1b221f9..a1f2557a 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -1,5 +1,5 @@ use crate::data::get_attribute; -use crate::data::property::Path; +use crate::data::property::{host_get_map, Path}; use cel_interpreter::objects::{Map, ValueType}; use cel_interpreter::{Context, Value}; use cel_parser::{parse, Expression as CelExpression, Member, ParseError}; @@ -135,6 +135,10 @@ impl Attribute { .expect("Failed getting to known attribute") .map(Value::Timestamp) .unwrap_or(Value::Null), + ValueType::Map => host_get_map(&self.path) + .map(cel_interpreter::objects::Map::from) + .map(Value::Map) + .unwrap_or(Value::Null), _ => todo!("Need support for `{t}`s!"), }, None => match get_attribute::(&self.path).expect("Path must resolve!") { diff --git a/src/data/property.rs b/src/data/property.rs index af5d9740..e667debe 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -2,6 +2,7 @@ use crate::data::attribute::KUADRANT_NAMESPACE; use log::debug; use log::warn; use proxy_wasm::types::Status; +use std::collections::HashMap; use std::fmt::{Debug, Display, Formatter}; fn remote_address() -> Result>, Status> { @@ -37,6 +38,33 @@ fn host_get_property(path: &Path) -> Result>, Status> { Ok(test::TEST_PROPERTY_VALUE.take()) } +#[cfg(test)] +pub fn host_get_map(path: &Path) -> Result, String> { + match *path.tokens() { + ["request", "headers"] => Ok(HashMap::default()), + _ => Err(format!("Unknown map requested {:?}", path)), + } +} + +#[cfg(not(test))] +pub fn host_get_map(path: &Path) -> Result, String> { + match *path.tokens() { + ["request", "headers"] => { + debug!( + "get_map: {:?}", + proxy_wasm::types::MapType::HttpRequestHeaders + ); + let map = + proxy_wasm::hostcalls::get_map(proxy_wasm::types::MapType::HttpRequestHeaders) + .unwrap() + .into_iter() + .collect(); + Ok(map) + } + _ => Err(format!("Unknown map requested {:?}", path)), + } +} + #[cfg(not(test))] fn host_get_property(path: &Path) -> Result>, Status> { debug!("get_property: {:?}", path); From 00d4863da966b1fb294486dd5c078375a07780be Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 15:29:04 -0400 Subject: [PATCH 22/26] Remove trailing comment Signed-off-by: Alex Snaps --- src/data/property.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/property.rs b/src/data/property.rs index e667debe..973e709c 100644 --- a/src/data/property.rs +++ b/src/data/property.rs @@ -75,7 +75,6 @@ pub fn get_property(path: &Path) -> Result>, Status> { match *path.tokens() { ["source", "remote_address"] => remote_address(), ["auth", ..] => host_get_property(&wasm_prop(path.tokens().as_slice())), - // for auth stuff => resolve_host_props() => json string literal as Bytes _ => host_get_property(path), } } From b63057f9654f67514ba4588936d7c921dad15eec Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 23 Oct 2024 17:58:03 -0400 Subject: [PATCH 23/26] No more dead code Signed-off-by: Alex Snaps --- src/data/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/mod.rs b/src/data/mod.rs index db0e2ba7..285564cc 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,5 +1,4 @@ mod attribute; -#[allow(dead_code)] mod cel; mod property; From b9af3dc1dbf19839b1d71120a14d24704e2c347a Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 06:46:19 -0400 Subject: [PATCH 24/26] Support for CEL expressions in data exports Signed-off-by: Alex Snaps --- src/configuration.rs | 32 ++++++++++++++++++++++++++------ src/configuration/action.rs | 37 +++++++++++++++++++++++++++---------- src/data/mod.rs | 1 + tests/remote_address.rs | 6 +++--- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index ca0337f3..22c23380 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use crate::configuration::action_set::ActionSet; use crate::configuration::action_set_index::ActionSetIndex; +use crate::data; use crate::data::PropertyPath; use crate::data::{AttributeValue, Predicate}; use crate::service::GrpcService; @@ -22,6 +23,23 @@ pub mod action; pub mod action_set; mod action_set_index; +#[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)] pub struct SelectorItem { // Selector of an attribute from the contextual properties provided by kuadrant @@ -67,6 +85,7 @@ pub struct StaticItem { pub enum DataType { Static(StaticItem), Selector(SelectorItem), + Expression(ExpressionItem), } impl DataType { @@ -74,6 +93,7 @@ impl DataType { match self { DataType::Static(_) => Ok(()), DataType::Selector(selector) => selector.compile(), + DataType::Expression(exp) => exp.compile(), } } } @@ -640,8 +660,9 @@ mod test { } }, { - "selector": { - "selector": "auth.metadata.username" + "expression": { + "key": "username", + "value": "auth.metadata.username" } }] }] @@ -721,10 +742,9 @@ mod test { panic!(); } - if let DataType::Selector(selector_item) = &rl_data_items[1].item { - assert_eq!(selector_item.selector, "auth.metadata.username"); - assert!(selector_item.key.is_none()); - assert!(selector_item.default.is_none()); + if let DataType::Expression(exp) = &rl_data_items[1].item { + assert_eq!(exp.key, "username"); + assert_eq!(exp.value, "auth.metadata.username"); } else { panic!(); } diff --git a/src/configuration/action.rs b/src/configuration/action.rs index a1600dde..4cbfe31b 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,6 +1,7 @@ use crate::configuration::{DataItem, DataType, PatternExpression}; use crate::data::Predicate; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; +use cel_interpreter::Value; use log::debug; use protobuf::RepeatedField; use serde::Deserialize; @@ -47,13 +48,28 @@ impl Action { // iterate over data items to allow any data item to skip the entire descriptor for data in self.data.iter() { - match &data.item { + let (key, value) = 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); + (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() + { + 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"), + }, + ), DataType::Selector(selector_item) => { let descriptor_key = match &selector_item.key { None => selector_item.path().to_string(), @@ -83,12 +99,13 @@ impl Action { // filter.context_id, attribute_path, e)) // .ok()?, }; - let mut descriptor_entry = RateLimitDescriptor_Entry::new(); - descriptor_entry.set_key(descriptor_key); - descriptor_entry.set_value(value); - entries.push(descriptor_entry); + (descriptor_key, value) } - } + }; + 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); diff --git a/src/data/mod.rs b/src/data/mod.rs index 285564cc..0e1cc101 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -6,6 +6,7 @@ pub use attribute::get_attribute; pub use attribute::store_metadata; pub use attribute::AttributeValue; +pub use cel::Expression; pub use cel::Predicate; pub use property::get_property; diff --git a/tests/remote_address.rs b/tests/remote_address.rs index 15b77daa..5ffdc5c0 100644 --- a/tests/remote_address.rs +++ b/tests/remote_address.rs @@ -49,9 +49,9 @@ fn it_limits_based_on_source_address() { "scope": "RLS-domain", "data": [ { - "selector": { - "selector": "source.remote_address", - "value": "1" + "expression": { + "key": "source.remote_address", + "value": "source.remote_address" } } ] From 7528942443fe17927b5a68bd6e3fbfe1290142a1 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 07:39:00 -0400 Subject: [PATCH 25/26] request.time is a Timestamp Signed-off-by: Alex Snaps --- src/data/cel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/cel.rs b/src/data/cel.rs index a1f2557a..c2c4d67b 100644 --- a/src/data/cel.rs +++ b/src/data/cel.rs @@ -201,7 +201,7 @@ fn copy(value_type: &ValueType) -> ValueType { fn new_well_known_attribute_map() -> HashMap { HashMap::from([ - ("request.time".into(), ValueType::String), + ("request.time".into(), ValueType::Timestamp), ("request.id".into(), ValueType::String), ("request.protocol".into(), ValueType::String), ("request.scheme".into(), ValueType::String), From 06cd03d790a576cf9f537057ec15a8edd2eea2c4 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Thu, 24 Oct 2024 08:32:48 -0400 Subject: [PATCH 26/26] README reflecting new config Signed-off-by: Alex Snaps --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 62ad7980..f3666936 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,50 @@ A Proxy-Wasm module written in Rust, acting as a shim between Envoy and Limitado Following is a sample configuration used by the shim. +```yaml +services: + auth-service: + type: auth + endpoint: auth-cluster + failureMode: deny + timeout: 10ms + ratelimit-service: + type: ratelimit + endpoint: ratelimit-cluster + failureMode: deny +actionSets: + - name: rlp-ns-A/rlp-name-A + routeRuleConditions: + hostnames: [ "*.toystore.com" ] + predicates: + - request.url_path.startsWith("/get") + - request.host == "test.toystore.com" + - request.method == "GET" + actions: + - service: ratelimit-service + scope: rlp-ns-A/rlp-name-A + conditions: [] + data: + - expression: + key: my_header + value: request.headers["My-Custom-Header"] +``` + +## Features + +### CEL Predicates and Expression + +`routeRuleConditions`'s `predicate`s are expressed in [Common Expression Language (CEL)](https://cel.dev). `Predicate`s +evaluating to a `bool` value, while `Expression`, used for passing data to a service, evaluate to some `Value`. + +These expression can operate on the data made available to them through the Well Known Attributes, see below + +#### Conditions, Selectors and Operators (deprecated!) + +
+ +While still supported, these will eventually disappear. For now though, you still can express them as such: + ```yaml services: auth-service: @@ -46,10 +90,6 @@ actionSets: value: "1" ``` -## Features - -#### Condition operators implemented - ```Rust #[derive(Deserialize, PartialEq, Debug, Clone)] pub enum WhenConditionOperator { @@ -66,15 +106,6 @@ pub enum WhenConditionOperator { } ``` -The `matches` operator is a a simple globbing pattern implementation based on regular expressions. -The only characters taken into account are: - -* `?`: 0 or 1 characters -* `*`: 0 or more characters -* `+`: 1 or more characters - -#### Selectors - Selector of an attribute from the contextual properties provided by kuadrant. See [Well Known Attributes](#Well-Known-Attributes) for more info about available attributes. @@ -109,12 +140,16 @@ Some path segments include dot `.` char in them. For instance envoy filter names: `envoy.filters.http.header_to_metadata`. In that particular cases, the dot chat (separator), needs to be escaped. +
+ + ### Well Known Attributes -| Attribute | Description | -| --- | --- | -| [Envoy Attributes](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes) | Contextual properties provided by Envoy during request and connection processing | -| `source.remote_address` | This attribute evaluates to the `trusted client address` (IP address without port) as it is being defined by [Envoy Doc](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for) | +| Attribute | Description | +|---------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Envoy Attributes](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes) | Contextual properties provided by Envoy during request and connection processing | +| `source.remote_address` | This attribute evaluates to the `trusted client address` (IP address without port) as it is being defined by [Envoy Doc](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for) | +| `auth.*` | Data made available by the authentication service to the `ActionSet`'s pipeline | ## Building