// Container image signature verification using Cosign // Verifies that container images are signed and trusted use anyhow::{Context, Result}; use std::process::Command; use tracing::{info, warn}; pub struct ImageVerifier { cosign_public_key: Option, require_signatures: bool, } impl ImageVerifier { pub fn new(cosign_public_key: Option) -> Self { Self { cosign_public_key, require_signatures: false, } } /// Create a verifier that requires all images to be signed. pub fn new_strict(cosign_public_key: Option) -> Self { Self { cosign_public_key, require_signatures: true, } } /// Verify a container image signature pub async fn verify_image(&self, image: &str, signature: Option<&str>) -> Result { if signature.is_none() && self.cosign_public_key.is_none() { if self.require_signatures { return Err(anyhow::anyhow!( "Image '{}' has no signature and no cosign key is configured. \ All container images must be signed for production use.", image )); } warn!("No signature provided for image: {}", image); return Ok(false); } // Check if cosign is available let cosign_available = Command::new("cosign").arg("version").output().is_ok(); if !cosign_available { if self.require_signatures { return Err(anyhow::anyhow!( "Cosign binary not found. Install cosign to verify container image signatures." )); } warn!("Cosign not available, skipping signature verification"); return Ok(false); } // If public key is provided, use it for verification if let Some(ref public_key) = self.cosign_public_key { let output = Command::new("cosign") .arg("verify") .arg("--key") .arg(public_key) .arg(image) .output() .context("Failed to run cosign verify")?; if output.status.success() { info!("Image signature verified: {}", image); return Ok(true); } else { let stderr = String::from_utf8_lossy(&output.stderr); return Err(anyhow::anyhow!("Signature verification failed: {}", stderr)); } } // If signature URL is provided, verify using that if let Some(sig_url) = signature { if sig_url.starts_with("cosign://") { // Extract signature reference let sig_ref = sig_url.strip_prefix("cosign://").unwrap(); let output = Command::new("cosign") .arg("verify") .arg("--signature") .arg(sig_ref) .arg(image) .output() .context("Failed to run cosign verify")?; if output.status.success() { info!("Image signature verified: {}", image); return Ok(true); } else { let stderr = String::from_utf8_lossy(&output.stderr); return Err(anyhow::anyhow!("Signature verification failed: {}", stderr)); } } } Ok(false) } /// Check if an image has a signature pub async fn has_signature(&self, image: &str) -> bool { // Try to find signature in registry let output = Command::new("cosign") .arg("triangulate") .arg(image) .output(); output.is_ok() && output.unwrap().status.success() } }