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