From 78275748ad4709dd6f7abf1290e883c9f4d1e1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 24 Jun 2022 13:08:32 +0200 Subject: [PATCH 1/6] chore: Fix debug strings, comments --- src/callback_handler/mod.rs | 4 ++-- src/callback_handler/sigstore_verification.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/callback_handler/mod.rs b/src/callback_handler/mod.rs index 5c5038f7..f7bc932e 100644 --- a/src/callback_handler/mod.rs +++ b/src/callback_handler/mod.rs @@ -189,9 +189,9 @@ impl CallbackHandler { .await .map(|response| { if response.was_cached { - debug!(?image, "Got sigstore pub keys verification from cache"); + debug!(?image, "Got sigstore keyless verification from cache"); } else { - debug!(?image, "Got sigstore pub keys verification by querying remote registry"); + debug!(?image, "Got sigstore keylesss verification by querying remote registry"); } CallbackResponse { payload: serde_json::to_vec(&response.value).unwrap() diff --git a/src/callback_handler/sigstore_verification.rs b/src/callback_handler/sigstore_verification.rs index 2705f7b7..61ec1883 100644 --- a/src/callback_handler/sigstore_verification.rs +++ b/src/callback_handler/sigstore_verification.rs @@ -51,7 +51,6 @@ impl Client { .verifier .verify(&image, self.docker_config.as_ref(), &verification_config) .await; - match result { Ok(digest) => Ok(VerificationResponse { digest, @@ -70,7 +69,7 @@ impl Client { if keyless.is_empty() { return Err(anyhow!("Must provide keyless info")); } - // Build intering VerificationConfig: + // Build interim VerificationConfig: // let mut signatures_all_of: Vec = Vec::new(); for k in keyless.iter() { @@ -85,6 +84,7 @@ impl Client { all_of: Some(signatures_all_of), any_of: None, }; + let result = self .verifier .verify(&image, self.docker_config.as_ref(), &verification_config) From 761d75319a9d4eb6826ca80d92af11ce8469434b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 24 Jun 2022 13:10:28 +0200 Subject: [PATCH 2/6] build: Consume kubewarden-policy-sdk 0.6.1 Contains needed GHA changes. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e8709af8..4d4f2192 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ dns-lookup = "1.0.8" json-patch = "0.2.6" kube = { version = "0.73.1", default-features = false, features = ["client", "rustls-tls"] } k8s-openapi = { version = "0.15.0", default-features = false } -kubewarden-policy-sdk = "0.6.0" +kubewarden-policy-sdk = "0.6.1" lazy_static = "1.4.0" policy-fetcher = { git = "https://github.com/kubewarden/policy-fetcher", tag = "v0.7.8" } serde_json = "1.0" From 112c9f14fed7094fbb09b5e0ffe148ccba5b5d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 24 Jun 2022 13:09:14 +0200 Subject: [PATCH 3/6] feat: Support verifying GHA signatures Add `verify_github_actions()`, which is called when we receive the new `CallbackRequestType::SigstoreGithubActionsVerify{}`. --- src/callback_handler/mod.rs | 50 +++++++++++++++++++ src/callback_handler/sigstore_verification.rs | 36 +++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/callback_handler/mod.rs b/src/callback_handler/mod.rs index f7bc932e..1f485ce3 100644 --- a/src/callback_handler/mod.rs +++ b/src/callback_handler/mod.rs @@ -201,6 +201,28 @@ impl CallbackHandler { warn!("callback handler: cannot send response back: {:?}", e); } }, + CallbackRequestType::SigstoreGithubActionsVerify { + image, + owner, + repo, + annotations, + } => { + let response = get_sigstore_github_actions_verification_cached(&mut self.sigstore_client, image.clone(), owner, repo, annotations) + .await + .map(|response| { + if response.was_cached { + debug!(?image, "Got sigstore GHA verification from cache"); + } else { + debug!(?image, "Got sigstore GHA verification by querying remote registry"); + } + CallbackResponse { + payload: serde_json::to_vec(&response.value).unwrap() + }}); + + if let Err(e) = req.response_channel.send(response) { + warn!("callback handler: cannot send response back: {:?}", e); + } + }, CallbackRequestType::DNSLookupHost { host, } => { @@ -310,3 +332,31 @@ async fn get_sigstore_keyless_verification_cached( .await .map(cached::Return::new) } + +// Sigstore verifications are time expensive, this can cause a massive slow down +// of policy evaluations, especially inside of PolicyServer. +// Because of that we will keep a cache of the digests results. +// +// Details about this cache: +// * the cache is time bound: cached values are purged after 60 seconds +// * only successful results are cached +#[cached( + time = 60, + result = true, + sync_writes = true, + key = "String", + convert = r#"{ format!("{}{:?}{:?}{:?}", image, owner, repo, annotations)}"#, + with_cached_flag = true +)] +async fn get_sigstore_github_actions_verification_cached( + client: &mut sigstore_verification::Client, + image: String, + owner: String, + repo: Option, + annotations: Option>, +) -> Result> { + client + .verify_github_actions(image, owner, repo, annotations) + .await + .map(cached::Return::new) +} diff --git a/src/callback_handler/sigstore_verification.rs b/src/callback_handler/sigstore_verification.rs index 61ec1883..6f0d19ec 100644 --- a/src/callback_handler/sigstore_verification.rs +++ b/src/callback_handler/sigstore_verification.rs @@ -89,7 +89,43 @@ impl Client { .verifier .verify(&image, self.docker_config.as_ref(), &verification_config) .await; + match result { + Ok(digest) => Ok(VerificationResponse { + digest, + is_trusted: true, + }), + Err(e) => Err(e), + } + } + + pub async fn verify_github_actions( + &mut self, + image: String, + owner: String, + repo: Option, + annotations: Option>, + ) -> Result { + if owner.is_empty() { + return Err(anyhow!("Must provide owner info")); + } + // Build interim VerificationConfig: + // + let mut signatures_all_of: Vec = Vec::new(); + let signature = Signature::GithubAction { + owner: owner.clone(), + repo: repo.clone(), + annotations: annotations.clone(), + }; + signatures_all_of.push(signature); + let verification_config = LatestVerificationConfig { + all_of: Some(signatures_all_of), + any_of: None, + }; + let result = self + .verifier + .verify(&image, self.docker_config.as_ref(), &verification_config) + .await; match result { Ok(digest) => Ok(VerificationResponse { digest, From 013c8067909ef18cb6aab199f1bbe78029887bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 4 Jul 2022 13:05:15 +0200 Subject: [PATCH 4/6] feat: Support verifying URL prefix subjects Add `verify_keyless_prefix()`, which is called when we receive the new `CallbackRequestType::SigstoreKeylessPrefixVerify{}`. --- Cargo.toml | 1 + src/callback_handler/mod.rs | 49 +++++++++++++++++++ src/callback_handler/sigstore_verification.rs | 39 +++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4d4f2192..1e12083c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ serde = { version = "1.0", features = ["derive"] } tokio = { version = "^1", features = ["rt", "rt-multi-thread"] } tracing = "0.1" tracing-futures = "0.2" +url = { version = "2.2.2", features = ["serde"] } validator = { version = "0.15", features = ["derive"] } wasmparser = "0.86.0" wapc = "1.0.0" diff --git a/src/callback_handler/mod.rs b/src/callback_handler/mod.rs index 1f485ce3..70acc3ce 100644 --- a/src/callback_handler/mod.rs +++ b/src/callback_handler/mod.rs @@ -201,6 +201,28 @@ impl CallbackHandler { warn!("callback handler: cannot send response back: {:?}", e); } }, + CallbackRequestType::SigstoreKeylessPrefixVerify { + image, + keyless, + annotations, + } => { + let response = get_sigstore_keyless_prefix_verification_cached(&mut self.sigstore_client, image.clone(), keyless, annotations) + .await + .map(|response| { + if response.was_cached { + debug!(?image, "Got sigstore keyless verification from cache"); + } else { + debug!(?image, "Got sigstore keylesss verification by querying remote registry"); + } + CallbackResponse { + payload: serde_json::to_vec(&response.value).unwrap() + }}); + + if let Err(e) = req.response_channel.send(response) { + warn!("callback handler: cannot send response back: {:?}", e); + } + }, + CallbackRequestType::SigstoreGithubActionsVerify { image, owner, @@ -333,6 +355,33 @@ async fn get_sigstore_keyless_verification_cached( .map(cached::Return::new) } +// Sigstore verifications are time expensive, this can cause a massive slow down +// of policy evaluations, especially inside of PolicyServer. +// Because of that we will keep a cache of the digests results. +// +// Details about this cache: +// * the cache is time bound: cached values are purged after 60 seconds +// * only successful results are cached +#[cached( + time = 60, + result = true, + sync_writes = true, + key = "String", + convert = r#"{ format!("{}{:?}{:?}", image, keyless, annotations)}"#, + with_cached_flag = true +)] +async fn get_sigstore_keyless_prefix_verification_cached( + client: &mut sigstore_verification::Client, + image: String, + keyless: Vec, + annotations: Option>, +) -> Result> { + client + .verify_keyless_prefix(image, keyless, annotations) + .await + .map(cached::Return::new) +} + // Sigstore verifications are time expensive, this can cause a massive slow down // of policy evaluations, especially inside of PolicyServer. // Because of that we will keep a cache of the digests results. diff --git a/src/callback_handler/sigstore_verification.rs b/src/callback_handler/sigstore_verification.rs index 6f0d19ec..e11dd796 100644 --- a/src/callback_handler/sigstore_verification.rs +++ b/src/callback_handler/sigstore_verification.rs @@ -98,6 +98,45 @@ impl Client { } } + pub async fn verify_keyless_prefix( + &mut self, + image: String, + keyless: Vec, + annotations: Option>, + ) -> Result { + if keyless.is_empty() { + return Err(anyhow!("Must provide keyless info")); + } + // Build interim VerificationConfig: + // + let mut signatures_all_of: Vec = Vec::new(); + for k in keyless.iter() { + let prefix = url::Url::parse(&k.subject).expect("Cannot build url prefix"); + let signature = Signature::GenericIssuer { + issuer: k.issuer.clone(), + subject: Subject::UrlPrefix(prefix), + annotations: annotations.clone(), + }; + signatures_all_of.push(signature); + } + let verification_config = LatestVerificationConfig { + all_of: Some(signatures_all_of), + any_of: None, + }; + + let result = self + .verifier + .verify(&image, self.docker_config.as_ref(), &verification_config) + .await; + match result { + Ok(digest) => Ok(VerificationResponse { + digest, + is_trusted: true, + }), + Err(e) => Err(e), + } + } + pub async fn verify_github_actions( &mut self, image: String, From 8ac5b68cd1f1a8de6ff1f5c139b8af584c043c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 4 Jul 2022 16:52:42 +0200 Subject: [PATCH 5/6] fix: Burrego clippy `format_push_string` warning --- crates/burrego/src/opa/builtins/regex.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/burrego/src/opa/builtins/regex.rs b/crates/burrego/src/opa/builtins/regex.rs index 50cd9955..8ec49b2f 100644 --- a/crates/burrego/src/opa/builtins/regex.rs +++ b/crates/burrego/src/opa/builtins/regex.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use core::fmt::Display; use regex::{escape as regex_escape, Regex}; +use std::fmt::Write as _; // import without risk of name clashing use std::{fmt, str::FromStr}; pub fn split(args: &[serde_json::Value]) -> Result { @@ -117,7 +118,7 @@ impl Display for ExpressionList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut result = String::new(); for expression in self.0.iter() { - result.push_str(&format!("{}", expression)); + write!(result, "{}", expression)?; } write!(f, "{}", result) } From 039ea98ed75e76b77d4c20a34e4b86977e9c5d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Tue, 5 Jul 2022 12:42:30 +0200 Subject: [PATCH 6/6] review: Simplify ExpressionList.fmt() --- crates/burrego/src/opa/builtins/regex.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/burrego/src/opa/builtins/regex.rs b/crates/burrego/src/opa/builtins/regex.rs index 8ec49b2f..b32a883a 100644 --- a/crates/burrego/src/opa/builtins/regex.rs +++ b/crates/burrego/src/opa/builtins/regex.rs @@ -1,7 +1,6 @@ use anyhow::{anyhow, Result}; use core::fmt::Display; use regex::{escape as regex_escape, Regex}; -use std::fmt::Write as _; // import without risk of name clashing use std::{fmt, str::FromStr}; pub fn split(args: &[serde_json::Value]) -> Result { @@ -116,11 +115,10 @@ struct ExpressionList(Vec); impl Display for ExpressionList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut result = String::new(); for expression in self.0.iter() { - write!(result, "{}", expression)?; + write!(f, "{}", expression)?; } - write!(f, "{}", result) + Ok(()) } }