use crate::capability_ext::CapabilitySetExt;
use crate::cli::{SandboxArgs, WhyArgs, WhyOp};
use crate::{network_policy, policy, profile, query_ext, sandbox_state};
use nono::{AccessMode, CapabilitySet, NonoError, Result};
struct WhyContext {
caps: CapabilitySet,
overridden_paths: Vec<std::path::PathBuf>,
allowed_domains: Vec<String>,
}
fn resolve_allowed_domains(profile: &profile::Profile) -> Vec<String> {
let policy_json = crate::config::embedded::embedded_network_policy_json();
let net_policy = match network_policy::load_network_policy(policy_json) {
Ok(p) => p,
Err(_) => return profile.network.allow_domain.clone(),
};
let mut domains = Vec::new();
if let Some(net_profile_name) = profile.network.resolved_network_profile() {
if let Ok(resolved) = network_policy::resolve_network_profile(&net_policy, net_profile_name)
{
domains.extend(resolved.hosts);
for suffix in &resolved.suffixes {
let wildcard = if suffix.starts_with('.') {
format!("*{}", suffix)
} else {
format!("*.{}", suffix)
};
domains.push(wildcard);
}
}
}
domains.extend(network_policy::expand_proxy_allow(
&net_policy,
&profile.network.allow_domain,
));
domains
}
pub(crate) fn run_why(args: WhyArgs) -> Result<()> {
use query_ext::{print_result, query_network, query_path, QueryResult};
use sandbox_state::load_sandbox_state;
let ctx: WhyContext = if args.self_query {
match load_sandbox_state() {
Some(state) => {
let paths = state.bypass_protection_as_paths();
WhyContext {
caps: state.to_caps()?,
overridden_paths: paths,
allowed_domains: state.allowed_domains.clone(),
}
}
None => {
let result = QueryResult::NotSandboxed {
message: "Not running inside a nono sandbox".to_string(),
};
if args.json {
let json = serde_json::to_string_pretty(&result).map_err(|e| {
NonoError::ConfigParse(format!("JSON serialization failed: {}", e))
})?;
println!("{}", json);
} else {
print_result(&result);
}
return Ok(());
}
}
} else if let Some(ref profile_name) = args.profile {
let profile = profile::load_profile(profile_name)?;
let workdir = args
.workdir
.clone()
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| std::path::PathBuf::from("."));
let sandbox_args = SandboxArgs {
allow: args.allow.clone(),
read: args.read.clone(),
write: args.write.clone(),
allow_file: args.allow_file.clone(),
read_file: args.read_file.clone(),
write_file: args.write_file.clone(),
block_net: args.block_net,
workdir: args.workdir.clone(),
..SandboxArgs::default()
};
let mut override_paths = Vec::new();
for tmpl in &profile.filesystem.bypass_protection {
let expanded = profile::expand_vars(tmpl, &workdir)?;
if expanded.exists() {
if let Ok(canonical) = expanded.canonicalize() {
override_paths.push(canonical);
}
} else {
override_paths.push(expanded);
}
}
let allowed_domains = resolve_allowed_domains(&profile);
let prepared = CapabilitySet::from_profile(&profile, &workdir, &sandbox_args)?;
let mut caps = prepared.caps;
if prepared.needs_unlink_overrides {
policy::apply_unlink_overrides(&mut caps);
}
WhyContext {
caps,
overridden_paths: override_paths,
allowed_domains,
}
} else {
let sandbox_args = SandboxArgs {
allow: args.allow.clone(),
read: args.read.clone(),
write: args.write.clone(),
allow_file: args.allow_file.clone(),
read_file: args.read_file.clone(),
write_file: args.write_file.clone(),
block_net: args.block_net,
workdir: args.workdir.clone(),
..SandboxArgs::default()
};
let prepared = CapabilitySet::from_args(&sandbox_args)?;
let mut caps = prepared.caps;
if prepared.needs_unlink_overrides {
policy::apply_unlink_overrides(&mut caps);
}
WhyContext {
caps,
overridden_paths: vec![],
allowed_domains: vec![],
}
};
let result = if let Some(ref path) = args.path {
let op = match args.op {
Some(WhyOp::Read) => AccessMode::Read,
Some(WhyOp::Write) => AccessMode::Write,
Some(WhyOp::ReadWrite) => AccessMode::ReadWrite,
None => AccessMode::Read,
};
query_path(path, op, &ctx.caps, &ctx.overridden_paths)?
} else if let Some(ref host) = args.host {
query_network(host, args.port, &ctx.caps, &ctx.allowed_domains)
} else {
return Err(NonoError::ConfigParse(
"--path or --host is required".to_string(),
));
};
if args.json {
let json = serde_json::to_string_pretty(&result)
.map_err(|e| NonoError::ConfigParse(format!("JSON serialization failed: {}", e)))?;
println!("{}", json);
} else {
print_result(&result);
}
Ok(())
}