diff --git a/README.md b/README.md
index a638a36..5068a97 100644
--- a/README.md
+++ b/README.md
@@ -50,102 +50,6 @@ evaluating to a `bool` value, while `Expression`, used for passing data to a ser
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:
- 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" ]
- matches:
- - selector: request.url_path
- operator: startswith
- value: /get
- - selector: request.host
- operator: eq
- value: test.toystore.com
- - selector: request.method
- operator: eq
- value: GET
- actions:
- - service: ratelimit-service
- scope: rlp-ns-A/rlp-name-A
- conditions: []
- data:
- - selector:
- selector: request.headers.My-Custom-Header
- - static:
- key: admin
- value: "1"
-```
-
-```Rust
-#[derive(Deserialize, PartialEq, Debug, Clone)]
-pub enum WhenConditionOperator {
- #[serde(rename = "eq")]
- EqualOperator,
- #[serde(rename = "neq")]
- NotEqualOperator,
- #[serde(rename = "startswith")]
- StartsWithOperator,
- #[serde(rename = "endswith")]
- EndsWithOperator,
- #[serde(rename = "matches")]
- MatchesOperator,
-}
-```
-
-Selector of an attribute from the contextual properties provided by kuadrant.
-See [Well Known Attributes](#Well-Known-Attributes) for more info about available attributes.
-
-The struct is
-
-```Rust
-#[derive(Deserialize, Debug, Clone)]
-pub struct SelectorItem {
- // Selector of an attribute from the contextual properties provided by kuadrant
- // during request and connection processing
- pub selector: String,
-
- // If not set it defaults to `selector` field value as the descriptor key.
- #[serde(default)]
- pub key: Option,
-
- // An optional value to use if the selector is not found in the context.
- // If not set and the selector is not found in the context, then no data is generated.
- #[serde(default)]
- pub default: Option,
-}
-```
-
-Selectors are tokenized at each non-escaped occurrence of a separator character `.`.
-Example:
-
-```
-Input: this.is.a.exam\.ple -> Retuns: ["this", "is", "a", "exam.ple"].
-```
-
-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 |
diff --git a/src/configuration.rs b/src/configuration.rs
index 6605f9a..a9c5dda 100644
--- a/src/configuration.rs
+++ b/src/configuration.rs
@@ -7,14 +7,10 @@ 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::data::Predicate;
use crate::service::GrpcService;
use cel_interpreter::functions::duration;
-use cel_interpreter::objects::ValueType;
-use cel_interpreter::{Context, Expression, Value};
-use cel_parser::{Atom, RelationOp};
-use log::debug;
+use cel_interpreter::Value;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer};
use std::time::Duration;
@@ -40,39 +36,6 @@ impl ExpressionItem {
}
}
-#[derive(Deserialize, Debug, Clone)]
-pub struct SelectorItem {
- // Selector of an attribute from the contextual properties provided by kuadrant
- // during request and connection processing
- pub selector: String,
-
- // If not set it defaults to `selector` field value as the descriptor key.
- #[serde(default)]
- pub key: Option,
-
- // An optional value to use if the selector is not found in the context.
- // If not set and the selector is not found in the context, then no data is generated.
- #[serde(default)]
- pub default: Option,
-
- #[serde(skip_deserializing)]
- path: OnceCell,
-}
-
-impl SelectorItem {
- pub fn compile(&self) -> Result<(), String> {
- self.path
- .set(self.selector.as_str().into())
- .map_err(|p| format!("Err on {p:?}"))
- }
-
- pub fn path(&self) -> &PropertyPath {
- self.path
- .get()
- .expect("SelectorItem wasn't previously compiled!")
- }
-}
-
#[derive(Deserialize, Debug, Clone)]
pub struct StaticItem {
pub value: String,
@@ -84,7 +47,6 @@ pub struct StaticItem {
#[serde(rename_all = "lowercase")]
pub enum DataType {
Static(StaticItem),
- Selector(SelectorItem),
Expression(ExpressionItem),
}
@@ -92,7 +54,6 @@ impl DataType {
pub fn compile(&self) -> Result<(), String> {
match self {
DataType::Static(_) => Ok(()),
- DataType::Selector(selector) => selector.compile(),
DataType::Expression(exp) => exp.compile(),
}
}
@@ -105,341 +66,6 @@ pub struct DataItem {
pub item: DataType,
}
-#[derive(Deserialize, PartialEq, Debug, Clone)]
-pub enum WhenConditionOperator {
- #[serde(rename = "eq")]
- Equal,
- #[serde(rename = "neq")]
- NotEqual,
- #[serde(rename = "startswith")]
- StartsWith,
- #[serde(rename = "endswith")]
- EndsWith,
- #[serde(rename = "matches")]
- Matches,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-#[serde(rename_all = "camelCase")]
-pub struct PatternExpression {
- pub selector: String,
- pub operator: WhenConditionOperator,
- pub value: String,
-
- #[serde(skip_deserializing)]
- path: OnceCell,
- #[serde(skip_deserializing)]
- compiled: OnceCell,
-}
-
-impl PatternExpression {
- pub fn compile(&self) -> Result<(), String> {
- self.path
- .set(self.selector.as_str().into())
- .map_err(|_| "Duh!")?;
- self.compiled
- .set(self.try_into()?)
- .map_err(|_| "Ooops".to_string())
- }
- pub fn path(&self) -> &PropertyPath {
- self.path
- .get()
- .expect("PatternExpression wasn't previously compiled!")
- }
-
- 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(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 => {}
- _ => unimplemented!("Need support for {}", cel_type),
- };
- let mut ctx = Context::default();
- ctx.add_variable_from_value("attribute", value);
- Value::resolve(&self.compiled.get().unwrap().expression, &ctx)
- .map(|v| {
- if let Value::Bool(result) = v {
- result
- } else {
- false
- }
- })
- .map_err(|err| format!("Error evaluating {:?}: {}", self.compiled, err))
- }
-
- fn applies(&self) -> bool {
- let attribute_value = match crate::data::get_property(self.path()).unwrap() {
- //TODO(didierofrivia): Replace hostcalls by DI
- None => {
- debug!(
- "pattern_expression_applies: selector not found: {}, defaulting to ``",
- self.selector
- );
- b"".to_vec()
- }
- 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
- })
- }
-}
-
-struct CelExpression {
- expression: Expression,
- cel_type: ValueType,
-}
-
-impl Debug for CelExpression {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(f, "CelExpression({}, {:?}", self.cel_type, self.expression)
- }
-}
-
-impl Clone for CelExpression {
- fn clone(&self) -> Self {
- Self {
- expression: self.expression.clone(),
- cel_type: match self.cel_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,
- },
- }
- }
-}
-
-impl TryFrom<&PatternExpression> for CelExpression {
- type Error = String;
-
- fn try_from(expression: &PatternExpression) -> Result {
- let cel_value = match cel_parser::parse(&expression.value) {
- Ok(exp) => match exp {
- Expression::Ident(ident) => Expression::Atom(Atom::String(ident)),
- Expression::Member(_, _) => {
- Expression::Atom(Atom::String(expression.value.to_string().into()))
- }
- _ => exp,
- },
- Err(_) => Expression::Atom(Atom::String(expression.value.clone().into())),
- };
- let cel_type = type_of(&expression.selector).unwrap_or(match &cel_value {
- Expression::List(_) => Ok(ValueType::List),
- Expression::Map(_) => Ok(ValueType::Map),
- Expression::Atom(atom) => Ok(match atom {
- Atom::Int(_) => ValueType::Int,
- Atom::UInt(_) => ValueType::UInt,
- Atom::Float(_) => ValueType::Float,
- Atom::String(_) => ValueType::String,
- Atom::Bytes(_) => ValueType::Bytes,
- Atom::Bool(_) => ValueType::Bool,
- Atom::Null => ValueType::Null,
- }),
- _ => Err(format!("Unsupported CEL value: {cel_value:?}")),
- }?);
-
- let value = match cel_type {
- ValueType::Map => match expression.operator {
- WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => {
- if let Expression::Map(data) = cel_value {
- Ok(Expression::Map(data))
- } else {
- Err(format!("Can't compare {cel_value:?} with a Map"))
- }
- }
- _ => Err(format!(
- "Unsupported operator {:?} on Map",
- &expression.operator
- )),
- },
- ValueType::Int | ValueType::UInt | ValueType::Float => match expression.operator {
- WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => {
- if let Expression::Atom(atom) = &cel_value {
- match atom {
- Atom::Int(_) | Atom::UInt(_) | Atom::Float(_) => Ok(cel_value),
- _ => Err(format!("Can't compare {cel_value:?} with a Number")),
- }
- } else {
- Err(format!("Can't compare {cel_value:?} with a Number"))
- }
- }
- _ => Err(format!(
- "Unsupported operator {:?} on Number",
- &expression.operator
- )),
- },
- ValueType::String => match &cel_value {
- Expression::Atom(Atom::String(_)) => Ok(cel_value),
- _ => Ok(Expression::Atom(Atom::String(Arc::new(
- expression.value.clone(),
- )))),
- },
- ValueType::Bytes => match expression.operator {
- WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => {
- if let Expression::Atom(atom) = &cel_value {
- match atom {
- Atom::String(_str) => Ok(cel_value),
- Atom::Bytes(_bytes) => Ok(cel_value),
- _ => Err(format!("Can't compare {cel_value:?} with Bytes")),
- }
- } else {
- Err(format!("Can't compare {cel_value:?} with Bytes"))
- }
- }
- _ => Err(format!(
- "Unsupported operator {:?} on Bytes",
- &expression.operator
- )),
- },
- ValueType::Bool => match expression.operator {
- WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => {
- if let Expression::Atom(atom) = &cel_value {
- match atom {
- Atom::Bool(_) => Ok(cel_value),
- _ => Err(format!("Can't compare {cel_value:?} with Bool")),
- }
- } else {
- Err(format!("Can't compare {cel_value:?} with Bool"))
- }
- }
- _ => Err(format!(
- "Unsupported operator {:?} on Bool",
- &expression.operator
- )),
- },
- ValueType::Timestamp => match expression.operator {
- WhenConditionOperator::Equal | WhenConditionOperator::NotEqual => {
- if let Expression::Atom(atom) = &cel_value {
- match atom {
- Atom::String(_) => Ok(Expression::FunctionCall(
- Expression::Ident("timestamp".to_string().into()).into(),
- None,
- [cel_value].to_vec(),
- )),
- _ => Err(format!("Can't compare {cel_value:?} with Timestamp")),
- }
- } else {
- Err(format!("Can't compare {cel_value:?} with Bool"))
- }
- }
- _ => Err(format!(
- "Unsupported operator {:?} on Bytes",
- &expression.operator
- )),
- },
- _ => Err(format!(
- "Still needs support for values of type `{cel_type}`"
- )),
- }?;
-
- let expression = match expression.operator {
- WhenConditionOperator::Equal => Expression::Relation(
- Expression::Ident(Arc::new("attribute".to_string())).into(),
- RelationOp::Equals,
- value.into(),
- ),
- WhenConditionOperator::NotEqual => Expression::Relation(
- Expression::Ident(Arc::new("attribute".to_string())).into(),
- RelationOp::NotEquals,
- value.into(),
- ),
- WhenConditionOperator::StartsWith => Expression::FunctionCall(
- Expression::Ident(Arc::new("startsWith".to_string())).into(),
- Some(Expression::Ident("attribute".to_string().into()).into()),
- [value].to_vec(),
- ),
- WhenConditionOperator::EndsWith => Expression::FunctionCall(
- Expression::Ident(Arc::new("endsWith".to_string())).into(),
- Some(Expression::Ident("attribute".to_string().into()).into()),
- [value].to_vec(),
- ),
- WhenConditionOperator::Matches => Expression::FunctionCall(
- Expression::Ident(Arc::new("matches".to_string())).into(),
- Some(Expression::Ident("attribute".to_string().into()).into()),
- [value].to_vec(),
- ),
- };
-
- Ok(Self {
- expression,
- cel_type,
- })
- }
-}
-
-pub fn type_of(path: &str) -> Option {
- match path {
- "request.time" => Some(ValueType::Timestamp),
- "request.id" => Some(ValueType::String),
- "request.protocol" => Some(ValueType::String),
- "request.scheme" => Some(ValueType::String),
- "request.host" => Some(ValueType::String),
- "request.method" => Some(ValueType::String),
- "request.path" => Some(ValueType::String),
- "request.url_path" => Some(ValueType::String),
- "request.query" => Some(ValueType::String),
- "request.referer" => Some(ValueType::String),
- "request.useragent" => Some(ValueType::String),
- "request.body" => Some(ValueType::String),
- "source.address" => Some(ValueType::String),
- "source.service" => Some(ValueType::String),
- "source.principal" => Some(ValueType::String),
- "source.certificate" => Some(ValueType::String),
- "destination.address" => Some(ValueType::String),
- "destination.service" => Some(ValueType::String),
- "destination.principal" => Some(ValueType::String),
- "destination.certificate" => Some(ValueType::String),
- "connection.requested_server_name" => Some(ValueType::String),
- "connection.tls_session.sni" => Some(ValueType::String),
- "connection.tls_version" => Some(ValueType::String),
- "connection.subject_local_certificate" => Some(ValueType::String),
- "connection.subject_peer_certificate" => Some(ValueType::String),
- "connection.dns_san_local_certificate" => Some(ValueType::String),
- "connection.dns_san_peer_certificate" => Some(ValueType::String),
- "connection.uri_san_local_certificate" => Some(ValueType::String),
- "connection.uri_san_peer_certificate" => Some(ValueType::String),
- "connection.sha256_peer_certificate_digest" => Some(ValueType::String),
- "ratelimit.domain" => Some(ValueType::String),
- "request.size" => Some(ValueType::Int),
- "source.port" => Some(ValueType::Int),
- "destination.port" => Some(ValueType::Int),
- "connection.id" => Some(ValueType::Int),
- "ratelimit.hits_addend" => Some(ValueType::Int),
- "request.headers" => Some(ValueType::Map),
- "request.context_extensions" => Some(ValueType::Map),
- "source.labels" => Some(ValueType::Map),
- "destination.labels" => Some(ValueType::Map),
- "filter_state" => Some(ValueType::Map),
- "connection.mtls" => Some(ValueType::Bool),
- "request.raw_body" => Some(ValueType::Bytes),
- "auth.identity" => Some(ValueType::Bytes),
- _ => None,
- }
-}
-
pub struct FilterConfig {
pub index: ActionSetIndex,
pub services: Rc>>,
@@ -460,12 +86,6 @@ impl TryFrom for FilterConfig {
fn try_from(config: PluginConfiguration) -> Result {
let mut index = ActionSetIndex::new();
for action_set in config.action_sets.iter() {
- for pe in &action_set.route_rule_conditions.matches {
- let result = pe.compile();
- if result.is_err() {
- return Err(result.err().unwrap());
- }
- }
let mut predicates = Vec::default();
for predicate in &action_set.route_rule_conditions.predicates {
predicates.push(Predicate::route_rule(predicate).map_err(|e| e.to_string())?);
@@ -476,12 +96,6 @@ impl TryFrom for FilterConfig {
.set(predicates)
.expect("Predicates must not be compiled yet!");
for action in &action_set.actions {
- for condition in &action.conditions {
- let result = condition.compile();
- if result.is_err() {
- 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())?);
@@ -621,22 +235,11 @@ mod test {
"name": "rlp-ns-A/rlp-name-A",
"routeRuleConditions": {
"hostnames": ["*.toystore.com", "example.com"],
- "matches": [
- {
- "selector": "request.path",
- "operator": "eq",
- "value": "/admin/toy"
- },
- {
- "selector": "request.method",
- "operator": "eq",
- "value": "POST"
- },
- {
- "selector": "request.host",
- "operator": "eq",
- "value": "cars.toystore.com"
- }]
+ "predicates": [
+ "request.path == '/admin/toy'",
+ "request.method == 'POST'",
+ "request.host == 'cars.toystore.com'"
+ ]
},
"actions": [
{
@@ -646,12 +249,9 @@ mod test {
{
"service": "limitador",
"scope": "rlp-ns-A/rlp-name-A",
- "conditions": [
- {
- "selector": "auth.metadata.username",
- "operator": "eq",
- "value": "alice"
- }],
+ "predicates": [
+ "auth.metadata.username == 'alice'"
+ ],
"data": [
{
"static": {
@@ -701,8 +301,10 @@ mod test {
panic!()
}
- let matches = &filter_config.action_sets[0].route_rule_conditions.matches;
- assert_eq!(matches.len(), 3);
+ let predicates = &filter_config.action_sets[0]
+ .route_rule_conditions
+ .predicates;
+ assert_eq!(predicates.len(), 3);
let actions = &filter_config.action_sets[0].actions;
assert_eq!(actions.len(), 2);
@@ -721,8 +323,8 @@ mod test {
let rl_data_items = &rl_action.data;
assert_eq!(rl_data_items.len(), 2);
- let rl_conditions = &rl_action.conditions;
- assert_eq!(rl_conditions.len(), 1);
+ let rl_predicates = &rl_action.predicates;
+ assert_eq!(rl_predicates.len(), 1);
// TODO(eastizle): DataItem does not implement PartialEq, add it only for testing?
//assert_eq!(
@@ -767,177 +369,7 @@ mod test {
}
#[test]
- fn parse_config_data_selector() {
- let config = r#"{
- "services": {
- "limitador": {
- "type": "ratelimit",
- "endpoint": "limitador-cluster",
- "failureMode": "deny"
- }
- },
- "actionSets": [
- {
- "name": "rlp-ns-A/rlp-name-A",
- "routeRuleConditions": {
- "hostnames": ["*.toystore.com", "example.com"]
- },
- "actions": [
- {
- "service": "limitador",
- "scope": "rlp-ns-A/rlp-name-A",
- "data": [
- {
- "selector": {
- "selector": "my.selector.path",
- "key": "mykey",
- "default": "my_selector_default_value"
- }
- }]
- }]
- }]
- }"#;
- let res = serde_json::from_str::(config);
- if let Err(ref e) = res {
- eprintln!("{e}");
- }
- assert!(res.is_ok());
-
- let filter_config = res.unwrap();
- assert_eq!(filter_config.action_sets.len(), 1);
-
- let actions = &filter_config.action_sets[0].actions;
- assert_eq!(actions.len(), 1);
-
- let data_items = &actions[0].data;
- assert_eq!(data_items.len(), 1);
-
- if let DataType::Selector(selector_item) = &data_items[0].item {
- assert_eq!(selector_item.selector, "my.selector.path");
- assert_eq!(selector_item.key.as_ref().unwrap(), "mykey");
- assert_eq!(
- selector_item.default.as_ref().unwrap(),
- "my_selector_default_value"
- );
- } else {
- panic!();
- }
- }
-
- #[test]
- fn parse_config_condition_selector_operators() {
- let config = r#"{
- "services": {
- "limitador": {
- "type": "ratelimit",
- "endpoint": "limitador-cluster",
- "failureMode": "deny"
- }
- },
- "actionSets": [
- {
- "name": "rlp-ns-A/rlp-name-A",
- "routeRuleConditions": {
- "hostnames": ["*.toystore.com", "example.com"],
- "matches": [
- {
- "selector": "request.path",
- "operator": "eq",
- "value": "/admin/toy"
- },
- {
- "selector": "request.method",
- "operator": "neq",
- "value": "POST"
- },
- {
- "selector": "request.host",
- "operator": "startswith",
- "value": "cars."
- },
- {
- "selector": "request.host",
- "operator": "endswith",
- "value": ".com"
- },
- {
- "selector": "request.host",
- "operator": "matches",
- "value": "*.com"
- }]
- },
- "actions": [
- {
- "service": "limitador",
- "scope": "rlp-ns-A/rlp-name-A",
- "conditions": [
- {
- "selector": "auth.metadata.username",
- "operator": "eq",
- "value": "alice"
- },
- {
- "selector": "request.path",
- "operator": "endswith",
- "value": "/car"
- }],
- "data": [ { "selector": { "selector": "my.selector.path" } }]
- }]
- }]
- }"#;
- let res = serde_json::from_str::(config);
- if let Err(ref e) = res {
- eprintln!("{e}");
- }
- assert!(res.is_ok());
-
- let filter_config = res.unwrap();
- assert_eq!(filter_config.action_sets.len(), 1);
-
- let matches = &filter_config.action_sets[0].route_rule_conditions.matches;
- assert_eq!(matches.len(), 5);
-
- let expected_matches = [
- // selector, value, operator
- ("request.path", "/admin/toy", WhenConditionOperator::Equal),
- ("request.method", "POST", WhenConditionOperator::NotEqual),
- ("request.host", "cars.", WhenConditionOperator::StartsWith),
- ("request.host", ".com", WhenConditionOperator::EndsWith),
- ("request.host", "*.com", WhenConditionOperator::Matches),
- ];
-
- for (m, (expected_selector, expected_value, expected_operator)) in
- matches.iter().zip(expected_matches.iter())
- {
- assert_eq!(m.selector, *expected_selector);
- assert_eq!(m.value, *expected_value);
- assert_eq!(m.operator, *expected_operator);
- }
-
- let conditions = &filter_config.action_sets[0].actions[0].conditions;
- assert_eq!(conditions.len(), 2);
-
- let expected_conditions = [
- // selector, value, operator
- (
- "auth.metadata.username",
- "alice",
- WhenConditionOperator::Equal,
- ),
- ("request.path", "/car", WhenConditionOperator::EndsWith),
- ];
-
- for (condition, (expected_selector, expected_value, expected_operator)) in
- conditions.iter().zip(expected_conditions.iter())
- {
- assert_eq!(condition.selector, *expected_selector);
- assert_eq!(condition.value, *expected_value);
- assert_eq!(condition.operator, *expected_operator);
- }
- }
-
- #[test]
- fn parse_config_conditions_optional() {
+ fn parse_config_predicates_optional() {
let config = r#"{
"services": {
"limitador": {
@@ -964,8 +396,9 @@ mod test {
}
},
{
- "selector": {
- "selector": "auth.metadata.username"
+ "expression": {
+ "key": "username",
+ "value": "auth.metadata.username"
}
}]
}]
@@ -986,8 +419,16 @@ mod test {
Timeout(Duration::from_millis(20))
);
- let matches = &filter_config.action_sets[0].route_rule_conditions.matches;
- assert_eq!(matches.len(), 0);
+ let predicates = &filter_config.action_sets[0]
+ .route_rule_conditions
+ .predicates;
+ assert_eq!(predicates.len(), 0);
+
+ let actions = &filter_config.action_sets[0].actions;
+ assert_eq!(actions.len(), 1);
+
+ let action_predicates = &actions[0].predicates;
+ assert_eq!(action_predicates.len(), 0);
}
#[test]
@@ -1017,8 +458,9 @@ mod test {
"key": "rlp-ns-A/rlp-name-A",
"value": "1"
},
- "selector": {
- "selector": "auth.metadata.username"
+ "expression": {
+ "key": "username",
+ "value": "auth.metadata.username"
}
}]
}]
@@ -1058,38 +500,6 @@ mod test {
}"#;
let res = serde_json::from_str::(bad_config);
assert!(res.is_err());
-
- // condition selector operator unknown
- let bad_config = r#"{
- "services": {
- "limitador": {
- "type": "ratelimit",
- "endpoint": "limitador-cluster",
- "failureMode": "deny"
- }
- },
- "actionSets": [
- {
- "name": "rlp-ns-A/rlp-name-A",
- "routeRuleConditions": {
- "hostnames": ["*.toystore.com", "example.com"],
- "matches": [
- {
- "selector": "request.path",
- "operator": "unknown",
- "value": "/admin/toy"
- }]
- },
- "actions": [
- {
- "service": "limitador",
- "scope": "rlp-ns-A/rlp-name-A",
- "data": [ { "selector": { "selector": "my.selector.path" } }]
- }]
- }]
- }"#;
- let res = serde_json::from_str::(bad_config);
- assert!(res.is_err());
}
#[test]
@@ -1115,170 +525,4 @@ mod test {
let rlp_option = filter_config.index.get_longest_match_action_sets("unknown");
assert!(rlp_option.is_none());
}
-
- mod pattern_expressions {
- use crate::configuration::{PatternExpression, WhenConditionOperator};
-
- #[test]
- fn test_legacy_string() {
- let p = PatternExpression {
- selector: "request.id".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "request_id".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval("request_id".as_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_proper_string() {
- let p = PatternExpression {
- selector: "request.id".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "\"request_id\"".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval("request_id".as_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_proper_int_as_string() {
- let p = PatternExpression {
- selector: "request.id".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "123".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval("123".as_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_proper_string_inferred() {
- let p = PatternExpression {
- selector: "foobar".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "\"123\"".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval("123".as_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_int() {
- let p = PatternExpression {
- selector: "destination.port".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "8080".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval(8080_i64.to_le_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_int_inferred() {
- let p = PatternExpression {
- selector: "foobar".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "8080".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval(8080_i64.to_le_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_float_inferred() {
- let p = PatternExpression {
- selector: "foobar".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "1.0".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval(1_f64.to_le_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_bool() {
- let p = PatternExpression {
- selector: "connection.mtls".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "true".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval((true as u8).to_le_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
-
- #[test]
- fn test_timestamp() {
- let p = PatternExpression {
- selector: "request.time".to_string(),
- operator: WhenConditionOperator::Equal,
- value: "2023-05-28T00:00:00+00:00".to_string(),
- path: Default::default(),
- compiled: Default::default(),
- };
- p.compile().expect("Should compile fine!");
- assert_eq!(
- p.eval(1685232000000000000_i64.to_le_bytes().to_vec()),
- Ok(true),
- "Expression: {:?}",
- p.compiled.get()
- )
- }
- }
}
diff --git a/src/configuration/action.rs b/src/configuration/action.rs
index 3e634d2..58e893f 100644
--- a/src/configuration/action.rs
+++ b/src/configuration/action.rs
@@ -1,8 +1,8 @@
-use crate::configuration::{DataItem, DataType, PatternExpression};
+use crate::configuration::{DataItem, DataType};
use crate::data::Predicate;
use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry};
use cel_interpreter::Value;
-use log::{debug, error};
+use log::error;
use protobuf::RepeatedField;
use serde::Deserialize;
use std::cell::OnceCell;
@@ -13,8 +13,6 @@ pub struct Action {
pub service: String,
pub scope: String,
#[serde(default)]
- pub conditions: Vec,
- #[serde(default)]
pub predicates: Vec,
#[serde(skip_deserializing)]
pub compiled_predicates: OnceCell>,
@@ -28,10 +26,8 @@ impl Action {
.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
+ predicates.is_empty()
+ || predicates
.iter()
.enumerate()
.all(|(pos, predicate)| match predicate.test() {
@@ -41,7 +37,6 @@ impl Action {
panic!("Err out of this!")
}
})
- }
}
pub fn build_descriptors(&self) -> RepeatedField {
@@ -85,37 +80,6 @@ impl Action {
}
},
),
- DataType::Selector(selector_item) => {
- let descriptor_key = match &selector_item.key {
- None => selector_item.path().to_string(),
- Some(key) => key.to_owned(),
- };
-
- 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: {}",
- selector_item.path()
- );
- match &selector_item.default {
- None => return None, // skipping the entire descriptor
- Some(default_value) => default_value.clone(),
- }
- }
- // TODO(eastizle): not all fields are strings
- // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes
- Some(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()?,
- };
- (descriptor_key, value)
- }
};
let mut descriptor_entry = RateLimitDescriptor_Entry::new();
descriptor_entry.set_key(key);
@@ -127,3 +91,27 @@ impl Action {
Some(res)
}
}
+
+#[cfg(test)]
+mod test {
+ use crate::configuration::action::Action;
+ use std::cell::OnceCell;
+
+ #[test]
+ fn empty_predicates_do_apply() {
+ let compiled_predicates = OnceCell::new();
+ compiled_predicates
+ .set(Vec::default())
+ .expect("predicates must not be compiled yet!");
+
+ let action = Action {
+ service: String::from("svc1"),
+ scope: String::from("sc1"),
+ predicates: vec![],
+ compiled_predicates,
+ data: vec![],
+ };
+
+ assert!(action.conditions_apply())
+ }
+}
diff --git a/src/configuration/action_set.rs b/src/configuration/action_set.rs
index 04f34f7..1e8a61a 100644
--- a/src/configuration/action_set.rs
+++ b/src/configuration/action_set.rs
@@ -1,5 +1,4 @@
use crate::configuration::action::Action;
-use crate::configuration::PatternExpression;
use crate::data::Predicate;
use log::error;
use serde::Deserialize;
@@ -9,8 +8,6 @@ use std::cell::OnceCell;
pub struct RouteRuleConditions {
pub hostnames: Vec,
#[serde(default)]
- pub matches: Vec,
- #[serde(default)]
pub predicates: Vec,
#[serde(skip_deserializing)]
pub compiled_predicates: OnceCell>,
@@ -44,15 +41,8 @@ impl ActionSet {
.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
+ predicates.is_empty()
+ || predicates
.iter()
.enumerate()
.all(|(pos, predicate)| match predicate.test() {
@@ -65,6 +55,26 @@ impl ActionSet {
panic!("Err out of this!")
}
})
- }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::configuration::action_set::ActionSet;
+
+ fn build_action_set(name: &str) -> ActionSet {
+ ActionSet::new(name.to_owned(), Default::default(), Vec::new())
+ }
+
+ #[test]
+ fn empty_route_rule_conditions_do_apply() {
+ let action_set_1 = build_action_set("as_1");
+ action_set_1
+ .route_rule_conditions
+ .compiled_predicates
+ .set(Vec::default())
+ .expect("Predicates must not be compiled yet!");
+
+ assert!(action_set_1.conditions_apply())
}
}
diff --git a/src/data/mod.rs b/src/data/mod.rs
index 0e1cc10..d61009f 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -4,10 +4,8 @@ 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
index 812edf8..06ad798 100644
--- a/src/data/property.rs
+++ b/src/data/property.rs
@@ -77,7 +77,7 @@ pub(super) fn host_get_property(path: &Path) -> Result