Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Predicate on our "well-known attributes" #113

Merged
merged 26 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
271b464
Refactored Attribute to data::AttributeValue
alexsnaps Oct 19, 2024
762091d
Refactored property stuff to data::property
alexsnaps Oct 19, 2024
78d5eb0
Refactored using PropertyPath everywhere
alexsnaps Oct 19, 2024
7624900
Refactored using AttributeValue everywhere
alexsnaps Oct 19, 2024
54ad632
ports are u64, not strings... this err'ed into Default
alexsnaps Oct 19, 2024
eede19a
Slowly getting there
alexsnaps Oct 20, 2024
222a7cf
Wired the base
alexsnaps Oct 20, 2024
f4fd18f
Clippy fixes
alexsnaps Oct 21, 2024
34c7050
Notes for auth/json integration
alexsnaps Oct 21, 2024
df48e11
Typos and test infra for hostcalls::get_property
alexsnaps Oct 21, 2024
8c001f3
Predicate test
alexsnaps Oct 21, 2024
ce49eb2
Transparently replace PatternExpression with CEL Predicate
alexsnaps Oct 21, 2024
60820e0
Moar clippy
alexsnaps Oct 21, 2024
067aaf2
Deal with unknown attributes as JSON literals
alexsnaps Oct 22, 2024
06000e0
Arguable: but lets fall back to string on bad json
alexsnaps Oct 22, 2024
7dc7ac9
wasm props are in the filter_state
alexsnaps Oct 23, 2024
611a900
Deal with more types & json encode values
alexsnaps Oct 23, 2024
f911798
Have auth in wasm prop and log on unexpected type
alexsnaps Oct 23, 2024
dbdf7e1
Deal with previously resolved values on path digging into these
alexsnaps Oct 23, 2024
5deae3b
Don't warn on structs when storing values as json
alexsnaps Oct 23, 2024
a786e94
Support headers in CEL
alexsnaps Oct 23, 2024
00d4863
Remove trailing comment
alexsnaps Oct 23, 2024
b63057f
No more dead code
alexsnaps Oct 23, 2024
b9af3dc
Support for CEL expressions in data exports
alexsnaps Oct 24, 2024
7528942
request.time is a Timestamp
alexsnaps Oct 24, 2024
06cd03d
README reflecting new config
alexsnaps Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make key optional? it would default to the expression value (not the referenced value, though).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes for a very horrible default, but... 🤷

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:
eguzki marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading