feat: add trace saving and loading

This commit is contained in:
Kristofers Solo 2025-09-25 12:59:25 +03:00
parent 45bfa3f50e
commit fab9efeac3
Signed by: kristoferssolo
GPG Key ID: 74FF8144483D82C8
3 changed files with 137 additions and 7 deletions

97
Cargo.lock generated
View File

@ -41,6 +41,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "captra"
version = "0.1.0"
@ -50,6 +56,7 @@ dependencies = [
"glob",
"serde",
"serde_json",
"tempfile",
"tracing",
"tracing-subscriber",
]
@ -93,6 +100,16 @@ dependencies = [
"tracing-error",
]
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "eyre"
version = "0.6.12"
@ -103,6 +120,24 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "gimli"
version = "0.31.1"
@ -139,6 +174,12 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.28"
@ -223,6 +264,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "regex-automata"
version = "0.4.10"
@ -246,6 +293,19 @@ version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -321,6 +381,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thread_local"
version = "1.1.9"
@ -426,6 +499,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -498,3 +589,9 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"

View File

@ -19,9 +19,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
[dev-dependencies]
claims = "0.8"
tempfile = "3.23"
[lints.clippy]
pedantic = "warn"
nursery = "warn"
unwrap_used = "warn"
expect_used = "warn"

View File

@ -1,7 +1,10 @@
use color_eyre::eyre::Result;
use glob::Pattern;
use serde::{Deserialize, Serialize};
use std::{fs::read_to_string, path::Path};
use std::{
fs::{self, read_to_string},
path::Path,
};
use tracing::{Level, info};
#[derive(Debug, Serialize, Deserialize)]
@ -30,7 +33,7 @@ pub struct CapabilityManifest {
// TODO: add signature
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TraceEvent {
pub seq: u64,
pub event_type: String,
@ -99,6 +102,26 @@ impl HostState {
pub fn finalize_trace(&self) -> String {
serde_json::to_string_pretty(&self.trace).unwrap_or_else(|_| "[]".into())
}
/// Save the current trace to a file as pretty JSON.
/// # Errors
/// If file write fails (e.g., I/O error) or JSON serialization fails.
pub fn save_trace<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let json_str = serde_json::to_string_pretty(&self.trace)?;
fs::write(path, json_str)?;
Ok(())
}
/// Load a trace from a JSON file to Vec<TraceEvent>.
///
/// # Errors
///
/// If file read fails (e.g., I/O error) or JSON parsing fails.
pub fn load_trace<P: AsRef<Path>>(path: P) -> Result<Vec<TraceEvent>> {
let json_str = fs::read_to_string(path)?;
let trace = serde_json::from_str(&json_str)?;
Ok(trace)
}
}
/// Loads a capability manifest from a JSON file.
@ -120,6 +143,7 @@ pub fn init_tracing() {
mod tests {
use super::*;
use claims::{assert_ok, assert_some};
use tempfile::tempdir;
#[test]
fn tracing_init() {
@ -151,7 +175,6 @@ mod tests {
// allowed - expect a tracing event
let out1 = host.execute_plugin("./workspace/config.toml");
assert!(out1);
// denied - event + entry
let out2 = host.execute_plugin("/etc/passwd");
assert!(!out2);
@ -169,8 +192,18 @@ mod tests {
assert_eq!(trace2.input, "/etc/passwd");
assert!(!trace2.outcome);
let json = host.finalize_trace();
let parsed = assert_ok!(serde_json::from_str::<Vec<TraceEvent>>(&json));
assert_eq!(parsed.len(), 2);
let tmp_dir = tempdir().expect("Temp dir failed");
let tmp_path = tmp_dir.as_ref().join("trace.json");
assert_ok!(host.save_trace(&tmp_path));
let loaded_trace = assert_ok!(HostState::load_trace(tmp_path));
assert_eq!(loaded_trace.len(), 2);
let loaded_trace1 = assert_some!(loaded_trace.first());
assert_eq!(trace1, loaded_trace1);
let loaded_trace2 = assert_some!(loaded_trace.get(1));
assert_eq!(trace2, loaded_trace2);
}
}