Skip to content

Commit d4ffa52

Browse files
feat(podman): honor OCI image working directories
Podman sandboxes now use a custom OCI image WORKDIR as the workspace and as the working directory for agent commands, matching Docker. Empty, /, and /sandbox values keep the managed /sandbox workspace volume. A custom workspace stays in the image's container filesystem: no workspace volume, no archive upload, and no root setup step. Resolve the image ID, user, environment, and working directory from one pinned inspection, validate the workdir with the shared OCI rules, and reject Podman control-path overlaps and image volumes or driver mounts that cover it. The runtime starts from / and passes the resolved path to agent commands with --workdir. Add podman_oci_identity to the Podman CI test list. Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
1 parent 3e87cad commit d4ffa52

8 files changed

Lines changed: 285 additions & 94 deletions

File tree

‎crates/openshell-driver-podman/README.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ only the channel bootstrap into the existing channel volume, preserving the
4444
workspace. The workload starts before the supervisor so its user namespace exists
4545
when the supervisor joins it; a stopped supervisor resolves that namespace again
4646
on its next start. The driver creates the managed workspace volume owned by
47-
the workload's final UID and GID, so the workload never starts as root.
47+
the workload's final UID and GID, so the workload never starts as root. Custom
48+
image workspaces have no workspace volume or upload.
4849

4950
The runtime must pass the sandbox's unprivileged enforcement probe, including
5051
nested seccomp notification and Landlock. Unsupported runtime defaults fail
@@ -91,6 +92,20 @@ binary extraction path. `supervisor_image` supplies the dynamically linked
9192
glibc `/openshell-supervisor` binary outside the workload. Image and request
9293
environment belong to agent children, never the supervisor process.
9394

95+
## OCI working directory
96+
97+
OpenShell reads `WORKDIR` from the workload image. If it is unset, `/`, or
98+
`/sandbox`, OpenShell uses its managed `/sandbox` workspace volume. A custom
99+
path must be absolute and normalized, and cannot overlap `/proc`, `/sys`,
100+
`/dev`, OpenShell-reserved paths, or the workload's private control and CA
101+
mounts. Image volumes and driver mounts cannot cover it; mounts nested below it
102+
remain valid.
103+
104+
A custom path stays in the image's container filesystem with its ownership and
105+
permissions. The workload starts as the final non-root user, which must be able
106+
to reach and write the directory. Agent commands use the path as their working
107+
directory.
108+
94109
## Lifecycle and readiness
95110

96111
Create builds both stopped containers and stages the private archives before

‎crates/openshell-driver-podman/src/client.rs‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ pub struct ImageConfig {
191191
pub user: String,
192192
#[serde(default)]
193193
pub env: Vec<String>,
194+
#[serde(default)]
195+
pub working_dir: String,
196+
#[serde(default)]
197+
pub volumes: Option<HashMap<String, Value>>,
194198
}
195199

196200
/// Whether a driver-owned volume has exactly the options `OpenShell` creates it
@@ -1226,12 +1230,12 @@ mod tests {
12261230
}
12271231

12281232
#[tokio::test]
1229-
async fn inspect_image_reads_immutable_id_and_oci_user() {
1233+
async fn inspect_image_reads_immutable_id_and_oci_config() {
12301234
let (socket_path, request_log, handle) = spawn_podman_stub(
12311235
"inspect-image",
12321236
vec![StubResponse::new(
12331237
StatusCode::OK,
1234-
r#"{"Id":"sha256:immutable","Config":{"User":"app:staff"}}"#,
1238+
r#"{"Id":"sha256:immutable","Config":{"User":"app:staff","Env":["A=one"],"WorkingDir":"/workspace/project","Volumes":{"/workspace/project/cache":{}}}}"#,
12351239
)],
12361240
);
12371241
let client = PodmanClient::new(socket_path.clone());
@@ -1246,6 +1250,20 @@ mod tests {
12461250
image.config.as_ref().map(|config| config.user.as_str()),
12471251
Some("app:staff")
12481252
);
1253+
assert_eq!(
1254+
image
1255+
.config
1256+
.as_ref()
1257+
.map(|config| config.working_dir.as_str()),
1258+
Some("/workspace/project")
1259+
);
1260+
assert!(
1261+
image
1262+
.config
1263+
.as_ref()
1264+
.and_then(|config| config.volumes.as_ref())
1265+
.is_some_and(|volumes| volumes.contains_key("/workspace/project/cache"))
1266+
);
12491267
handle.await.expect("stub task should finish");
12501268
assert_eq!(
12511269
request_log

0 commit comments

Comments
 (0)