mirror of
https://github.com/kristoferssolo/captra.git
synced 2025-12-20 11:04:39 +00:00
feat(log): add tracing
This commit is contained in:
parent
b6181315a9
commit
45bfa3f50e
110
Cargo.lock
generated
110
Cargo.lock
generated
@ -17,6 +17,15 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
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]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.75"
|
version = "0.3.75"
|
||||||
@ -41,6 +50,8 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -128,6 +139,21 @@ version = "0.2.175"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
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]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
@ -143,6 +169,15 @@ dependencies = [
|
|||||||
"adler2",
|
"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]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@ -188,6 +223,23 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"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]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
@ -252,6 +304,12 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
@ -279,9 +337,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@ -302,15 +372,46 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.20"
|
version = "0.3.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
"tracing-serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -325,6 +426,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|||||||
@ -14,6 +14,8 @@ color-eyre = "0.6"
|
|||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
claims = "0.8"
|
claims = "0.8"
|
||||||
|
|||||||
94
src/lib.rs
94
src/lib.rs
@ -2,6 +2,7 @@ use color_eyre::eyre::Result;
|
|||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fs::read_to_string, path::Path};
|
use std::{fs::read_to_string, path::Path};
|
||||||
|
use tracing::{Level, info};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct FsCapability {
|
pub struct FsCapability {
|
||||||
@ -29,21 +30,36 @@ pub struct CapabilityManifest {
|
|||||||
// TODO: add signature
|
// 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 struct HostState {
|
||||||
pub manifest: CapabilityManifest,
|
pub manifest: CapabilityManifest,
|
||||||
|
pub trace: Vec<TraceEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostState {
|
impl HostState {
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(manifest: CapabilityManifest) -> Self {
|
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.
|
/// Simulate "plugin execution": check if path is allowed via FS read cap.
|
||||||
/// Returns `true` if allowed, `false` if denied.
|
/// Returns `true` if allowed, `false` if denied.
|
||||||
#[must_use]
|
#[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 {
|
let Some(fs_cap) = &self.manifest.capabilities.fs else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -54,11 +70,34 @@ impl HostState {
|
|||||||
|
|
||||||
let path_str = path.as_ref().to_string_lossy();
|
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)
|
Pattern::new(pattern)
|
||||||
.map(|p| p.matches(&path_str))
|
.map(|p| p.matches(&path_str))
|
||||||
.unwrap_or(false)
|
.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)
|
Ok(manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_tracing() {
|
||||||
|
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use claims::assert_some;
|
use claims::{assert_ok, assert_some};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tracing_init() {
|
||||||
|
init_tracing();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn manifest_loader() {
|
fn manifest_loader() {
|
||||||
@ -94,13 +142,35 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn host_enforcement() {
|
fn host_enforcement_with_trace() {
|
||||||
let manifest = load_manifest("examples/manifest.json").expect("Load failed");
|
init_tracing();
|
||||||
let host = HostState::new(manifest);
|
|
||||||
|
|
||||||
// Allowed: matches ./workspace/*
|
let manifest = load_manifest("examples/manifest.json").expect("Load failed");
|
||||||
assert!(host.execute_plugin("./workspace/config.toml"));
|
let mut host = HostState::new(manifest);
|
||||||
// Disallowed: outside pattern
|
|
||||||
assert!(!host.execute_plugin("/etc/passwd"))
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user