Skip to main content

ploit_malper/db/
mod.rs

1pub mod content;
2pub mod models;
3pub mod pocketbase;
4pub mod schema;
5pub mod sqlite;
6
7use crate::config::DatabaseConfig;
8use crate::error::Result;
9use models::{Asset, ExploitExecution, Finding, Observation, ScanRun, Service};
10
11/// Collection names used by every backend. Kept intentionally small: bulk
12/// content (full reports, long finding evidence) is stored as files in the
13/// content store, so the database only holds metadata and short fields.
14pub mod collections {
15    pub const ASSETS: &str = "Assets";
16    pub const SERVICES: &str = "Services";
17    pub const FINDINGS: &str = "Findings";
18    pub const OBSERVATIONS: &str = "Observations";
19    pub const SCAN_RUNS: &str = "ScanRuns";
20    pub const EXPLOIT_EXECUTIONS: &str = "ExploitExecutions";
21
22    pub const ALL: [&str; 6] = [
23        ASSETS,
24        SERVICES,
25        FINDINGS,
26        OBSERVATIONS,
27        SCAN_RUNS,
28        EXPLOIT_EXECUTIONS,
29    ];
30}
31
32/// Backend-agnostic persistence interface used by the importer.
33///
34/// All write operations are upserts keyed by a stable, content-derived id so
35/// that re-running an import over the same scan folder is idempotent. History
36/// is preserved through the `Observations` collection rather than by
37/// overwriting prior rows.
38pub trait Storage: Send {
39    fn kind(&self) -> &str;
40
41    /// Verify connectivity (PocketBase health / SQLite open + query).
42    fn ping(&mut self) -> Result<bool>;
43
44    /// Create all required collections/tables if they do not exist.
45    fn ensure_schema(&mut self) -> Result<()>;
46
47    // --- Scan runs ------------------------------------------------------
48
49    fn upsert_scan_run(&mut self, run: &ScanRun) -> Result<()>;
50    fn get_scan_run(&mut self, run_id: &str) -> Result<Option<ScanRun>>;
51    fn list_scan_runs(&mut self) -> Result<Vec<ScanRun>>;
52
53    // --- Assets ---------------------------------------------------------
54
55    fn upsert_asset(&mut self, asset: &Asset) -> Result<()>;
56    fn get_asset(&mut self, asset_id: &str) -> Result<Option<Asset>>;
57    fn list_assets(&mut self) -> Result<Vec<Asset>>;
58
59    // --- Services -------------------------------------------------------
60
61    fn upsert_service(&mut self, service: &Service) -> Result<()>;
62    fn get_service(&mut self, service_id: &str) -> Result<Option<Service>>;
63    fn list_services_for_asset(&mut self, asset_id: &str) -> Result<Vec<Service>>;
64    fn list_all_services(&mut self) -> Result<Vec<Service>>;
65
66    // --- Findings -------------------------------------------------------
67
68    fn upsert_finding(&mut self, finding: &Finding) -> Result<()>;
69    fn get_finding(&mut self, finding_id: &str) -> Result<Option<Finding>>;
70    fn list_findings_for_asset(&mut self, asset_id: &str) -> Result<Vec<Finding>>;
71    fn list_all_findings(&mut self) -> Result<Vec<Finding>>;
72
73    // --- Observations ---------------------------------------------------
74
75    fn add_observation(&mut self, observation: &Observation) -> Result<()>;
76    fn get_observation(&mut self, obs_id: &str) -> Result<Option<Observation>>;
77    fn list_observations_for(
78        &mut self,
79        subject_type: &str,
80        subject_id: &str,
81    ) -> Result<Vec<Observation>>;
82    fn list_all_observations(&mut self) -> Result<Vec<Observation>>;
83
84    // --- Exploit executions ---------------------------------------------
85
86    fn upsert_exploit_execution(&mut self, execution: &ExploitExecution) -> Result<()>;
87    fn get_exploit_execution(&mut self, execution_id: &str) -> Result<Option<ExploitExecution>>;
88    fn list_exploit_executions(&mut self) -> Result<Vec<ExploitExecution>>;
89    fn list_exploit_executions_for_run(&mut self, run_id: &str) -> Result<Vec<ExploitExecution>>;
90}
91
92/// Open a storage backend based on the persisted configuration.
93pub fn open_storage(config: &DatabaseConfig) -> Result<Box<dyn Storage>> {
94    if config.backend == "sqlite" {
95        Ok(Box::new(sqlite::SqliteStorage::open(&config.sqlite_path)?))
96    } else {
97        Ok(Box::new(pocketbase::PocketBaseStorage::new(config)?))
98    }
99}
100
101/// Open a storage backend, overriding the configured backend.
102pub fn open_storage_with(
103    config: &DatabaseConfig,
104    backend: &str,
105    pocketbase_url: Option<&str>,
106    sqlite_path: Option<&str>,
107) -> Result<Box<dyn Storage>> {
108    if backend == "sqlite" {
109        let path = sqlite_path
110            .map(str::to_string)
111            .unwrap_or_else(|| config.sqlite_path.clone());
112        Ok(Box::new(sqlite::SqliteStorage::open(&path)?))
113    } else {
114        let mut override_config = config.clone();
115        if let Some(url) = pocketbase_url {
116            override_config.pocketbase_url = url.to_string();
117        }
118        Ok(Box::new(pocketbase::PocketBaseStorage::new(
119            &override_config,
120        )?))
121    }
122}