feat(log): add tracing

This commit is contained in:
Kristofers Solo 2025-09-24 14:25:03 +03:00
parent b6181315a9
commit 45bfa3f50e
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
3 changed files with 194 additions and 12 deletions

110
Cargo.lock generated
View File

@ -17,6 +17,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "backtrace"
version = "0.3.75"
@ -41,6 +50,8 @@ dependencies = [
"glob",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -128,6 +139,21 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.7.5"
@ -143,6 +169,15 @@ dependencies = [
"adler2",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [
"windows-sys",
]
[[package]]
name = "object"
version = "0.36.7"
@ -188,6 +223,23 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustc-demangle"
version = "0.1.26"
@ -252,6 +304,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.106"
@ -279,9 +337,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.34"
@ -302,15 +372,46 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-serde"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
dependencies = [
"serde",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
"tracing-serde",
]
[[package]]
@ -325,6 +426,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"

View File

@ -14,6 +14,8 @@ color-eyre = "0.6"
glob = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
[dev-dependencies]
claims = "0.8"

View File

@ -2,6 +2,7 @@ use color_eyre::eyre::Result;
use glob::Pattern;
use serde::{Deserialize, Serialize};
use std::{fs::read_to_string, path::Path};
use tracing::{Level, info};
#[derive(Debug, Serialize, Deserialize)]
pub struct FsCapability {
@ -29,21 +30,36 @@ pub struct CapabilityManifest {
// TODO: add signature
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceEvent {
pub seq: u64,
pub event_type: String,
pub input: String,
pub outcome: bool,
}
#[derive(Debug)]
pub struct HostState {
pub manifest: CapabilityManifest,
pub trace: Vec<TraceEvent>,
}
impl HostState {
#[inline]
#[must_use]
pub const fn new(manifest: CapabilityManifest) -> Self {
Self { manifest }
Self {
manifest,
trace: Vec::new(),
}
}
/// Simulate "plugin execution": check if path is allowed via FS read cap.
/// Returns `true` if allowed, `false` if denied.
#[must_use]
pub fn execute_plugin<P: AsRef<Path>>(&self, path: P) -> bool {
pub fn execute_plugin<P: AsRef<Path>>(&mut self, path: P) -> bool {
let seq = u64::try_from(self.trace.len()).map_or_else(|_| 1, |len| len + 1);
let Some(fs_cap) = &self.manifest.capabilities.fs else {
return false;
};
@ -54,11 +70,34 @@ impl HostState {
let path_str = path.as_ref().to_string_lossy();
read_patterns.iter().any(|pattern| {
let is_allowed = read_patterns.iter().any(|pattern| {
Pattern::new(pattern)
.map(|p| p.matches(&path_str))
.unwrap_or(false)
})
});
info!(
seq = seq,
event_type = "cap.call",
input = %path_str,
outcome = is_allowed,
plugin = %self.manifest.plugin
);
self.trace.push(TraceEvent {
seq,
event_type: "cap.call".into(),
input: path_str.into(),
outcome: is_allowed,
});
is_allowed
}
#[inline]
#[must_use]
pub fn finalize_trace(&self) -> String {
serde_json::to_string_pretty(&self.trace).unwrap_or_else(|_| "[]".into())
}
}
@ -73,10 +112,19 @@ pub fn load_manifest(path: &str) -> Result<CapabilityManifest> {
Ok(manifest)
}
pub fn init_tracing() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
}
#[cfg(test)]
mod tests {
use super::*;
use claims::assert_some;
use claims::{assert_ok, assert_some};
#[test]
fn tracing_init() {
init_tracing();
}
#[test]
fn manifest_loader() {
@ -94,13 +142,35 @@ mod tests {
}
#[test]
fn host_enforcement() {
let manifest = load_manifest("examples/manifest.json").expect("Load failed");
let host = HostState::new(manifest);
fn host_enforcement_with_trace() {
init_tracing();
// Allowed: matches ./workspace/*
assert!(host.execute_plugin("./workspace/config.toml"));
// Disallowed: outside pattern
assert!(!host.execute_plugin("/etc/passwd"))
let manifest = load_manifest("examples/manifest.json").expect("Load failed");
let mut host = HostState::new(manifest);
// 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);
assert_eq!(host.trace.len(), 2);
let trace1 = assert_some!(host.trace.first());
assert_eq!(trace1.seq, 1);
assert_eq!(trace1.event_type, "cap.call");
assert_eq!(trace1.input, "./workspace/config.toml");
assert!(trace1.outcome);
let trace2 = assert_some!(host.trace.get(1));
assert_eq!(trace2.seq, 2);
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);
}
}