@@ -21,6 +21,22 @@ const BASE_IMAGE: &str = "nvcr.io/nvidia/base/ubuntu:24.04";
2121const ENGINE_ENV : & str = "OPENSHELL_TEST_CONTAINER_ENGINE" ;
2222const CREATE_TIMEOUT : Duration = Duration :: from_mins ( 10 ) ;
2323const COMMAND_TIMEOUT : Duration = Duration :: from_mins ( 2 ) ;
24+ /// Sandbox condition reason for an image `WORKDIR` the sandbox identity
25+ /// cannot use.
26+ const WORKSPACE_VALIDATION_FAILED : & str = "WorkspaceValidationFailed" ;
27+ /// A complete sandbox policy without a `process` section, so the sandbox
28+ /// identity falls back to the image `USER`.
29+ const IMAGE_IDENTITY_POLICY : & str = "version: 1
30+
31+ filesystem_policy:
32+ include_workdir: true
33+ read_only: [/usr, /lib, /lib64, /proc, /dev/urandom, /etc]
34+ read_write: [/sandbox, /tmp, /dev/null]
35+ landlock:
36+ compatibility: best_effort
37+
38+ network_policies: {}
39+ " ;
2440
2541/// A named image user that owns its custom `WORKDIR`. Existing image content
2642/// keeps its ownership.
@@ -42,14 +58,15 @@ USER app
4258 "{} test \" $(stat -c %u:%g .)\" = 1234:1235;" ,
4359 workspace_checks( "1234:1235" , "/workspace/project" , true )
4460 ) ;
45- let sandbox = create_sandbox ( runner, "named" , "nu" , & image, & checks) . await ?;
61+ let sandbox = create_sandbox ( runner, "named" , "nu" , & image, None , & checks) . await ?;
4662 file_transfer_uses_workspace ( runner, & sandbox) . await
4763 } )
4864 . await ;
4965}
5066
5167/// A numeric image user without passwd entries can reach a custom `WORKDIR`
52- /// whose parent directories are private to that user.
68+ /// whose parent directories are private to that user. The sandbox policy omits
69+ /// `process`, so the image `USER` is the only source of the sandbox identity.
5370#[ tokio:: test]
5471async fn custom_workdir_with_numeric_user_and_private_parents ( ) {
5572 run ( "oci-image/custom-workdir-numeric-user" , async |runner| {
@@ -66,8 +83,16 @@ USER 2345:2346
6683"
6784 ) ,
6885 ) ?;
86+ let policy_file = tempfile:: NamedTempFile :: new ( )
87+ . map_err ( |error| format ! ( "create policy file: {error}" ) ) ?;
88+ std:: fs:: write ( policy_file. path ( ) , IMAGE_IDENTITY_POLICY )
89+ . map_err ( |error| format ! ( "write policy file: {error}" ) ) ?;
90+ let policy = policy_file
91+ . path ( )
92+ . to_str ( )
93+ . ok_or ( "policy path is not UTF-8" ) ?;
6994 let checks = workspace_checks ( "2345:2346" , "/home/app/project" , true ) ;
70- create_sandbox ( runner, "numeric" , "pu" , & image, & checks)
95+ create_sandbox ( runner, "numeric" , "pu" , & image, Some ( policy ) , & checks)
7196 . await
7297 . map ( drop)
7398 } )
@@ -84,15 +109,16 @@ async fn default_workdir_uses_managed_workspace() {
84109 & format ! ( "FROM {BASE_IMAGE}\n USER 2345:2346\n " ) ,
85110 ) ?;
86111 let checks = workspace_checks ( "2345:2346" , "/sandbox" , false ) ;
87- create_sandbox ( runner, "default" , "dw" , & image, & checks)
112+ create_sandbox ( runner, "default" , "dw" , & image, None , & checks)
88113 . await
89114 . map ( drop)
90115 } )
91116 . await ;
92117}
93118
94119/// OpenShell rejects a custom `WORKDIR` that the image user cannot write
95- /// instead of granting the user new access to it.
120+ /// instead of granting the user new access to it, and reports that reason
121+ /// rather than a generic startup failure.
96122#[ tokio:: test]
97123async fn unwritable_custom_workdir_is_rejected ( ) {
98124 run ( "oci-image/unwritable-workdir" , async |runner| {
@@ -130,6 +156,13 @@ USER app
130156 if create. success ( ) || create. stdout ( ) . contains ( "should-not-run" ) {
131157 return Err ( create. failure_diagnostic ( "sandbox creation fails before the command runs" ) ) ;
132158 }
159+ if !create. stdout ( ) . contains ( WORKSPACE_VALIDATION_FAILED )
160+ && !create. stderr ( ) . contains ( WORKSPACE_VALIDATION_FAILED )
161+ {
162+ return Err ( create. failure_diagnostic ( & format ! (
163+ "sandbox creation fails with {WORKSPACE_VALIDATION_FAILED}"
164+ ) ) ) ;
165+ }
133166 Ok ( ( ) )
134167 } )
135168 . await ;
@@ -173,6 +206,7 @@ async fn create_sandbox(
173206 suffix : & str ,
174207 short : & str ,
175208 image : & TestImage ,
209+ policy : Option < & str > ,
176210 checks : & str ,
177211) -> Result < String , String > {
178212 // Sandbox names are limited to 19 characters on some drivers.
@@ -182,14 +216,16 @@ async fn create_sandbox(
182216 "(set -eu; {checks} touch main-write) >/tmp/oci-main.log 2>&1; \
183217 echo $? >/tmp/oci-main.status; exec sleep infinity"
184218 ) ;
219+ let mut args = vec ! [ "sandbox" , "create" , "--name" , & name, "--from" , & image. tag] ;
220+ if let Some ( policy) = policy {
221+ args. extend ( [ "--policy" , policy] ) ;
222+ }
223+ args. extend ( [ "--detach" , "--" , "sh" , "-c" , & main] ) ;
185224 runner
186225 . step ( format ! ( "{suffix}/create" ) )
187226 . description ( "sandbox starts from the test image" )
188227 . with_timeout ( CREATE_TIMEOUT )
189- . run ( & [
190- "sandbox" , "create" , "--name" , & name, "--from" , & image. tag , "--detach" , "--" , "sh" ,
191- "-c" , & main,
192- ] )
228+ . run ( & args)
193229 . await
194230 . map_err ( |error| error. to_string ( ) ) ?
195231 . require_success ( ) ?;
0 commit comments