Skip to content

Commit

Permalink
Merge pull request #113 from Kuadrant/known_attr
Browse files Browse the repository at this point in the history
Predicate on our "well-known attributes"
  • Loading branch information
eguzki authored Oct 25, 2024
2 parents f5caadd + 06cd03d commit fcff575
Show file tree
Hide file tree
Showing 18 changed files with 1,086 additions and 334 deletions.
69 changes: 52 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!)

<details>

While still supported, these will eventually disappear. For now though, you still can express them as such:

```yaml
services:
auth-service:
Expand Down Expand Up @@ -46,10 +90,6 @@ actionSets:
value: "1"
```

## Features
#### Condition operators implemented
```Rust
#[derive(Deserialize, PartialEq, Debug, Clone)]
pub enum WhenConditionOperator {
Expand All @@ -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.

Expand Down Expand Up @@ -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.

</details>


### 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

Expand Down
85 changes: 63 additions & 22 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<data::Expression>,
}

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
Expand All @@ -38,7 +56,7 @@ pub struct SelectorItem {
pub default: Option<String>,

#[serde(skip_deserializing)]
path: OnceCell<Path>,
path: OnceCell<PropertyPath>,
}

impl SelectorItem {
Expand All @@ -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!")
Expand All @@ -67,13 +85,15 @@ pub struct StaticItem {
pub enum DataType {
Static(StaticItem),
Selector(SelectorItem),
Expression(ExpressionItem),
}

impl DataType {
pub fn compile(&self) -> Result<(), String> {
match self {
DataType::Static(_) => Ok(()),
DataType::Selector(selector) => selector.compile(),
DataType::Expression(exp) => exp.compile(),
}
}
}
Expand Down Expand Up @@ -107,7 +127,7 @@ pub struct PatternExpression {
pub value: String,

#[serde(skip_deserializing)]
path: OnceCell<Path>,
path: OnceCell<PropertyPath>,
#[serde(skip_deserializing)]
compiled: OnceCell<CelExpression>,
}
Expand All @@ -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<u8>) -> Result<bool, String> {
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 => {}
Expand All @@ -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!(
Expand All @@ -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
Expand Down Expand Up @@ -443,13 +466,31 @@ impl TryFrom<PluginConfiguration> 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();
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())?);
}
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() {
Expand Down Expand Up @@ -619,8 +660,9 @@ mod test {
}
},
{
"selector": {
"selector": "auth.metadata.username"
"expression": {
"key": "username",
"value": "auth.metadata.username"
}
}]
}]
Expand Down Expand Up @@ -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!();
}
Expand Down
Loading

0 comments on commit fcff575

Please sign in to comment.