Skip to main content

nucleus/isolation/
namespaces.rs

1use crate::error::{NucleusError, Result, StateTransition};
2use crate::isolation::state::NamespaceState;
3use crate::isolation::usermap::{UserNamespaceConfig, UserNamespaceMapper};
4use nix::mount::MsFlags;
5use nix::sched::{unshare, CloneFlags};
6use nix::unistd::sethostname;
7use tracing::{debug, info};
8
9/// Namespace configuration
10///
11/// Defines which Linux namespaces to create for isolation
12#[derive(Debug, Clone)]
13pub struct NamespaceConfig {
14    /// PID namespace - process isolation
15    pub pid: bool,
16    /// Mount namespace - filesystem isolation
17    pub mnt: bool,
18    /// Network namespace - network isolation
19    pub net: bool,
20    /// UTS namespace - hostname isolation
21    pub uts: bool,
22    /// IPC namespace - inter-process communication isolation
23    pub ipc: bool,
24    /// Cgroup namespace - isolate cgroup hierarchy visibility
25    pub cgroup: bool,
26    /// User namespace - user/group ID isolation
27    pub user: bool,
28    /// Time namespace - isolate time offsets from the host
29    pub time: bool,
30}
31
32impl NamespaceConfig {
33    /// Create config with all namespaces enabled
34    pub fn all() -> Self {
35        Self {
36            pid: true,
37            mnt: true,
38            net: true,
39            uts: true,
40            ipc: true,
41            cgroup: true,
42            user: false, // User namespace requires special setup
43            time: false, // Opt-in until time namespace support is broadly validated
44        }
45    }
46
47    /// Create config with minimal namespaces for isolation
48    pub fn minimal() -> Self {
49        Self {
50            pid: true,
51            mnt: true,
52            net: true,
53            uts: false,
54            ipc: false,
55            cgroup: true,
56            user: false,
57            time: false,
58        }
59    }
60
61    /// Enable or disable cgroup namespace isolation.
62    pub fn with_cgroup_namespace(mut self, enabled: bool) -> Self {
63        self.cgroup = enabled;
64        self
65    }
66
67    /// Enable or disable time namespace isolation.
68    pub fn with_time_namespace(mut self, enabled: bool) -> Self {
69        self.time = enabled;
70        self
71    }
72
73    /// Convert to CloneFlags for unshare(2)
74    fn to_clone_flags(&self) -> CloneFlags {
75        let mut flags = CloneFlags::empty();
76
77        if self.pid {
78            flags |= CloneFlags::CLONE_NEWPID;
79        }
80        if self.mnt {
81            flags |= CloneFlags::CLONE_NEWNS;
82        }
83        if self.net {
84            flags |= CloneFlags::CLONE_NEWNET;
85        }
86        if self.uts {
87            flags |= CloneFlags::CLONE_NEWUTS;
88        }
89        if self.ipc {
90            flags |= CloneFlags::CLONE_NEWIPC;
91        }
92        if self.cgroup {
93            flags |= CloneFlags::CLONE_NEWCGROUP;
94        }
95        if self.user {
96            flags |= CloneFlags::CLONE_NEWUSER;
97        }
98        if self.time {
99            flags |= CloneFlags::from_bits_retain(libc::CLONE_NEWTIME);
100        }
101
102        flags
103    }
104}
105
106impl Default for NamespaceConfig {
107    fn default() -> Self {
108        Self::all()
109    }
110}
111
112/// Namespace manager
113///
114/// Implements the namespace lifecycle state machine from
115/// Nucleus_Isolation_NamespaceLifecycle.tla
116pub struct NamespaceManager {
117    config: NamespaceConfig,
118    state: NamespaceState,
119    user_mapper: Option<UserNamespaceMapper>,
120}
121
122impl NamespaceManager {
123    pub fn new(config: NamespaceConfig) -> Self {
124        Self {
125            config,
126            state: NamespaceState::Uninitialized,
127            user_mapper: None,
128        }
129    }
130
131    /// Create a new namespace manager with user namespace mapping
132    pub fn with_user_mapping(mut self, user_config: UserNamespaceConfig) -> Self {
133        self.user_mapper = Some(UserNamespaceMapper::new(user_config));
134        self
135    }
136
137    /// Create namespaces via unshare(2)
138    ///
139    /// This implements the transition: uninitialized -> unshared
140    /// in the namespace state machine
141    pub fn unshare_namespaces(&mut self) -> Result<()> {
142        if self.state != NamespaceState::Uninitialized {
143            debug!("Namespaces already created, skipping");
144            return Ok(());
145        }
146
147        info!("Creating namespaces: {:?}", self.config);
148
149        let flags = self.config.to_clone_flags();
150
151        unshare(flags).map_err(|e| {
152            NucleusError::NamespaceError(format!("Failed to unshare namespaces: {}", e))
153        })?;
154
155        // Ensure mount events do not propagate back to the host namespace.
156        if self.config.mnt {
157            nix::mount::mount(
158                None::<&str>,
159                "/",
160                None::<&str>,
161                MsFlags::MS_REC | MsFlags::MS_PRIVATE,
162                None::<&str>,
163            )
164            .map_err(|e| {
165                NucleusError::NamespaceError(format!(
166                    "Failed to set mount propagation to private: {}",
167                    e
168                ))
169            })?;
170        }
171
172        // If user namespace is enabled and we have a mapper, setup UID/GID mappings
173        // This must be done immediately after unshare(CLONE_NEWUSER)
174        if self.config.user {
175            if let Some(mapper) = &self.user_mapper {
176                info!("Setting up user namespace UID/GID mappings");
177                mapper.setup_mappings()?;
178            } else {
179                debug!("User namespace enabled but no mapper configured");
180            }
181        }
182
183        // State transition: Uninitialized -> Unshared
184        self.state = self.state.transition(NamespaceState::Unshared)?;
185        info!("Successfully created namespaces");
186
187        Ok(())
188    }
189
190    /// Check if namespaces have been created
191    pub fn is_unshared(&self) -> bool {
192        self.state != NamespaceState::Uninitialized
193    }
194
195    /// Mark that the process has entered the namespaces
196    ///
197    /// State transition: Unshared -> Entered
198    pub fn enter(&mut self) -> Result<()> {
199        self.state = self.state.transition(NamespaceState::Entered)?;
200        debug!("Namespace state: {:?}", self.state);
201        Ok(())
202    }
203
204    /// Get the current namespace state
205    pub fn state(&self) -> NamespaceState {
206        self.state
207    }
208
209    /// Get namespace configuration
210    pub fn config(&self) -> &NamespaceConfig {
211        &self.config
212    }
213
214    /// Set hostname in UTS namespace
215    ///
216    /// This only works if the UTS namespace is enabled in the config
217    pub fn set_hostname(&self, hostname: &str) -> Result<()> {
218        if !self.config.uts {
219            debug!("UTS namespace not enabled, skipping hostname setting");
220            return Ok(());
221        }
222
223        info!("Setting hostname to: {}", hostname);
224
225        sethostname(hostname)
226            .map_err(|e| NucleusError::NamespaceError(format!("Failed to set hostname: {}", e)))?;
227
228        info!("Successfully set hostname to: {}", hostname);
229
230        Ok(())
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn test_namespace_config_all() {
240        let config = NamespaceConfig::all();
241        assert!(config.pid);
242        assert!(config.mnt);
243        assert!(config.net);
244        assert!(config.uts);
245        assert!(config.ipc);
246        assert!(config.cgroup);
247        assert!(!config.user); // User namespace disabled by default
248        assert!(!config.time);
249    }
250
251    #[test]
252    fn test_namespace_config_minimal() {
253        let config = NamespaceConfig::minimal();
254        assert!(config.pid);
255        assert!(config.mnt);
256        assert!(config.net);
257        assert!(!config.uts);
258        assert!(!config.ipc);
259        assert!(config.cgroup);
260        assert!(!config.user);
261        assert!(!config.time);
262    }
263
264    #[test]
265    fn test_namespace_manager_initial_state() {
266        let mgr = NamespaceManager::new(NamespaceConfig::minimal());
267        assert!(!mgr.is_unshared());
268    }
269
270    // Note: Testing actual unshare requires root or user namespace setup
271    // This is tested in integration tests
272}