clnrm_core/backend/volume.rs
1//! Volume mounting support for containers
2//!
3//! Provides secure volume mount configuration with validation and whitelist support.
4
5use crate::config::VolumeConfig;
6use crate::error::{CleanroomError, Result};
7use std::path::{Path, PathBuf};
8
9/// Volume mount configuration
10#[derive(Debug, Clone)]
11pub struct VolumeMount {
12 /// Host path (absolute, validated)
13 host_path: PathBuf,
14 /// Container path (absolute)
15 container_path: PathBuf,
16 /// Read-only flag
17 read_only: bool,
18}
19
20impl VolumeMount {
21 /// Create a new volume mount with validation
22 ///
23 /// # Arguments
24 ///
25 /// * `host_path` - Path on the host system
26 /// * `container_path` - Path inside the container
27 /// * `read_only` - Whether mount is read-only
28 ///
29 /// # Errors
30 ///
31 /// Returns error if:
32 /// - Host path is not absolute
33 /// - Host path does not exist
34 /// - Container path is not absolute
35 /// - Path canonicalization fails
36 ///
37 /// # Example
38 ///
39 /// ```no_run
40 /// use clnrm_core::backend::volume::VolumeMount;
41 ///
42 /// let mount = VolumeMount::new("/tmp/data", "/data", false)?;
43 /// assert!(!mount.is_read_only());
44 /// # Ok::<(), clnrm_core::error::CleanroomError>(())
45 /// ```
46 pub fn new(
47 host_path: impl AsRef<Path>,
48 container_path: impl AsRef<Path>,
49 read_only: bool,
50 ) -> Result<Self> {
51 let host_path = host_path.as_ref();
52 let container_path = container_path.as_ref();
53
54 // Validate host path is absolute
55 if !host_path.is_absolute() {
56 return Err(CleanroomError::validation_error(format!(
57 "Host path must be absolute: {}",
58 host_path.display()
59 )));
60 }
61
62 // Validate host path exists
63 if !host_path.exists() {
64 return Err(CleanroomError::validation_error(format!(
65 "Host path does not exist: {}",
66 host_path.display()
67 )));
68 }
69
70 // Canonicalize host path to resolve symlinks and relative components
71 let host_path = host_path.canonicalize().map_err(|e| {
72 CleanroomError::validation_error(format!(
73 "Failed to canonicalize host path {}: {}",
74 host_path.display(),
75 e
76 ))
77 })?;
78
79 // Validate container path is absolute
80 if !container_path.is_absolute() {
81 return Err(CleanroomError::validation_error(format!(
82 "Container path must be absolute: {}",
83 container_path.display()
84 )));
85 }
86
87 Ok(Self {
88 host_path,
89 container_path: container_path.to_path_buf(),
90 read_only,
91 })
92 }
93
94 /// Create from VolumeConfig with validation
95 pub fn from_config(config: &VolumeConfig) -> Result<Self> {
96 let read_only = config.read_only.unwrap_or(false);
97 Self::new(&config.host_path, &config.container_path, read_only)
98 }
99
100 /// Get host path
101 pub fn host_path(&self) -> &Path {
102 &self.host_path
103 }
104
105 /// Get container path
106 pub fn container_path(&self) -> &Path {
107 &self.container_path
108 }
109
110 /// Check if mount is read-only
111 pub fn is_read_only(&self) -> bool {
112 self.read_only
113 }
114}
115
116/// Volume security validator with whitelist support
117#[derive(Debug, Clone)]
118pub struct VolumeValidator {
119 /// Allowed base directories for mounting
120 whitelist: Vec<PathBuf>,
121}
122
123impl VolumeValidator {
124 /// Create a new volume validator with whitelist
125 ///
126 /// # Example
127 ///
128 /// ```no_run
129 /// use clnrm_core::backend::volume::VolumeValidator;
130 /// use std::path::PathBuf;
131 ///
132 /// let validator = VolumeValidator::new(vec![
133 /// PathBuf::from("/tmp"),
134 /// PathBuf::from("/var/data"),
135 /// ]);
136 /// ```
137 pub fn new(whitelist: Vec<PathBuf>) -> Self {
138 Self { whitelist }
139 }
140
141 /// Validate a volume mount against whitelist
142 ///
143 /// # Errors
144 ///
145 /// Returns error if host path is not under any whitelisted directory
146 pub fn validate(&self, mount: &VolumeMount) -> Result<()> {
147 // If whitelist is empty, allow all paths (permissive mode)
148 if self.whitelist.is_empty() {
149 return Ok(());
150 }
151
152 let host_path = mount.host_path();
153
154 // Check if host path is under any whitelisted directory
155 for allowed in &self.whitelist {
156 if host_path.starts_with(allowed) {
157 return Ok(());
158 }
159 }
160
161 Err(CleanroomError::validation_error(format!(
162 "Host path {} is not in whitelist. Allowed directories: {}",
163 host_path.display(),
164 self.whitelist
165 .iter()
166 .map(|p| p.display().to_string())
167 .collect::<Vec<_>>()
168 .join(", ")
169 )))
170 }
171
172 /// Validate multiple mounts
173 pub fn validate_all(&self, mounts: &[VolumeMount]) -> Result<()> {
174 for mount in mounts {
175 self.validate(mount)?;
176 }
177 Ok(())
178 }
179}
180
181impl Default for VolumeValidator {
182 /// Create default validator with common safe directories
183 fn default() -> Self {
184 let mut whitelist = vec![PathBuf::from("/tmp"), PathBuf::from("/var/tmp")];
185
186 // Add system temp directory (varies by OS)
187 whitelist.push(std::env::temp_dir());
188
189 // Add current directory access
190 if let Ok(current_dir) = std::env::current_dir() {
191 whitelist.push(current_dir);
192 }
193
194 Self::new(whitelist)
195 }
196}