use crate::error::{NucleusError, Result};
use std::fs;
use tracing::{debug, info};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdMapping {
pub container_id: u32,
pub host_id: u32,
pub count: u32,
}
impl IdMapping {
pub fn new(container_id: u32, host_id: u32, count: u32) -> Self {
Self {
container_id,
host_id,
count,
}
}
pub fn rootless() -> Self {
let uid = nix::unistd::getuid().as_raw();
Self::new(0, uid, 1)
}
fn format(&self) -> String {
format!("{} {} {}\n", self.container_id, self.host_id, self.count)
}
}
#[derive(Debug, Clone)]
pub struct UserNamespaceConfig {
pub uid_mappings: Vec<IdMapping>,
pub gid_mappings: Vec<IdMapping>,
}
impl UserNamespaceConfig {
pub fn rootless() -> Self {
let uid = nix::unistd::getuid().as_raw();
let gid = nix::unistd::getgid().as_raw();
Self {
uid_mappings: vec![IdMapping::new(0, uid, 1)],
gid_mappings: vec![IdMapping::new(0, gid, 1)],
}
}
pub fn custom(uid_mappings: Vec<IdMapping>, gid_mappings: Vec<IdMapping>) -> Self {
Self {
uid_mappings,
gid_mappings,
}
}
}
pub struct UserNamespaceMapper {
config: UserNamespaceConfig,
}
impl UserNamespaceMapper {
pub fn new(config: UserNamespaceConfig) -> Self {
Self { config }
}
pub fn setup_mappings(&self) -> Result<()> {
info!("Setting up user namespace mappings");
self.write_setgroups_deny()?;
self.write_uid_map()?;
self.write_gid_map()?;
info!("Successfully configured user namespace mappings");
Ok(())
}
fn write_setgroups_deny(&self) -> Result<()> {
let path = "/proc/self/setgroups";
debug!("Writing 'deny' to {}", path);
fs::write(path, "deny\n").map_err(|e| {
NucleusError::NamespaceError(format!("Failed to write to {}: {}", path, e))
})?;
Ok(())
}
fn write_uid_map(&self) -> Result<()> {
let path = "/proc/self/uid_map";
let mut content = String::new();
for mapping in &self.config.uid_mappings {
content.push_str(&mapping.format());
}
debug!("Writing UID mappings to {}: {}", path, content.trim());
fs::write(path, &content).map_err(|e| {
NucleusError::NamespaceError(format!("Failed to write UID mappings: {}", e))
})?;
Ok(())
}
fn write_gid_map(&self) -> Result<()> {
let path = "/proc/self/gid_map";
let mut content = String::new();
for mapping in &self.config.gid_mappings {
content.push_str(&mapping.format());
}
debug!("Writing GID mappings to {}: {}", path, content.trim());
fs::write(path, &content).map_err(|e| {
NucleusError::NamespaceError(format!("Failed to write GID mappings: {}", e))
})?;
Ok(())
}
pub fn config(&self) -> &UserNamespaceConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_id_mapping_format() {
let mapping = IdMapping::new(0, 1000, 1);
assert_eq!(mapping.format(), "0 1000 1\n");
let mapping = IdMapping::new(1000, 2000, 100);
assert_eq!(mapping.format(), "1000 2000 100\n");
}
#[test]
fn test_id_mapping_rootless() {
let mapping = IdMapping::rootless();
assert_eq!(mapping.container_id, 0);
assert_eq!(mapping.count, 1);
}
#[test]
fn test_user_namespace_config_rootless() {
let config = UserNamespaceConfig::rootless();
assert_eq!(config.uid_mappings.len(), 1);
assert_eq!(config.gid_mappings.len(), 1);
assert_eq!(config.uid_mappings[0].container_id, 0);
assert_eq!(config.gid_mappings[0].container_id, 0);
}
#[test]
fn test_user_namespace_config_custom() {
let uid_mappings = vec![IdMapping::new(0, 1000, 1), IdMapping::new(1000, 2000, 100)];
let gid_mappings = vec![IdMapping::new(0, 1000, 1)];
let config = UserNamespaceConfig::custom(uid_mappings.clone(), gid_mappings.clone());
assert_eq!(config.uid_mappings, uid_mappings);
assert_eq!(config.gid_mappings, gid_mappings);
}
}