@@ -307,3 +307,201 @@ func createOOMKilledContainer(
307
307
308
308
return containerID
309
309
}
310
+
311
+ var _ = framework .KubeDescribe ("Container Mount Readonly" , func () {
312
+ f := framework .NewDefaultCRIFramework ()
313
+
314
+ var rc internalapi.RuntimeService
315
+ var ic internalapi.ImageManagerService
316
+ var runtimeHandler string
317
+
318
+ BeforeEach (func () {
319
+ rc = f .CRIClient .CRIRuntimeClient
320
+ ic = f .CRIClient .CRIImageClient
321
+ runtimeHandler = framework .TestContext .RuntimeHandler
322
+ })
323
+
324
+ Context ("runtime should support readonly mounts" , func () {
325
+ var podID string
326
+ var podConfig * runtimeapi.PodSandboxConfig
327
+
328
+ BeforeEach (func () {
329
+ podID , podConfig = createPrivilegedPodSandbox (rc , true )
330
+ })
331
+
332
+ AfterEach (func () {
333
+ By ("stop PodSandbox" )
334
+ rc .StopPodSandbox (context .TODO (), podID )
335
+ By ("delete PodSandbox" )
336
+ rc .RemovePodSandbox (context .TODO (), podID )
337
+ })
338
+
339
+ testRRO := func (rc internalapi.RuntimeService , ic internalapi.ImageManagerService , rro bool ) {
340
+ if rro && ! runtimeSupportsRRO (rc , runtimeHandler ) {
341
+ Skip ("runtime does not implement recursive readonly mounts" )
342
+ return
343
+ }
344
+
345
+ By ("create host path" )
346
+ hostPath , clearHostPath := createHostPathForRROMount (podID )
347
+ defer clearHostPath () // clean up the TempDir
348
+
349
+ By ("create container with volume" )
350
+ containerID := createRROMountContainer (rc , ic , podID , podConfig , hostPath , "/mnt" , rro )
351
+
352
+ By ("test start container with volume" )
353
+ testStartContainer (rc , containerID )
354
+
355
+ By ("check whether `touch /mnt/tmpfs/file` succeeds" )
356
+ command := []string {"touch" , "/mnt/tmpfs/file" }
357
+ if rro {
358
+ command = []string {"sh" , "-c" , `touch /mnt/tmpfs/foo 2>&1 | grep -q "Read-only file system"` }
359
+ }
360
+ execSyncContainer (rc , containerID , command )
361
+ }
362
+
363
+ It ("should support non-recursive readonly mounts" , func () {
364
+ testRRO (rc , ic , false )
365
+ })
366
+ It ("should support recursive readonly mounts" , func () {
367
+ testRRO (rc , ic , true )
368
+ })
369
+ testRROInvalidPropagation := func (prop runtimeapi.MountPropagation ) {
370
+ if ! runtimeSupportsRRO (rc , runtimeHandler ) {
371
+ Skip ("runtime does not implement recursive readonly mounts" )
372
+ return
373
+ }
374
+ hostPath , clearHostPath := createHostPathForRROMount (podID )
375
+ defer clearHostPath () // clean up the TempDir
376
+ mounts := []* runtimeapi.Mount {
377
+ {
378
+ HostPath : hostPath ,
379
+ ContainerPath : "/mnt" ,
380
+ Readonly : true ,
381
+ RecursiveReadOnly : true ,
382
+ SelinuxRelabel : true ,
383
+ Propagation : prop ,
384
+ },
385
+ }
386
+ const expectErr = true
387
+ createMountContainer (rc , ic , podID , podConfig , mounts , expectErr )
388
+ }
389
+ It ("should reject a recursive readonly mount with PROPAGATION_HOST_TO_CONTAINER" , func () {
390
+ testRROInvalidPropagation (runtimeapi .MountPropagation_PROPAGATION_HOST_TO_CONTAINER )
391
+ })
392
+ It ("should reject a recursive readonly mount with PROPAGATION_BIDIRECTIONAL" , func () {
393
+ testRROInvalidPropagation (runtimeapi .MountPropagation_PROPAGATION_BIDIRECTIONAL )
394
+ })
395
+ It ("should reject a recursive readonly mount with ReadOnly: false" , func () {
396
+ if ! runtimeSupportsRRO (rc , runtimeHandler ) {
397
+ Skip ("runtime does not implement recursive readonly mounts" )
398
+ return
399
+ }
400
+ hostPath , clearHostPath := createHostPathForRROMount (podID )
401
+ defer clearHostPath () // clean up the TempDir
402
+ mounts := []* runtimeapi.Mount {
403
+ {
404
+ HostPath : hostPath ,
405
+ ContainerPath : "/mnt" ,
406
+ Readonly : false ,
407
+ RecursiveReadOnly : true ,
408
+ SelinuxRelabel : true ,
409
+ },
410
+ }
411
+ const expectErr = true
412
+ createMountContainer (rc , ic , podID , podConfig , mounts , expectErr )
413
+ })
414
+ })
415
+ })
416
+
417
+ func runtimeSupportsRRO (rc internalapi.RuntimeService , runtimeHandlerName string ) bool {
418
+ ctx := context .Background ()
419
+ status , err := rc .Status (ctx , false )
420
+ framework .ExpectNoError (err , "failed to check runtime status" )
421
+ for _ , h := range status .RuntimeHandlers {
422
+ if h .Name == runtimeHandlerName {
423
+ if f := h .Features ; f != nil {
424
+ return f .RecursiveReadOnlyMounts
425
+ }
426
+ }
427
+ }
428
+ return false
429
+ }
430
+
431
+ // createHostPath creates the hostPath for RRO mount test.
432
+ //
433
+ // hostPath contains a "tmpfs" directory with tmpfs mounted on it.
434
+ func createHostPathForRROMount (podID string ) (string , func ()) {
435
+ hostPath , err := os .MkdirTemp ("" , "test" + podID )
436
+ framework .ExpectNoError (err , "failed to create TempDir %q: %v" , hostPath , err )
437
+
438
+ tmpfsMntPoint := filepath .Join (hostPath , "tmpfs" )
439
+ err = os .MkdirAll (tmpfsMntPoint , 0700 )
440
+ framework .ExpectNoError (err , "failed to create tmpfs dir %q: %v" , tmpfsMntPoint , err )
441
+
442
+ err = unix .Mount ("none" , tmpfsMntPoint , "tmpfs" , 0 , "" )
443
+ framework .ExpectNoError (err , "failed to mount tmpfs on dir %q: %v" , tmpfsMntPoint , err )
444
+
445
+ clearHostPath := func () {
446
+ By ("clean up the TempDir" )
447
+ err := unix .Unmount (tmpfsMntPoint , unix .MNT_DETACH )
448
+ framework .ExpectNoError (err , "failed to unmount \" tmpfsMntPoint\" : %v" , err )
449
+ err = os .RemoveAll (hostPath )
450
+ framework .ExpectNoError (err , "failed to remove \" hostPath\" : %v" , err )
451
+ }
452
+
453
+ return hostPath , clearHostPath
454
+ }
455
+
456
+ func createRROMountContainer (
457
+ rc internalapi.RuntimeService ,
458
+ ic internalapi.ImageManagerService ,
459
+ podID string ,
460
+ podConfig * runtimeapi.PodSandboxConfig ,
461
+ hostPath , containerPath string ,
462
+ rro bool ,
463
+ ) string {
464
+ mounts := []* runtimeapi.Mount {
465
+ {
466
+ HostPath : hostPath ,
467
+ ContainerPath : containerPath ,
468
+ Readonly : true ,
469
+ RecursiveReadOnly : rro ,
470
+ SelinuxRelabel : true ,
471
+ },
472
+ }
473
+ return createMountContainer (rc , ic , podID , podConfig , mounts , false )
474
+ }
475
+
476
+ func createMountContainer (
477
+ rc internalapi.RuntimeService ,
478
+ ic internalapi.ImageManagerService ,
479
+ podID string ,
480
+ podConfig * runtimeapi.PodSandboxConfig ,
481
+ mounts []* runtimeapi.Mount ,
482
+ expectErr bool ,
483
+ ) string {
484
+ By ("create a container with volume and name" )
485
+ containerName := "test-mount-" + framework .NewUUID ()
486
+ containerConfig := & runtimeapi.ContainerConfig {
487
+ Metadata : framework .BuildContainerMetadata (containerName , framework .DefaultAttempt ),
488
+ Image : & runtimeapi.ImageSpec {Image : framework .TestContext .TestImageList .DefaultTestContainerImage },
489
+ Command : pauseCmd ,
490
+ Mounts : mounts ,
491
+ }
492
+
493
+ if expectErr {
494
+ _ , err := framework .CreateContainerWithError (rc , ic , containerConfig , podID , podConfig )
495
+ Expect (err ).To (HaveOccurred ())
496
+ return ""
497
+ }
498
+
499
+ containerID := framework .CreateContainer (rc , ic , containerConfig , podID , podConfig )
500
+
501
+ By ("verifying container status" )
502
+ resp , err := rc .ContainerStatus (context .TODO (), containerID , true )
503
+ framework .ExpectNoError (err , "unable to get container status" )
504
+ Expect (len (resp .Status .Mounts ), len (mounts ))
505
+
506
+ return containerID
507
+ }
0 commit comments