diff --git a/limitador-server/examples/envoy.yaml b/limitador-server/examples/envoy.yaml index 208d3c76..1a7d0eda 100644 --- a/limitador-server/examples/envoy.yaml +++ b/limitador-server/examples/envoy.yaml @@ -28,8 +28,8 @@ static_resources: rate_limits: - stage: 0 actions: - - {request_headers: {header_name: "userid", descriptor_key: "user_id"}} - - { request_headers: { header_name: ":method", descriptor_key: "req_method" } } + - { request_headers: { header_name: "userid", descriptor_key: "user_id" } } + - { request_headers: { header_name: ":method", descriptor_key: "descriptors[0]['method']" } } http_filters: - name: envoy.filters.http.ratelimit typed_config: diff --git a/limitador-server/examples/limits.yaml b/limitador-server/examples/limits.yaml index be60b900..c47aedf4 100644 --- a/limitador-server/examples/limits.yaml +++ b/limitador-server/examples/limits.yaml @@ -10,6 +10,6 @@ max_value: 5 seconds: 60 conditions: - - "req_method == 'POST'" + - "descriptors[0]['req.method'] == 'POST'" variables: - user_id diff --git a/limitador-server/sandbox/README.md b/limitador-server/sandbox/README.md index ca0cd9b9..76614284 100644 --- a/limitador-server/sandbox/README.md +++ b/limitador-server/sandbox/README.md @@ -99,11 +99,11 @@ bin/grpcurl -plaintext -d @ 127.0.0.1:18081 envoy.service.ratelimit.v3.RateLimit { "entries": [ { - "key": "req_method", + "key": "req.method", "value": "POST" }, { - "key": "req_path", + "key": "req.path", "value": "/" } ] @@ -125,11 +125,11 @@ while :; do bin/grpcurl -plaintext -d @ 127.0.0.1:18081 envoy.service.ratelimit. { "entries": [ { - "key": "req_method", + "key": "req.method", "value": "POST" }, { - "key": "req_path", + "key": "req.path", "value": "/" } ] diff --git a/limitador-server/sandbox/envoy.yaml b/limitador-server/sandbox/envoy.yaml index bf5ee863..7e678e10 100644 --- a/limitador-server/sandbox/envoy.yaml +++ b/limitador-server/sandbox/envoy.yaml @@ -27,10 +27,10 @@ static_resources: - actions: - request_headers: header_name: :method - descriptor_key: req_method + descriptor_key: req.method - request_headers: header_name: :path - descriptor_key: req_path + descriptor_key: req.path http_filters: - name: envoy.filters.http.ratelimit typed_config: diff --git a/limitador-server/sandbox/envoy2.yaml b/limitador-server/sandbox/envoy2.yaml index 351ba1d3..d1d15958 100644 --- a/limitador-server/sandbox/envoy2.yaml +++ b/limitador-server/sandbox/envoy2.yaml @@ -27,10 +27,10 @@ static_resources: - actions: - request_headers: header_name: :method - descriptor_key: req_method + descriptor_key: req.method - request_headers: header_name: :path - descriptor_key: req_path + descriptor_key: req.path http_filters: - name: envoy.filters.http.ratelimit typed_config: diff --git a/limitador-server/sandbox/envoy3.yaml b/limitador-server/sandbox/envoy3.yaml index ba557fb0..03180c47 100644 --- a/limitador-server/sandbox/envoy3.yaml +++ b/limitador-server/sandbox/envoy3.yaml @@ -27,10 +27,10 @@ static_resources: - actions: - request_headers: header_name: :method - descriptor_key: req_method + descriptor_key: req.method - request_headers: header_name: :path - descriptor_key: req_path + descriptor_key: req.path http_filters: - name: envoy.filters.http.ratelimit typed_config: diff --git a/limitador-server/sandbox/limits.yaml b/limitador-server/sandbox/limits.yaml index 853c0340..cb354bf2 100644 --- a/limitador-server/sandbox/limits.yaml +++ b/limitador-server/sandbox/limits.yaml @@ -3,20 +3,20 @@ max_value: 10 seconds: 60 conditions: - - "req_method == 'GET'" - - "req_path != '/json'" + - "descriptors[0]['req.method'] == 'GET'" + - "descriptors[0]['req.path'] != '/json'" variables: [] - namespace: test_namespace max_value: 5 seconds: 60 conditions: - - "req_method == 'POST'" - - "req_path != '/json'" + - "descriptors[0]['req.method'] == 'POST'" + - "descriptors[0]['req.path'] != '/json'" variables: [] - namespace: test_namespace max_value: 50000 seconds: 10 conditions: - - "req_method == 'GET'" - - "req_path == '/json'" + - "descriptors[0]['req.method'] == 'GET'" + - "descriptors[0]['req.path'] == '/json'" variables: [] diff --git a/limitador-server/sandbox/load-test.json b/limitador-server/sandbox/load-test.json index 0cd6c81b..b23422cc 100644 --- a/limitador-server/sandbox/load-test.json +++ b/limitador-server/sandbox/load-test.json @@ -5,11 +5,11 @@ { "entries": [ { - "key": "req_method", + "key": "req.method", "value": "GET" }, { - "key": "req_path", + "key": "req.path", "value": "/json" } ] diff --git a/limitador-server/sandbox/redis-otel/README.md b/limitador-server/sandbox/redis-otel/README.md index 1759437a..8b2f10ab 100644 --- a/limitador-server/sandbox/redis-otel/README.md +++ b/limitador-server/sandbox/redis-otel/README.md @@ -24,7 +24,7 @@ bin/grpcurl -plaintext -d @ 127.0.0.1:18081 envoy.service.ratelimit.v3.RateLimit { "entries": [ { - "key": "req_method", + "key": "req.method", "value": "POST" } ] diff --git a/limitador-server/src/envoy_rls/server.rs b/limitador-server/src/envoy_rls/server.rs index b6d24e1b..3a885c4c 100644 --- a/limitador-server/src/envoy_rls/server.rs +++ b/limitador-server/src/envoy_rls/server.rs @@ -3,12 +3,6 @@ use opentelemetry::propagation::Extractor; use std::collections::HashMap; use std::sync::Arc; -use limitador::CheckResult; -use tonic::codegen::http::HeaderMap; -use tonic::{transport, transport::Server, Request, Response, Status}; -use tracing::Span; -use tracing_opentelemetry::OpenTelemetrySpanExt; - use crate::envoy_rls::server::envoy::config::core::v3::HeaderValue; use crate::envoy_rls::server::envoy::service::ratelimit::v3::rate_limit_response::Code; use crate::envoy_rls::server::envoy::service::ratelimit::v3::rate_limit_service_server::{ @@ -19,6 +13,12 @@ use crate::envoy_rls::server::envoy::service::ratelimit::v3::{ }; use crate::prometheus_metrics::PrometheusMetrics; use crate::Limiter; +use limitador::limit::Context; +use limitador::CheckResult; +use tonic::codegen::http::HeaderMap; +use tonic::{transport, transport::Server, Request, Response, Status}; +use tracing::Span; +use tracing_opentelemetry::OpenTelemetrySpanExt; include!("envoy_types.rs"); @@ -72,7 +72,7 @@ impl RateLimitService for MyRateLimiter { ) -> Result, Status> { debug!("Request received: {:?}", request); - let mut values: HashMap = HashMap::new(); + let mut values: Vec> = Vec::default(); let (metadata, _ext, req) = request.into_parts(); let namespace = req.domain; let rl_headers = RateLimitRequestHeaders::new(metadata.into_headers()); @@ -96,9 +96,11 @@ impl RateLimitService for MyRateLimiter { let namespace = namespace.into(); for descriptor in &req.descriptors { + let mut map = HashMap::default(); for entry in &descriptor.entries { - values.insert(entry.key.clone(), entry.value.clone()); + map.insert(entry.key.clone(), entry.value.clone()); } + values.push(map); } // "hits_addend" is optional according to the spec, and should default @@ -109,7 +111,8 @@ impl RateLimitService for MyRateLimiter { req.hits_addend }; - let ctx = values.into(); + let mut ctx = Context::default(); + ctx.list_binding("descriptors".to_string(), values); let rate_limited_resp = match &*self.limiter { Limiter::Blocking(limiter) => limiter.check_rate_limited_and_update( @@ -255,8 +258,12 @@ mod tests { namespace, 1, 60, - vec!["req_method == 'GET'".try_into().expect("failed parsing!")], - vec!["app_id".try_into().expect("failed parsing!")], + vec!["descriptors[0]['req.method'] == 'GET'" + .try_into() + .expect("failed parsing!")], + vec!["descriptors[0]['app.id']" + .try_into() + .expect("failed parsing!")], ); let limiter = RateLimiter::new(10_000); @@ -276,11 +283,11 @@ mod tests { descriptors: vec![RateLimitDescriptor { entries: vec![ Entry { - key: "req_method".to_string(), + key: "req.method".to_string(), value: "GET".to_string(), }, Entry { - key: "app_id".to_string(), + key: "app.id".to_string(), value: "1".to_string(), }, ], @@ -337,7 +344,7 @@ mod tests { domain: "test_namespace".to_string(), descriptors: vec![RateLimitDescriptor { entries: vec![Entry { - key: "req_method".to_string(), + key: "req.method".to_string(), value: "GET".to_string(), }], limit: None, @@ -371,7 +378,7 @@ mod tests { domain: "".to_string(), descriptors: vec![RateLimitDescriptor { entries: vec![Entry { - key: "req_method".to_string(), + key: "req.method".to_string(), value: "GET".to_string(), }], limit: None, @@ -400,18 +407,18 @@ mod tests { namespace, 10, 60, - vec!["x == '1'".try_into().expect("failed parsing!")], - vec!["z".try_into().expect("failed parsing!")], + vec!["descriptors[0].x == '1'".try_into().expect("failed parsing!")], + vec!["descriptors[0].z".try_into().expect("failed parsing!")], ), Limit::new( namespace, 0, 60, vec![ - "x == '1'".try_into().expect("failed parsing!"), - "y == '2'".try_into().expect("failed parsing!"), + "descriptors[0].x == '1'".try_into().expect("failed parsing!"), + "descriptors[1].y == '2'".try_into().expect("failed parsing!"), ], - vec!["z".try_into().expect("failed parsing!")], + vec!["descriptors[0].z".try_into().expect("failed parsing!")], ), ] .into_iter() @@ -480,8 +487,8 @@ mod tests { namespace, 10, 60, - vec!["x == '1'".try_into().expect("failed parsing!")], - vec!["y".try_into().expect("failed parsing!")], + vec!["descriptors[0].x == '1'".try_into().expect("failed parsing!")], + vec!["descriptors[0].y".try_into().expect("failed parsing!")], ); let limiter = RateLimiter::new(10_000); @@ -555,8 +562,8 @@ mod tests { namespace, 1, 60, - vec!["x == '1'".try_into().expect("failed parsing!")], - vec!["y".try_into().expect("failed parsing!")], + vec!["descriptors[0].x == '1'".try_into().expect("failed parsing!")], + vec!["descriptors[0].y".try_into().expect("failed parsing!")], ); let limiter = RateLimiter::new(10_000); diff --git a/limitador-server/src/http_api/server.rs b/limitador-server/src/http_api/server.rs index ed413e53..219c3f10 100644 --- a/limitador-server/src/http_api/server.rs +++ b/limitador-server/src/http_api/server.rs @@ -14,6 +14,7 @@ use paperclip::actix::{ }; use std::fmt; use std::sync::Arc; +use limitador::limit::Context; struct RateLimitData { limiter: Arc, @@ -122,7 +123,8 @@ async fn check( response_headers: _, } = request.into_inner(); let namespace = namespace.into(); - let ctx = values.into(); + let mut ctx = Context::default(); + ctx.list_binding("descriptors".to_string(), vec![values]); let is_rate_limited_result = match state.get_ref().limiter() { Limiter::Blocking(limiter) => limiter.is_rate_limited(&namespace, &ctx, delta), Limiter::Async(limiter) => limiter.is_rate_limited(&namespace, &ctx, delta).await, @@ -153,7 +155,8 @@ async fn report( response_headers: _, } = request.into_inner(); let namespace = namespace.into(); - let ctx = values.into(); + let mut ctx = Context::default(); + ctx.list_binding("descriptors".to_string(), vec![values]); let update_counters_result = match data.get_ref().limiter() { Limiter::Blocking(limiter) => limiter.update_counters(&namespace, &ctx, delta), Limiter::Async(limiter) => limiter.update_counters(&namespace, &ctx, delta).await, @@ -178,7 +181,8 @@ async fn check_and_report( response_headers, } = request.into_inner(); let namespace = namespace.into(); - let ctx = values.into(); + let mut ctx = Context::default(); + ctx.list_binding("descriptors".to_string(), vec![values]); let rate_limit_data = data.get_ref(); let rate_limited_and_update_result = match rate_limit_data.limiter() { Limiter::Blocking(limiter) => limiter.check_rate_limited_and_update( @@ -382,8 +386,8 @@ mod tests { // Prepare values to check let mut values = HashMap::new(); - values.insert("req_method".into(), "GET".into()); - values.insert("app_id".into(), "1".into()); + values.insert("req.method".into(), "GET".into()); + values.insert("req.id".into(), "1".into()); let info = CheckAndReportInfo { namespace: namespace.into(), values, @@ -433,8 +437,8 @@ mod tests { // Prepare values to check let mut values = HashMap::new(); - values.insert("req_method".into(), "GET".into()); - values.insert("app_id".into(), "1".into()); + values.insert("req.method".into(), "GET".into()); + values.insert("app.id".into(), "1".into()); let info = CheckAndReportInfo { namespace: namespace.into(), values, @@ -508,8 +512,8 @@ mod tests { // Prepare values to check let mut values = HashMap::new(); - values.insert("req_method".into(), "GET".into()); - values.insert("app_id".into(), "1".into()); + values.insert("req.method".into(), "GET".into()); + values.insert("app.id".into(), "1".into()); let info = CheckAndReportInfo { namespace: namespace.into(), values, @@ -551,8 +555,12 @@ mod tests { namespace, max, 60, - vec!["req_method == 'GET'".try_into().expect("failed parsing!")], - vec!["app_id".try_into().expect("failed parsing!")], + vec!["descriptors[0]['req.method'] == 'GET'" + .try_into() + .expect("failed parsing!")], + vec!["descriptors[0]['app.id']" + .try_into() + .expect("failed parsing!")], ); match &limiter {