Skip to content

Commit f192442

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 93cd614 commit f192442

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
@@ -92,6 +93,20 @@ binary extraction path. `supervisor_image` supplies the dynamically linked
9293
glibc `/openshell-supervisor` binary outside the workload. Image and request
9394
environment belong to agent children, never the supervisor process.
9495

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

97112
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)