From 23059addeedb0ef45437976b6fa6bde82ba0c219 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 11:17:41 +0200 Subject: [PATCH 1/6] feat: add AdmissionRequest type Signed-off-by: Fabrizio Sestito --- src/admission_request.rs | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/admission_request.rs diff --git a/src/admission_request.rs b/src/admission_request.rs new file mode 100644 index 00000000..b8bd696a --- /dev/null +++ b/src/admission_request.rs @@ -0,0 +1,43 @@ +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdmissionRequest { + pub uid: String, + pub kind: GroupVersionKind, + pub resource: GroupVersionResource, + #[serde(skip_serializing_if = "Option::is_none")] + pub sub_resource: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub request_kind: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub request_resource: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub request_sub_resource: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub namespace: Option, + pub operation: String, + pub user_info: k8s_openapi::api::authentication::v1::UserInfo, + #[serde(skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub old_object: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dry_run: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct GroupVersionKind { + pub group: String, + pub version: String, + pub kind: String, +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct GroupVersionResource { + pub group: String, + pub version: String, + pub resource: String, +} From 8b8f0be148c86e820bc671d4bedd778160a66858 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 11:17:23 +0200 Subject: [PATCH 2/6] feat: refactor ValidateRequest to an Enum and add Raw request variant Signed-off-by: Fabrizio Sestito --- src/lib.rs | 1 + src/policy_evaluator.rs | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1170b77f..820eb255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub extern crate burrego; extern crate wasmparser; +pub mod admission_request; pub mod admission_response; pub mod callback_handler; pub mod callback_requests; diff --git a/src/policy_evaluator.rs b/src/policy_evaluator.rs index 090ae23d..c1c3abc2 100644 --- a/src/policy_evaluator.rs +++ b/src/policy_evaluator.rs @@ -5,6 +5,7 @@ use serde::Serialize; use serde_json::value; use std::{convert::TryFrom, fmt}; +use crate::admission_request::AdmissionRequest; use crate::admission_response::AdmissionResponse; use crate::policy::Policy; use crate::runtimes::burrego::Runtime as BurregoRuntime; @@ -32,19 +33,21 @@ impl fmt::Display for PolicyExecutionMode { } } -#[derive(Debug, Serialize)] -pub struct ValidateRequest(pub(crate) serde_json::Value); +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum ValidateRequest { + Raw(serde_json::Value), + AdmissionRequest(AdmissionRequest), +} impl ValidateRequest { - pub fn new(request: serde_json::Value) -> Self { - ValidateRequest(request) - } - pub(crate) fn uid(&self) -> &str { - if let Some(uid) = self.0.get("uid").and_then(value::Value::as_str) { - uid - } else { - "" + match self { + ValidateRequest::Raw(raw_req) => raw_req + .get("uid") + .and_then(value::Value::as_str) + .unwrap_or_default(), + ValidateRequest::AdmissionRequest(adm_req) => &adm_req.uid, } } } From 9a94e36477140f46ee973519a9bb8b4496abaf7b Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 11:18:01 +0200 Subject: [PATCH 3/6] feat: support raw requests in runtimes Signed-off-by: Fabrizio Sestito --- src/runtimes/burrego.rs | 32 ++++++++++++++++++-------------- src/runtimes/wapc.rs | 8 +++++++- src/runtimes/wasi_cli/runtime.rs | 19 ++++++++++++++----- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/runtimes/burrego.rs b/src/runtimes/burrego.rs index 97b08c8b..19934068 100644 --- a/src/runtimes/burrego.rs +++ b/src/runtimes/burrego.rs @@ -40,20 +40,24 @@ impl<'a> Runtime<'a> { // OPA and Gatekeeper expect arguments in different ways. Provide the ones that each expect. let (document_to_evaluate, data) = match self.0.policy_execution_mode { - RegoPolicyExecutionMode::Opa => { - // Policies for OPA expect the whole `AdmissionReview` - // object: produce a synthetic external one so - // existing OPA policies are compatible. - ( - json!({ - "apiVersion": "admission.k8s.io/v1", - "kind": "AdmissionReview", - "request": &request.0, - }), - json!(settings), - ) - } + RegoPolicyExecutionMode::Opa => ( + json!({ + "request": &request, + }), + json!(settings), + ), RegoPolicyExecutionMode::Gatekeeper => { + // Gatekeeper policies expect the `AdmissionRequest` variant only. + let request = match request { + ValidateRequest::AdmissionRequest(adm_req) => adm_req, + ValidateRequest::Raw(_) => { + return AdmissionResponse::reject_internal_server_error( + uid.to_string(), + "Gatekeeper does not support raw validation requests".to_string(), + ); + } + }; + // Gatekeeper policies include a toplevel `review` // object that contains the AdmissionRequest to be // evaluated in an `object` attribute, and the @@ -62,7 +66,7 @@ impl<'a> Runtime<'a> { ( json!({ "parameters": settings, - "review": &request.0, + "review": &request, }), json!({"kubernetes": ""}), // TODO (ereslibre): Kubernetes context goes here ) diff --git a/src/runtimes/wapc.rs b/src/runtimes/wapc.rs index b11761a5..aa2ca0e2 100644 --- a/src/runtimes/wapc.rs +++ b/src/runtimes/wapc.rs @@ -513,8 +513,14 @@ impl<'a> Runtime<'a> { ) -> AdmissionResponse { let uid = request.uid(); + let req_json_value = + serde_json::to_value(request).expect("cannot convert request to json value"); + //NOTE: object is null for DELETE operations - let req_obj = request.0.get("object"); + let req_obj = match request { + ValidateRequest::Raw(_) => Some(&req_json_value), + ValidateRequest::AdmissionRequest(_) => req_json_value.get("object"), + }; let validate_params = json!({ "request": request, diff --git a/src/runtimes/wasi_cli/runtime.rs b/src/runtimes/wasi_cli/runtime.rs index a6568340..4cbf3be2 100644 --- a/src/runtimes/wasi_cli/runtime.rs +++ b/src/runtimes/wasi_cli/runtime.rs @@ -119,11 +119,20 @@ impl<'a> Runtime<'a> { ) } match serde_json::from_slice::(stdout.as_bytes()) { - Ok(pvr) => AdmissionResponse::from_policy_validation_response( - request.uid().to_string(), - request.0.get("object"), - &pvr, - ) + Ok(pvr) => { + let req_json_value = serde_json::to_value(request) + .expect("cannot convert request to json value"); + let req_obj = match request { + ValidateRequest::Raw(_) => Some(&req_json_value), + ValidateRequest::AdmissionRequest(_) => req_json_value.get("object"), + }; + + AdmissionResponse::from_policy_validation_response( + request.uid().to_string(), + req_obj, + &pvr, + ) + } .unwrap_or_else(|e| { AdmissionResponse::reject_internal_server_error( request.uid().to_string(), From 0ec6a822afc1b83729e09ad2d66d94fa4838aa8c Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 11:33:02 +0200 Subject: [PATCH 4/6] chore: apply cargo clippy suggestions to tests Signed-off-by: Fabrizio Sestito --- src/admission_response.rs | 4 ++-- src/policy.rs | 22 +++++----------------- src/policy_evaluator.rs | 2 +- src/policy_metadata.rs | 2 +- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/admission_response.rs b/src/admission_response.rs index 47566c48..005d5a6d 100644 --- a/src/admission_response.rs +++ b/src/admission_response.rs @@ -147,7 +147,7 @@ mod tests { let response = AdmissionResponse::reject(uid.clone(), message.clone(), code); assert_eq!(response.uid, uid); - assert_eq!(response.allowed, false); + assert!(!response.allowed); assert_eq!(response.patch, None); assert_eq!(response.patch_type, None); @@ -187,7 +187,7 @@ mod tests { let response = response.unwrap(); assert_eq!(response.uid, uid); - assert_eq!(response.allowed, false); + assert!(!response.allowed); assert_eq!(response.patch, None); assert_eq!(response.patch_type, None); assert_eq!(response.audit_annotations, Some(audit_annotations)); diff --git a/src/policy.rs b/src/policy.rs index 8a85d476..8fe00b39 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -13,7 +13,7 @@ use crate::policy_metadata::ContextAwareResource; /// This struct is used extensively inside of the `host_callback` /// function to obtain information about the policy that is invoking /// a host waPC function, and handle the request. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Policy { /// The policy identifier. This is mostly relevant for Policy Server, /// which uses the identifier provided by the user inside of the `policy.yml` @@ -56,18 +56,6 @@ impl PartialEq for Policy { } } -#[cfg(test)] -impl Default for Policy { - fn default() -> Self { - Policy { - id: String::default(), - instance_id: None, - callback_channel: None, - ctx_aware_resources_allow_list: HashSet::new(), - } - } -} - impl Policy { pub(crate) fn new( id: String, @@ -167,18 +155,18 @@ mod tests { let id = "test".to_string(); let policy_id = Some(1); let callback_channel = None; - let ctx_aware_resources_allow_list = Some(HashSet::new()); + let ctx_aware_resources_allow_list = HashSet::new(); let policy = Policy::new( id.clone(), - policy_id.clone(), + policy_id, callback_channel.clone(), - ctx_aware_resources_allow_list.clone(), + Some(ctx_aware_resources_allow_list.clone()), ) .expect("cannot create policy"); assert!(policy.id == id); assert!(policy.instance_id == policy_id); - assert!(policy.ctx_aware_resources_allow_list == ctx_aware_resources_allow_list.unwrap()); + assert!(policy.ctx_aware_resources_allow_list == ctx_aware_resources_allow_list); } } diff --git a/src/policy_evaluator.rs b/src/policy_evaluator.rs index c1c3abc2..85c86d76 100644 --- a/src/policy_evaluator.rs +++ b/src/policy_evaluator.rs @@ -199,7 +199,7 @@ mod tests { for (mode_str, expected) in &test_data { let actual: std::result::Result = - serde_json::from_str(&mode_str); + serde_json::from_str(mode_str); assert_eq!(expected, &actual.unwrap()); } diff --git a/src/policy_metadata.rs b/src/policy_metadata.rs index b2b22985..a0950110 100644 --- a/src/policy_metadata.rs +++ b/src/policy_metadata.rs @@ -319,7 +319,7 @@ mod tests { #[test] fn metadata_with_rego_execution_mode_must_have_a_valid_protocol() { - for mode in vec![PolicyExecutionMode::Opa, PolicyExecutionMode::OpaGatekeeper] { + for mode in [PolicyExecutionMode::Opa, PolicyExecutionMode::OpaGatekeeper] { let metadata = Metadata { protocol_version: Some(ProtocolVersion::Unknown), execution_mode: mode, From 3350f975ac5a360d84f93a70de0a64962694c963 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 16:03:04 +0200 Subject: [PATCH 5/6] docs: add docstring to ValidateRequest Signed-off-by: Fabrizio Sestito --- src/policy_evaluator.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/policy_evaluator.rs b/src/policy_evaluator.rs index 85c86d76..5ba00d65 100644 --- a/src/policy_evaluator.rs +++ b/src/policy_evaluator.rs @@ -33,6 +33,8 @@ impl fmt::Display for PolicyExecutionMode { } } +/// A validation request that can be sent to a policy evaluator. +/// It can be either a raw JSON object, or a Kubernetes AdmissionRequest. #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum ValidateRequest { From 48505333f80515c88ed54b4680122530a179b2f3 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Fri, 20 Oct 2023 16:09:20 +0200 Subject: [PATCH 6/6] docs: add docstring to AdmissionRequest Signed-off-by: Fabrizio Sestito --- src/admission_request.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/admission_request.rs b/src/admission_request.rs index b8bd696a..a5d0a9e0 100644 --- a/src/admission_request.rs +++ b/src/admission_request.rs @@ -1,3 +1,4 @@ +/// This models the admission/v1/AdmissionRequest object of Kubernetes #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdmissionRequest {