Skip to content

Commit e37cd63

Browse files
mjamivdrew
authored andcommitted
fix(kubernetes): address PVC subPath review feedback
Signed-off-by: mjamiv <michael.commack@gmail.com>
1 parent 6488515 commit e37cd63

7 files changed

Lines changed: 377 additions & 144 deletions

File tree

crates/openshell-core/src/driver_mounts.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
//! Shared validation helpers for driver-config mounts.
55
6+
use std::collections::HashSet;
67
use std::path::Path;
78

89
/// `SELinux` relabelling mode for bind mounts.
@@ -32,6 +33,11 @@ const RESERVED_MOUNT_TARGETS: &[&str] = &[
3233
"/run/netns",
3334
];
3435

36+
/// Serde default helper for mount options that default to read-only.
37+
pub fn default_true() -> bool {
38+
true
39+
}
40+
3541
/// Validate a non-empty driver mount source.
3642
pub fn validate_mount_source(source: &str, field: &str) -> Result<(), String> {
3743
if source.is_empty() {
@@ -122,10 +128,27 @@ pub fn normalize_mount_target(target: &str) -> String {
122128
target.trim_end_matches('/').to_string()
123129
}
124130

125-
fn path_is_or_under(path: &Path, parent: &Path) -> bool {
131+
/// Return true when `path` is exactly `parent` or is contained below it.
132+
pub fn path_is_or_under(path: &Path, parent: &Path) -> bool {
126133
path == parent || path.starts_with(parent)
127134
}
128135

136+
/// Validate that already-normalized driver mount targets are unique.
137+
pub fn validate_unique_mount_targets<'a>(
138+
targets: impl IntoIterator<Item = &'a str>,
139+
driver_name: &str,
140+
) -> Result<(), String> {
141+
let mut seen = HashSet::new();
142+
for target in targets {
143+
if !seen.insert(target) {
144+
return Err(format!(
145+
"duplicate {driver_name} driver_config mount target '{target}'"
146+
));
147+
}
148+
}
149+
Ok(())
150+
}
151+
129152
#[cfg(test)]
130153
mod tests {
131154
use super::*;
@@ -162,6 +185,33 @@ mod tests {
162185
validate_container_mount_target("/etc/openshell-tools").unwrap();
163186
}
164187

188+
#[test]
189+
fn path_is_or_under_matches_boundaries() {
190+
assert!(path_is_or_under(
191+
Path::new("/sandbox"),
192+
Path::new("/sandbox")
193+
));
194+
assert!(path_is_or_under(
195+
Path::new("/sandbox/work"),
196+
Path::new("/sandbox")
197+
));
198+
assert!(!path_is_or_under(
199+
Path::new("/sandbox-work"),
200+
Path::new("/sandbox")
201+
));
202+
}
203+
204+
#[test]
205+
fn unique_mount_targets_rejects_duplicates() {
206+
let err =
207+
validate_unique_mount_targets(["/sandbox/work", "/sandbox/work"], "test").unwrap_err();
208+
209+
assert_eq!(
210+
err,
211+
"duplicate test driver_config mount target '/sandbox/work'"
212+
);
213+
}
214+
165215
#[test]
166216
fn mount_subpath_must_be_relative_without_parent_dirs() {
167217
assert!(validate_mount_subpath("project/a").is_ok());

crates/openshell-driver-docker/src/lib.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use openshell_core::proto_struct::{
4949
deserialize_optional_non_empty_string_list, struct_to_json_value,
5050
};
5151
use openshell_core::{Config, Error, Result as CoreResult};
52-
use std::collections::{HashMap, HashSet};
52+
use std::collections::HashMap;
5353
use std::io::Read;
5454
use std::net::{IpAddr, SocketAddr};
5555
use std::path::{Path, PathBuf};
@@ -262,15 +262,15 @@ enum DockerDriverMountConfig {
262262
Bind {
263263
source: String,
264264
target: String,
265-
#[serde(default = "default_true")]
265+
#[serde(default = "driver_mounts::default_true")]
266266
read_only: bool,
267267
#[serde(default)]
268268
selinux_label: Option<SelinuxLabel>,
269269
},
270270
Volume {
271271
source: String,
272272
target: String,
273-
#[serde(default = "default_true")]
273+
#[serde(default = "driver_mounts::default_true")]
274274
read_only: bool,
275275
#[serde(default)]
276276
subpath: Option<String>,
@@ -287,17 +287,13 @@ enum DockerDriverMountConfig {
287287
Image {
288288
source: String,
289289
target: String,
290-
#[serde(default = "default_true")]
290+
#[serde(default = "driver_mounts::default_true")]
291291
read_only: bool,
292292
#[serde(default)]
293293
subpath: Option<String>,
294294
},
295295
}
296296

297-
fn default_true() -> bool {
298-
true
299-
}
300-
301297
type WatchStream =
302298
Pin<Box<dyn Stream<Item = Result<WatchSandboxesEvent, Status>> + Send + 'static>>;
303299

@@ -1845,7 +1841,7 @@ fn validate_docker_driver_mounts(
18451841
mounts: &[DockerDriverMountConfig],
18461842
enable_bind_mounts: bool,
18471843
) -> Result<(), Status> {
1848-
let mut targets = HashSet::new();
1844+
let mut targets = Vec::with_capacity(mounts.len());
18491845
for mount in mounts {
18501846
let target = match mount {
18511847
DockerDriverMountConfig::Bind { source, target, .. } => {
@@ -1899,14 +1895,10 @@ fn validate_docker_driver_mounts(
18991895
};
19001896
driver_mounts::validate_container_mount_target(target)
19011897
.map_err(Status::failed_precondition)?;
1902-
let normalized_target = driver_mounts::normalize_mount_target(target);
1903-
if !targets.insert(normalized_target.clone()) {
1904-
return Err(Status::failed_precondition(format!(
1905-
"duplicate docker driver_config mount target '{normalized_target}'"
1906-
)));
1907-
}
1898+
targets.push(driver_mounts::normalize_mount_target(target));
19081899
}
1909-
Ok(())
1900+
driver_mounts::validate_unique_mount_targets(targets.iter().map(String::as_str), "docker")
1901+
.map_err(Status::failed_precondition)
19101902
}
19111903

19121904
fn validate_optional_positive_integral_i64(

crates/openshell-driver-docker/src/tests.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,39 @@ fn driver_config_allows_explicit_writable_volume_mounts() {
755755
assert_eq!(mounts[0].read_only, Some(false));
756756
}
757757

758+
#[test]
759+
fn driver_config_rejects_duplicate_mount_targets() {
760+
let mut sandbox = test_sandbox();
761+
sandbox
762+
.spec
763+
.as_mut()
764+
.unwrap()
765+
.template
766+
.as_mut()
767+
.unwrap()
768+
.driver_config = Some(json_struct(serde_json::json!({
769+
"mounts": [
770+
{
771+
"type": "volume",
772+
"source": "work-nfs",
773+
"target": "/sandbox/work"
774+
},
775+
{
776+
"type": "tmpfs",
777+
"target": "/sandbox/work"
778+
}
779+
]
780+
})));
781+
782+
let err = build_container_create_body(&sandbox, &runtime_config()).unwrap_err();
783+
784+
assert_eq!(err.code(), tonic::Code::FailedPrecondition);
785+
assert!(
786+
err.message()
787+
.contains("duplicate docker driver_config mount target")
788+
);
789+
}
790+
758791
#[test]
759792
fn driver_config_rejects_bind_mounts_unless_enabled() {
760793
let mut sandbox = test_sandbox();

crates/openshell-driver-kubernetes/README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,16 @@ resource details.
149149

150150
Use PVC volumes to mount existing Kubernetes PersistentVolumeClaims into the
151151
agent container. PVC volumes and mounts default to read-only unless
152-
`read_only: false` is set explicitly. A read-only PVC volume cannot be mounted
153-
read-write. The driver rejects duplicate volume names, mounts that reference
154-
unknown volumes, non-normalized or protected mount paths, and absolute or
155-
parent-traversing `sub_path` values.
156-
157-
Any explicit driver-config mount under `/sandbox/` disables the driver's
158-
default `/sandbox` workspace PVC injection for that sandbox. This keeps image
159-
contents fresh while allowing selected durable data paths to come from an
160-
external PVC.
152+
`read_only: false` is set explicitly. Read-write access requires
153+
`read_only: false` on both the PVC volume and each writable mount. The driver
154+
rejects duplicate volume names, invalid DNS-1123 volume or PVC claim names,
155+
mounts that reference unknown volumes, non-normalized or protected mount paths,
156+
and absolute or parent-traversing `sub_path` values.
157+
158+
Any explicit driver-config mount under `/sandbox` disables the driver's
159+
default `/sandbox` workspace PVC injection for that sandbox. Only the explicit
160+
mount paths persist through the external PVC; other `/sandbox` paths come from
161+
the current sandbox image.
161162

162163
```shell
163164
openshell sandbox create \

0 commit comments

Comments
 (0)