Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support verifying GHA signatures, Url prefix subjects #146

Merged
merged 6 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ 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"
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"
Expand Down
5 changes: 2 additions & 3 deletions crates/burrego/src/opa/builtins/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,10 @@ struct ExpressionList(Vec<Expression>);

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!(f, "{}", expression)?;
}
write!(f, "{}", result)
Ok(())
}
}

Expand Down
103 changes: 101 additions & 2 deletions src/callback_handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,53 @@ 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()
}});

if let Err(e) = req.response_channel.send(response) {
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,
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()
Expand Down Expand Up @@ -310,3 +354,58 @@ 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, keyless, annotations)}"#,
with_cached_flag = true
)]
async fn get_sigstore_keyless_prefix_verification_cached(
client: &mut sigstore_verification::Client,
image: String,
keyless: Vec<KeylessInfo>,
annotations: Option<HashMap<String, String>>,
) -> Result<cached::Return<VerificationResponse>> {
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.
//
// 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<String>,
annotations: Option<HashMap<String, String>>,
) -> Result<cached::Return<VerificationResponse>> {
client
.verify_github_actions(image, owner, repo, annotations)
.await
.map(cached::Return::new)
}
79 changes: 77 additions & 2 deletions src/callback_handler/sigstore_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ impl Client {
.verifier
.verify(&image, self.docker_config.as_ref(), &verification_config)
.await;

match result {
Ok(digest) => Ok(VerificationResponse {
digest,
Expand All @@ -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<Signature> = Vec::new();
for k in keyless.iter() {
Expand All @@ -85,11 +84,87 @@ impl Client {
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_keyless_prefix(
&mut self,
image: String,
keyless: Vec<KeylessInfo>,
annotations: Option<HashMap<String, String>>,
) -> Result<VerificationResponse> {
if keyless.is_empty() {
return Err(anyhow!("Must provide keyless info"));
}
// Build interim VerificationConfig:
//
let mut signatures_all_of: Vec<Signature> = 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,
owner: String,
repo: Option<String>,
annotations: Option<HashMap<String, String>>,
) -> Result<VerificationResponse> {
if owner.is_empty() {
return Err(anyhow!("Must provide owner info"));
}
// Build interim VerificationConfig:
//
let mut signatures_all_of: Vec<Signature> = 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,
Expand Down