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 diff --git a/src/configuration.rs b/src/configuration.rs index fb239820..22c23380 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -4,10 +4,11 @@ use std::fmt::{Debug, Formatter}; use std::rc::Rc; use std::sync::Arc; -use crate::attribute::Attribute; use crate::configuration::action_set::ActionSet; use crate::configuration::action_set_index::ActionSetIndex; -use crate::property_path::Path; +use crate::data; +use crate::data::PropertyPath; +use crate::data::{AttributeValue, Predicate}; use crate::service::GrpcService; use cel_interpreter::functions::duration; use cel_interpreter::objects::ValueType; @@ -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 @@ -38,7 +56,7 @@ pub struct SelectorItem { pub default: Option, #[serde(skip_deserializing)] - path: OnceCell, + path: OnceCell, } impl SelectorItem { @@ -48,7 +66,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!") @@ -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(), } } } @@ -107,7 +127,7 @@ pub struct PatternExpression { pub value: String, #[serde(skip_deserializing)] - path: OnceCell, + path: OnceCell, #[serde(skip_deserializing)] compiled: OnceCell, } @@ -121,23 +141,22 @@ 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 { 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 => {} @@ -157,8 +176,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(self.path()).unwrap() { //TODO(didierofrivia): Replace hostcalls by DI None => { debug!( @@ -169,6 +187,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 @@ -443,6 +466,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(); @@ -450,6 +482,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() { @@ -619,8 +660,9 @@ mod test { } }, { - "selector": { - "selector": "auth.metadata.username" + "expression": { + "key": "username", + "value": "auth.metadata.username" } }] }] @@ -700,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 32f538d9..4cbfe31b 100644 --- a/src/configuration/action.rs +++ b/src/configuration/action.rs @@ -1,9 +1,11 @@ -use crate::attribute::Attribute; 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; +use std::cell::OnceCell; #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -13,12 +15,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 { @@ -34,28 +48,42 @@ 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(), Some(key) => key.to_owned(), }; - let attribute_path = selector_item.path(); - let value = match crate::property::get_property(attribute_path.tokens()) - .unwrap() + let value = match crate::data::get_attribute::(selector_item.path()) + .expect("Error!") { //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 @@ -64,26 +92,20 @@ 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) { - Ok(attr_str) => attr_str, - Err(e) => { - debug!("build_single_descriptor: failed to parse selector value: {}, error: {}", - attribute_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: {}", // 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/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/attribute.rs b/src/data/attribute.rs similarity index 76% rename from src/attribute.rs rename to src/data/attribute.rs index 130af5f0..9e65caa9 100644 --- a/src/attribute.rs +++ b/src/data/attribute.rs @@ -1,18 +1,19 @@ -use crate::property_path::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; 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 +24,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 +40,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 +56,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 +72,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 +90,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!( @@ -107,19 +108,19 @@ impl Attribute for DateTime { } } -pub fn get_attribute(attr: &str) -> Result +pub fn get_attribute(path: &PropertyPath) -> Result, String> where - T: Attribute, + T: AttributeValue, { - match crate::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}")), + 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:?}")), } } 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}"), }; @@ -128,7 +129,7 @@ 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}"); + let attr = format!("{KUADRANT_NAMESPACE}\\.auth\\.{key}"); debug!("set_attribute: {attr} = {value}"); set_attribute(attr.as_str(), value.into_bytes().as_slice()); } @@ -143,11 +144,29 @@ 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 { + if !value.has_struct_value() { + warn!( + "Don't know how to store Struct field `{}` of kind {:?}", + key, value.kind + ); + } + 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 @@ -155,7 +174,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; @@ -199,7 +218,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())] ); } @@ -214,8 +233,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] @@ -234,8 +254,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/cel.rs b/src/data/cel.rs new file mode 100644 index 00000000..c2c4d67b --- /dev/null +++ b/src/data/cel.rs @@ -0,0 +1,517 @@ +use crate::data::get_attribute; +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}; +use chrono::{DateTime, FixedOffset}; +use serde_json::Value as JsonValue; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::sync::OnceLock; + +#[derive(Clone, Debug)] +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()); + + let mut attributes: Vec = props + .into_iter() + .map(|tokens| { + let path = Path::new(tokens); + known_attribute_for(&path).unwrap_or(Attribute { + path, + cel_type: None, + }) + }) + .collect(); + + attributes.sort_by(|a, b| a.path.tokens().len().cmp(&b.path.tokens().len())); + + Ok(Self { + attributes, + expression, + }) + } + + pub fn eval(&self) -> Value { + 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, + 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) -> Map { + data::AttributeMap::new(self.attributes.clone()).into() + } +} + +#[derive(Clone, Debug)] +pub struct Predicate { + expression: Expression, +} + +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, + _ => false, + } + } +} + +pub struct Attribute { + path: Path, + cel_type: Option, +} + +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: self.cel_type.as_ref().map(copy), + } + } +} + +impl Attribute { + pub fn get(&self) -> Value { + 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), + 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!") { + None => Value::Null, + Some(json) => json_to_cel(&json), + }, + } + } +} + +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 { + path: path.clone(), + cel_type: Some(copy(t)), + }) +} + +fn json_to_cel(json: &str) -> Value { + let json_value: Result = serde_json::from_str(json); + match json_value { + 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!"), + }, + _ => json.into(), + } +} + +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::Timestamp), + ("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), + ]) +} + +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(); + } + } + } +} + +pub mod data { + use crate::data::cel::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, + // 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())); + } + } + } + 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::cel::known_attribute_for; + + #[test] + fn it_works() { + let map = AttributeMap::new( + [ + 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(), + ); + + println!("{:#?}", map.data); + + assert_eq!(3, map.data.len()); + 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(_) => panic!("Not supposed to get here!"), + Token::Value(v) => assert_eq!(v.path, "source.address".into()), + } + } + 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(_) => panic!("Not supposed to get here!"), + Token::Value(v) => assert_eq!(v.path, "destination.port".into()), + } + } + Token::Value(_) => panic!("Not supposed to get here!"), + } + + 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(_) => 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(_) => panic!("Not supposed to get here!"), + Token::Value(v) => assert_eq!(v.path, "request.referer".into()), + } + } + Token::Value(_) => panic!("Not supposed to get here!"), + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::data::cel::{known_attribute_for, Expression, Predicate}; + 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 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())); + 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()); + // 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] + 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 = known_attribute_for(&path).expect("Must be a hit!"); + assert_eq!(attr.path, path); + match attr.cel_type { + Some(ValueType::String) => {} + _ => panic!("Not supposed to get here!"), + } + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 00000000..0e1cc101 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,13 @@ +mod attribute; +mod cel; +mod property; + +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; +pub use property::Path as PropertyPath; diff --git a/src/data/property.rs b/src/data/property.rs new file mode 100644 index 00000000..973e709c --- /dev/null +++ b/src/data/property.rs @@ -0,0 +1,194 @@ +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> { + // 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(&"source.address".into())? { + 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 wasm_prop(tokens: &[&str]) -> Path { + let mut flat_attr = format!("filter_state.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); + 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); + proxy_wasm::hostcalls::get_property(path.tokens()) +} + +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())), + _ => host_get_property(path), + } +} + +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct Path { + tokens: Vec, +} + +impl Display for Path { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.tokens + .iter() + .map(|t| t.replace('.', "\\.")) + .collect::>() + .join(".") + ) + } +} + +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(); + let mut tokens: Vec = Vec::new(); + let mut chars = value.chars(); + while let Some(ch) = chars.next() { + match ch { + '.' => { + tokens.push(token); + token = String::new(); + } + '\\' => { + if let Some(next) = chars.next() { + token.push(next); + } + } + _ => token.push(ch), + } + } + tokens.push(token); + + Self { tokens } + } +} + +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() + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use std::cell::Cell; + + thread_local!( + pub static TEST_PROPERTY_VALUE: Cell>> = const { Cell::new(None) }; + ); + + #[test] + fn path_tokenizes_with_escaping_basic() { + let path: Path = r"one\.two..three\\\\.four\\\.\five.".into(); + assert_eq!( + path.tokens(), + vec!["one.two", "", r"three\\", r"four\.five", ""] + ); + } + + #[test] + fn path_tokenizes_with_escaping_ends_with_separator() { + let path: Path = r"one.".into(); + assert_eq!(path.tokens(), vec!["one", ""]); + } + + #[test] + fn path_tokenizes_with_escaping_ends_with_escape() { + let path: Path = r"one\".into(); + assert_eq!(path.tokens(), vec!["one"]); + } + + #[test] + fn path_tokenizes_with_escaping_starts_with_separator() { + let path: Path = r".one".into(); + assert_eq!(path.tokens(), vec!["", "one"]); + } + + #[test] + fn path_tokenizes_with_escaping_starts_with_escape() { + 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(), 2); + assert_eq!( + *path.tokens(), + ["filter_state", "wasm.kuadrant.auth.identity.anonymous"] + ); + } +} 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..9f065b3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,10 @@ -mod attribute; mod configuration; +mod data; #[allow(renamed_and_removed_lints)] mod envoy; mod filter; mod glob; mod operation_dispatcher; -mod property; -mod property_path; mod service; #[cfg(test)] diff --git a/src/operation_dispatcher.rs b/src/operation_dispatcher.rs index 07de507b..618203d0 100644 --- a/src/operation_dispatcher.rs +++ b/src/operation_dispatcher.rs @@ -252,6 +252,7 @@ mod tests { 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( @@ -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/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/property_path.rs b/src/property_path.rs deleted file mode 100644 index c2a5e049..00000000 --- a/src/property_path.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; - -#[derive(Debug, Clone)] -pub struct Path { - tokens: Vec, -} - -impl Display for Path { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - self.tokens - .iter() - .map(|t| t.replace('.', "\\.")) - .collect::>() - .join(".") - ) - } -} - -impl From<&str> for Path { - fn from(value: &str) -> Self { - let mut token = String::new(); - let mut tokens: Vec = Vec::new(); - let mut chars = value.chars(); - while let Some(ch) = chars.next() { - match ch { - '.' => { - tokens.push(token); - token = String::new(); - } - '\\' => { - if let Some(next) = chars.next() { - token.push(next); - } - } - _ => token.push(ch), - } - } - tokens.push(token); - - Self { tokens } - } -} - -impl Path { - pub fn tokens(&self) -> Vec<&str> { - self.tokens.iter().map(String::as_str).collect() - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn path_tokenizes_with_escaping_basic() { - let path: Path = r"one\.two..three\\\\.four\\\.\five.".into(); - assert_eq!( - path.tokens(), - vec!["one.two", "", r"three\\", r"four\.five", ""] - ); - } - - #[test] - fn path_tokenizes_with_escaping_ends_with_separator() { - let path: Path = r"one.".into(); - assert_eq!(path.tokens(), vec!["one", ""]); - } - - #[test] - fn path_tokenizes_with_escaping_ends_with_escape() { - let path: Path = r"one\".into(); - assert_eq!(path.tokens(), vec!["one"]); - } - - #[test] - fn path_tokenizes_with_escaping_starts_with_separator() { - let path: Path = r".one".into(); - assert_eq!(path.tokens(), vec!["", "one"]); - } - - #[test] - fn path_tokenizes_with_escaping_starts_with_escape() { - let path: Path = r"\one".into(); - assert_eq!(path.tokens(), vec!["one"]); - } -} diff --git a/src/service/auth.rs b/src/service/auth.rs index 7a177f43..c8e4a4d2 100644 --- a/src/service/auth.rs +++ b/src/service/auth.rs @@ -1,5 +1,5 @@ -use crate::attribute::{get_attribute, store_metadata}; 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, @@ -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 } 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), ) 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": [ { 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" } } ]