diff --git a/src/settings.rs b/src/settings.rs index fc4ebd22..593c66dc 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -10,7 +10,8 @@ use std::path::{Path, PathBuf}; #[derive(Deserialize, Debug, Clone)] pub struct Policy { pub url: String, - + #[serde(rename = "allowedToMutate")] + pub allowed_to_mutate: Option, #[serde(skip)] pub wasm_module_path: PathBuf, @@ -65,10 +66,46 @@ example: assert!(!policies.is_empty()); let policy = policies.get("example").unwrap(); + assert!(policy.allowed_to_mutate.is_none()); let settings = policy.settings(); assert!(settings.is_some()); } + #[test] + fn test_allowed_to_mutate_settings() { + let input = r#" +--- +example: + url: file:///tmp/namespace-validate-policy.wasm + allowedToMutate: true + settings: + valid_namespace: valid +"#; + let policies: HashMap = serde_yaml::from_str(input).unwrap(); + assert!(!policies.is_empty()); + + let policy = policies.get("example").unwrap(); + assert!(policy.allowed_to_mutate.unwrap()); + let settings = policy.settings(); + assert!(settings.is_some()); + + let input2 = r#" +--- +example: + url: file:///tmp/namespace-validate-policy.wasm + allowedToMutate: false + settings: + valid_namespace: valid +"#; + let policies2: HashMap = serde_yaml::from_str(input2).unwrap(); + assert!(!policies2.is_empty()); + + let policy2 = policies2.get("example").unwrap(); + assert_eq!(policy2.allowed_to_mutate.unwrap(), false); + let settings2 = policy2.settings(); + assert!(settings2.is_some()); + } + #[test] fn get_settings_when_empty_map_is_provided() { let input = r#" diff --git a/src/worker.rs b/src/worker.rs index 3a1ba442..e8f7ed6c 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -5,7 +5,7 @@ use policy_evaluator::{ policy_evaluator::{PolicyEvaluator, ValidateRequest}, policy_evaluator_builder::PolicyEvaluatorBuilder, policy_metadata::Metadata, - validation_response::ValidationResponse, + validation_response::{ValidationResponse, ValidationResponseStatus}, }; use std::collections::HashMap; use std::fmt; @@ -18,8 +18,13 @@ use crate::metrics; use crate::settings::Policy; use crate::utils::convert_yaml_map_to_json; +struct PolicyEvaluatorWithSettings { + policy_evaluator: PolicyEvaluator, + allowed_to_mutate: Option, +} + pub(crate) struct Worker { - evaluators: HashMap, + evaluators: HashMap, channel_rx: Receiver, } @@ -118,7 +123,12 @@ impl Worker { continue; } - evs.insert(id.to_string(), policy_evaluator); + let policy_evaluator_with_settings = PolicyEvaluatorWithSettings { + policy_evaluator, + allowed_to_mutate: policy.allowed_to_mutate, + }; + + evs.insert(id.to_string(), policy_evaluator_with_settings); } if !evs_errors.is_empty() { @@ -137,7 +147,10 @@ impl Worker { let _enter = span.enter(); let res = match self.evaluators.get_mut(&req.policy_id) { - Some(policy_evaluator) => match serde_json::to_value(req.req.clone()) { + Some(PolicyEvaluatorWithSettings { + policy_evaluator, + allowed_to_mutate, + }) => match serde_json::to_value(req.req.clone()) { Ok(json) => { let start_time = Instant::now(); let resp = policy_evaluator.validate(ValidateRequest::new(json)); @@ -149,6 +162,21 @@ impl Worker { } else { None }; + let resp = if mutated && allowed_to_mutate.as_ref() == Some(&false) { + ValidationResponse { + allowed: false, + status: Some(ValidationResponseStatus { + message: Some(format!("Request rejected by policy {}. The policy attempted to mutate the request, but it is currently configured to not allow mutations.", &req.policy_id)), + code: None, + }), + // if `allowed_to_mutate` is false, we are in a validating webhook. If we send a patch, k8s will fail because validating webhook do not expect this field + patch: None, + patch_type: None, + ..resp + } + } else { + resp + }; let res = req.resp_chan.send(Some(resp)); let policy_evaluation = metrics::PolicyEvaluation { policy_name: policy_evaluator.policy.id.clone(),