#[cfg(target_os = "linux")]
use nix::sched::{CloneFlags, unshare};
use crate::policy::SandboxPolicy;
#[derive(Debug, Clone)]
pub struct NamespaceConfig {
pub new_pid: bool,
pub new_mount: bool,
pub new_net: bool,
pub new_user: bool,
}
impl NamespaceConfig {
pub fn from_policy(policy: &SandboxPolicy) -> Self {
Self {
new_pid: true, new_mount: true, new_net: !policy.network.enabled, new_user: true, }
}
pub fn any_enabled(&self) -> bool {
self.new_pid || self.new_mount || self.new_net || self.new_user
}
#[cfg(target_os = "linux")]
pub fn clone_flags(&self) -> CloneFlags {
let mut flags = CloneFlags::empty();
if self.new_pid {
flags |= CloneFlags::CLONE_NEWPID;
}
if self.new_mount {
flags |= CloneFlags::CLONE_NEWNS;
}
if self.new_net {
flags |= CloneFlags::CLONE_NEWNET;
}
if self.new_user {
flags |= CloneFlags::CLONE_NEWUSER;
}
flags
}
}
#[cfg(target_os = "linux")]
pub fn apply_namespaces(config: &NamespaceConfig) -> crate::Result<()> {
if !config.any_enabled() {
return Ok(());
}
let flags = config.clone_flags();
unshare(flags)
.map_err(|e| crate::KavachError::ExecFailed(format!("namespace unshare failed: {e}")))?;
tracing::debug!(?flags, "namespace isolation applied");
Ok(())
}
#[cfg(target_os = "linux")]
pub fn drop_capabilities() -> crate::Result<()> {
use caps::{CapSet, Capability};
let dangerous = [
Capability::CAP_SYS_ADMIN,
Capability::CAP_SYS_PTRACE,
Capability::CAP_NET_RAW,
Capability::CAP_NET_ADMIN,
Capability::CAP_SYS_MODULE,
Capability::CAP_SYS_RAWIO,
Capability::CAP_SYS_BOOT,
Capability::CAP_SYS_CHROOT,
Capability::CAP_MKNOD,
];
for cap in &dangerous {
let _ = caps::drop(None, CapSet::Effective, *cap);
let _ = caps::drop(None, CapSet::Permitted, *cap);
let _ = caps::drop(None, CapSet::Inheritable, *cap);
}
tracing::debug!("dangerous capabilities dropped");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_from_policy_network_disabled() {
let mut policy = SandboxPolicy::minimal();
policy.network.enabled = false;
let config = NamespaceConfig::from_policy(&policy);
assert!(config.new_pid);
assert!(config.new_mount);
assert!(config.new_net); assert!(config.new_user);
}
#[test]
fn config_from_policy_network_enabled() {
let mut policy = SandboxPolicy::minimal();
policy.network.enabled = true;
let config = NamespaceConfig::from_policy(&policy);
assert!(!config.new_net); }
#[test]
fn any_enabled_default() {
let policy = SandboxPolicy::minimal();
let config = NamespaceConfig::from_policy(&policy);
assert!(config.any_enabled());
}
#[test]
fn any_enabled_none() {
let config = NamespaceConfig {
new_pid: false,
new_mount: false,
new_net: false,
new_user: false,
};
assert!(!config.any_enabled());
}
#[cfg(target_os = "linux")]
#[test]
fn clone_flags_all() {
let config = NamespaceConfig {
new_pid: true,
new_mount: true,
new_net: true,
new_user: true,
};
let flags = config.clone_flags();
assert!(flags.contains(CloneFlags::CLONE_NEWPID));
assert!(flags.contains(CloneFlags::CLONE_NEWNS));
assert!(flags.contains(CloneFlags::CLONE_NEWNET));
assert!(flags.contains(CloneFlags::CLONE_NEWUSER));
}
#[cfg(target_os = "linux")]
#[test]
fn clone_flags_partial() {
let config = NamespaceConfig {
new_pid: true,
new_mount: false,
new_net: false,
new_user: true,
};
let flags = config.clone_flags();
assert!(flags.contains(CloneFlags::CLONE_NEWPID));
assert!(!flags.contains(CloneFlags::CLONE_NEWNS));
assert!(!flags.contains(CloneFlags::CLONE_NEWNET));
assert!(flags.contains(CloneFlags::CLONE_NEWUSER));
}
}