use std::sync::{Mutex, PoisonError};
use anyhow::Result;
use openmoat_audit::{EventId, NewEvent, Store};
use openmoat_core::{CompiledPolicy, Decision, Taint, Verdict};
use openmoat_hosts::Host;
use openmoat_proxy::TaintLookup;
use super::{Pass, SessionTaint, fold};
use crate::home::Home;
pub const RUN_SESSION_ENV: &str = "MOAT_RUN_SESSION";
pub const LINK_HOST: &str = "moat-run";
const LINK_RULE: &str = "run-session";
pub fn run_session(value: Option<&str>) -> Option<&str> {
value.filter(|v| {
v.strip_prefix("run-").is_some_and(|rest| {
!rest.is_empty()
&& rest.len() <= 64
&& rest.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
})
})
}
pub fn link(store: &Store, run: &str, host: Host, session: &str) -> Result<()> {
if store.session_has(run, LINK_HOST, session)? {
return Ok(());
}
let decision = Decision::single(
Verdict::Allow,
LINK_RULE,
format!(
"{} session {session} runs under this `openmoat run`; its taint applies to the proxy",
host.id()
),
);
store.record(&NewEvent {
host: LINK_HOST,
session_id: run,
call_id: Some(session),
cwd: None,
tool: host.id(),
action: None,
decision: &decision,
latency_us: 0,
})?;
Ok(())
}
pub struct RunTaint<'p> {
home: Home,
run: String,
policy: &'p CompiledPolicy<'p>,
cwd: String,
state: Mutex<State>,
}
struct State {
store: Option<Store>,
scanned: EventId,
linked: Vec<Linked>,
}
struct Linked {
host: Host,
session: String,
upto: EventId,
taint: SessionTaint,
}
impl<'p> RunTaint<'p> {
pub fn new(home: Home, run: &str, policy: &'p CompiledPolicy<'p>, cwd: &str) -> Self {
Self {
home,
run: run.to_owned(),
policy,
cwd: cwd.to_owned(),
state: Mutex::new(State {
store: None,
scanned: EventId(0),
linked: Vec::new(),
}),
}
}
fn fold(&self) -> Result<Taint> {
let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
let state = &mut *state;
if state.store.is_none() {
state.store = Some(self.home.open_audit()?);
}
let Some(store) = &state.store else {
return Ok(Taint::default());
};
let rows = store.session_after(&self.run, state.scanned)?;
if let Some(last) = rows.last() {
state.scanned = last.id;
}
for row in rows.iter().filter(|r| r.host == LINK_HOST) {
let (Some(session), Ok(host)) = (&row.call_id, row.tool.parse::<Host>()) else {
continue;
};
if !state
.linked
.iter()
.any(|l| l.host == host && l.session == *session)
{
state.linked.push(Linked {
host,
session: session.clone(),
upto: EventId(0),
taint: SessionTaint::default(),
});
}
}
let mut out = Taint::default();
for linked in &mut state.linked {
let events = store.session_after(&linked.session, linked.upto)?;
fold(
&mut linked.taint,
&events,
linked.host,
self.policy,
&self.cwd,
&mut Pass::default(),
)?;
if let Some(last) = events.last() {
linked.upto = last.id;
}
out.absorb(linked.taint.taint.clone());
}
Ok(out)
}
}
impl TaintLookup for RunTaint<'_> {
fn taint(&self, session: &str) -> Result<Taint, String> {
if session != self.run {
return Err(format!("{session} is not this proxy's session"));
}
self.fold().map_err(|e| format!("{e:#}"))
}
fn version(&self) -> Option<i64> {
let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
if state.store.is_none() {
state.store = self.home.open_audit().ok();
}
let id = state.store.as_ref()?.latest_id().ok()?;
Some(id.map_or(0, |id| id.0))
}
}
#[cfg(test)]
mod tests;