use super::{AlertCalculatorTask, TaskTrait};
use crate::{
Config, KonarrError,
bom::{BomParser, Parsers},
models::{
Alerts, Projects, ServerSettings, Setting, dependencies::snapshots::SnapshotState,
security::SecurityState,
},
tools::{Grype, Tool, ToolConfig},
};
use async_trait::async_trait;
use geekorm::{ConnectionManager, prelude::*};
use log::{debug, info, warn};
#[derive(Default)]
pub struct AdvisoriesTask {}
#[async_trait]
impl TaskTrait for AdvisoriesTask {
async fn run(&self, database: &ConnectionManager) -> Result<(), crate::KonarrError> {
self.scan(database).await?;
AlertCalculatorTask::spawn(database).await?;
Ok(())
}
}
impl<'a> AdvisoriesTask {
pub fn new(_config: &'a Config) -> Result<Self, KonarrError> {
Ok(Self {})
}
}
impl AdvisoriesTask {
pub async fn scan(&self, database: &geekorm::ConnectionManager) -> Result<(), KonarrError> {
if ServerSettings::get_bool(&database.acquire().await, Setting::Security).await? {
log::info!("Scanning projects for security alerts");
self.scan_projects(database).await?;
Ok(())
} else {
Err(KonarrError::UnknownError(
"Advisories Polling is disabled".to_string(),
))
}
}
pub async fn scan_projects(
&self,
database: &geekorm::ConnectionManager,
) -> Result<(), KonarrError> {
info!("Scanning projects snapshots for security alerts");
let mut tool_grype = Grype::init().await;
if !tool_grype.is_available() {
warn!("Installing Grype, this may take a few minutes...");
tool_grype.install().await?;
}
log::debug!("Grype Config: {:?}", tool_grype);
let mut projects = Projects::fetch_containers(&database.acquire().await).await?;
info!("Projects Count: {}", projects.len());
for project in projects.iter_mut() {
self.scan_project(database, &tool_grype, project).await?;
}
Ok(())
}
pub async fn scan_project(
&self,
database: &geekorm::ConnectionManager,
tool_config: &ToolConfig,
project: &mut Projects,
) -> Result<(), KonarrError> {
debug!("Project: {}", project.name);
let sbom_dir = std::env::temp_dir().join("konarr");
if !sbom_dir.exists() {
tokio::fs::create_dir_all(&sbom_dir).await?;
}
if let Some(snapshot) = project.snapshots.first() {
if snapshot.state == SnapshotState::Failed {
warn!("Snapshot is in failed state, skipping");
return Ok(());
}
let mut snapshot = snapshot.clone();
info!(
"Scanning Snapshot :: {} - {}",
snapshot.id,
snapshot.components.len()
);
let mut alerts =
Alerts::fetch_by_snapshot_id(&database.acquire().await, snapshot.id).await?;
if let Some(tool_alerts) = snapshot.find_metadata("security.tools.alerts") {
if tool_alerts.as_bool() {
if ServerSettings::get_bool(
&database.acquire().await,
Setting::SecurityToolsAlerts,
)
.await?
{
info!(
"Project('{}', snapshot = '{}', components = '{}', vulnerabilities = '{}')",
project.name,
snapshot.id,
snapshot.components.len(),
alerts.len()
);
info!("Security Alerts coming from tools, skipping");
return Ok(());
} else {
info!(
"Security Tools Alerts setting is disabled, scanning project for security alerts"
);
}
}
}
let mut results = Vec::new();
let sbom_data = if let Ok(data) = snapshot.sbom(&database.acquire().await).await {
data
} else {
warn!("No SBOM data for project: {}", project.name);
snapshot.rescan(&database.acquire().await).await?;
return Ok(());
};
let sbom = Parsers::parse(&sbom_data)?;
log::info!("BillOfMaterials(comps='{}')", sbom.components.len(),);
let sbom_file = uuid::Uuid::new_v4().to_string();
let sbom_path =
sbom_dir.join(format!("{}.{}", sbom_file, sbom.sbom_type.to_file_name()));
tokio::fs::write(&sbom_path, sbom_data).await?;
log::debug!("Using Grype to scan SBOM: {}", sbom_path.display());
let bom = tool_config.run(&sbom_path.display().to_string()).await?;
let sbom = Parsers::parse(&bom.as_bytes())?;
log::info!(
"BillOfMaterials(comps='{}', vulns='{}')",
sbom.components.len(),
sbom.vulnerabilities.len()
);
for vuln in sbom.vulnerabilities.iter() {
log::trace!("Vulnerability: {:?}", vuln);
let alts =
Alerts::from_bom_vulnerability(&database.acquire().await, &snapshot, vuln)
.await?;
results.extend(alts);
}
for alert in alerts.iter_mut() {
if !results.iter().any(|r| r.id == alert.id) {
debug!("Marking Alert as Resolved: {}", alert.id);
alert.state = SecurityState::Secure;
alert.update(&database.acquire().await).await?;
}
}
snapshot
.set_metadata(&database.acquire().await, "rescan", "false")
.await?;
snapshot.updated_at = Some(chrono::Utc::now());
snapshot.state = SnapshotState::Completed;
snapshot.update(&database.acquire().await).await?;
info!(
"Project('{}', snapshot = '{}', components = '{}', vulnerabilities = '{}')",
project.name,
snapshot.id,
snapshot.components.len(),
results.len()
);
} else {
warn!("No snapshots for project: {}", project.name);
}
Ok(())
}
}