From abd2ff86248a9823b63a620f6f7493b1771df6d5 Mon Sep 17 00:00:00 2001 From: Adam Cattermole Date: Thu, 29 Aug 2024 13:30:12 +0100 Subject: [PATCH] Add and use extensions from configuration Signed-off-by: Adam Cattermole --- src/configuration.rs | 119 ++++++++++++++++++++++++++----------- src/filter/http_context.rs | 28 ++++++--- src/policy.rs | 10 +--- src/policy_index.rs | 8 +-- src/service.rs | 17 +++++- 5 files changed, 123 insertions(+), 59 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index a3436dde..f3b8b6e1 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,4 +1,5 @@ use std::cell::OnceCell; +use std::collections::HashMap; use std::fmt::{Debug, Display, Formatter}; use std::rc::Rc; use std::sync::Arc; @@ -441,17 +442,14 @@ pub fn type_of(path: &str) -> Option { pub struct FilterConfig { pub index: PolicyIndex, - // Deny/Allow request when faced with an irrecoverable failure. - pub failure_mode: FailureMode, - pub service: Rc, + pub services: Rc>>, } impl Default for FilterConfig { fn default() -> Self { Self { index: PolicyIndex::new(), - failure_mode: FailureMode::Deny, - service: Rc::new(GrpcService::default()), + services: Rc::new(HashMap::new()), } } } @@ -484,21 +482,30 @@ impl TryFrom for FilterConfig { } } - // todo(adam-cattermole): retrieve from config - let rl_service = - GrpcService::new(ExtensionType::RateLimit, config.policies[0].service.clone()); + // configure grpc services from the extensions in config + let mut services = HashMap::new(); + for (name, ext) in config.extensions { + services.insert( + name, + Rc::new(GrpcService::new( + ext.extension_type, + ext.endpoint, + ext.failure_mode, + )), + ); + } Ok(Self { index, - failure_mode: config.failure_mode, - service: Rc::new(rl_service), + services: Rc::new(services), }) } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Default)] #[serde(rename_all = "lowercase")] pub enum FailureMode { + #[default] Deny, Allow, } @@ -513,8 +520,16 @@ pub enum ExtensionType { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct PluginConfiguration { - #[serde(rename = "rateLimitPolicies")] + pub extensions: HashMap, pub policies: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Extension { + #[serde(rename = "type")] + pub extension_type: ExtensionType, + pub endpoint: String, // Deny/Allow request when faced with an irrecoverable failure. pub failure_mode: FailureMode, } @@ -524,12 +539,17 @@ mod test { use super::*; const CONFIG: &str = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { @@ -621,8 +641,8 @@ mod test { #[test] fn parse_config_min() { let config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [] + "extensions": {}, + "policies": [] }"#; let res = serde_json::from_str::(config); if let Err(ref e) = res { @@ -637,12 +657,17 @@ mod test { #[test] fn parse_config_data_selector() { let config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { @@ -687,12 +712,17 @@ mod test { #[test] fn parse_config_condition_selector_operators() { let config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { @@ -766,12 +796,17 @@ mod test { #[test] fn parse_config_conditions_optional() { let config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { @@ -810,12 +845,17 @@ mod test { fn parse_config_invalid_data() { // data item fields are mutually exclusive let bad_config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { @@ -837,8 +877,14 @@ mod test { // data item unknown fields are forbidden let bad_config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", @@ -861,12 +907,17 @@ mod test { // condition selector operator unknown let bad_config = r#"{ - "failureMode": "deny", - "rateLimitPolicies": [ + "extensions": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador-cluster", + "failureMode": "deny" + } + }, + "policies": [ { "name": "rlp-ns-A/rlp-name-A", "domain": "rlp-ns-A/rlp-name-A", - "service": "limitador-cluster", "hostnames": ["*.toystore.com", "example.com"], "rules": [ { diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs index 531ab7f8..fa80e75c 100644 --- a/src/filter/http_context.rs +++ b/src/filter/http_context.rs @@ -40,13 +40,19 @@ impl Filter { return Action::Continue; } - let rls = GrpcServiceHandler::new( - Rc::clone(&self.config.service), - Rc::clone(&self.header_resolver), - ); + // todo(adam-cattermole): For now we just get the first GrpcService but we expect to have + // an action which links to the service that should be used + let rls = self + .config + .services + .values() + .next() + .expect("expect a value"); + + let handler = GrpcServiceHandler::new(Rc::clone(rls), Rc::clone(&self.header_resolver)); let message = RateLimitService::message(rlp.domain.clone(), descriptors); - match rls.send(message) { + match handler.send(message) { Ok(call_id) => { debug!( "#{} initiated gRPC call (id# {}) to Limitador", @@ -56,7 +62,7 @@ impl Filter { } Err(e) => { warn!("gRPC call to Limitador failed! {e:?}"); - if let FailureMode::Deny = self.config.failure_mode { + if let FailureMode::Deny = rls.failure_mode() { self.send_http_response(500, vec![], Some(b"Internal Server Error.\n")) } Action::Continue @@ -65,7 +71,15 @@ impl Filter { } fn handle_error_on_grpc_response(&self) { - match &self.config.failure_mode { + // todo(adam-cattermole): We need a method of knowing which service is the one currently + // being used (the current action) so that we can get the failure mode + let rls = self + .config + .services + .values() + .next() + .expect("expect a value"); + match rls.failure_mode() { FailureMode::Deny => { self.send_http_response(500, vec![], Some(b"Internal Server Error.\n")) } diff --git a/src/policy.rs b/src/policy.rs index 7b894c16..7505b8fd 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -26,24 +26,16 @@ pub struct Rule { pub struct Policy { pub name: String, pub domain: String, - pub service: String, pub hostnames: Vec, pub rules: Vec, } impl Policy { #[cfg(test)] - pub fn new( - name: String, - domain: String, - service: String, - hostnames: Vec, - rules: Vec, - ) -> Self { + pub fn new(name: String, domain: String, hostnames: Vec, rules: Vec) -> Self { Policy { name, domain, - service, hostnames, rules, } diff --git a/src/policy_index.rs b/src/policy_index.rs index 4a1bf607..58b31d94 100644 --- a/src/policy_index.rs +++ b/src/policy_index.rs @@ -41,13 +41,7 @@ mod tests { use crate::policy_index::PolicyIndex; fn build_ratelimit_policy(name: &str) -> Policy { - Policy::new( - name.to_owned(), - "".to_owned(), - "".to_owned(), - Vec::new(), - Vec::new(), - ) + Policy::new(name.to_owned(), "".to_owned(), Vec::new(), Vec::new()) } #[test] diff --git a/src/service.rs b/src/service.rs index e0b01681..e6b13d61 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,7 +1,7 @@ pub(crate) mod auth; pub(crate) mod rate_limit; -use crate::configuration::ExtensionType; +use crate::configuration::{ExtensionType, FailureMode}; use crate::service::auth::{AUTH_METHOD_NAME, AUTH_SERVICE_NAME}; use crate::service::rate_limit::{RATELIMIT_METHOD_NAME, RATELIMIT_SERVICE_NAME}; use crate::service::TracingHeader::{Baggage, Traceparent, Tracestate}; @@ -18,20 +18,23 @@ pub struct GrpcService { endpoint: String, name: &'static str, method: &'static str, + failure_mode: FailureMode, } impl GrpcService { - pub fn new(extension_type: ExtensionType, endpoint: String) -> Self { + pub fn new(extension_type: ExtensionType, endpoint: String, failure_mode: FailureMode) -> Self { match extension_type { ExtensionType::Auth => Self { endpoint, name: AUTH_SERVICE_NAME, method: AUTH_METHOD_NAME, + failure_mode, }, ExtensionType::RateLimit => Self { endpoint, name: RATELIMIT_SERVICE_NAME, method: RATELIMIT_METHOD_NAME, + failure_mode, }, } } @@ -44,8 +47,12 @@ impl GrpcService { fn method(&self) -> &str { self.method } + pub fn failure_mode(&self) -> &FailureMode { + &self.failure_mode + } } +#[derive(Default)] pub struct GrpcServiceHandler { service: Rc, header_resolver: Rc, @@ -83,6 +90,12 @@ pub struct HeaderResolver { headers: OnceCell>, } +impl Default for HeaderResolver { + fn default() -> Self { + Self::new() + } +} + impl HeaderResolver { pub fn new() -> Self { Self {