@@ -307,3 +307,199 @@ 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
+ return f .RecursiveReadOnlyMounts
423
+ }
424
+ }
425
+ }
426
+ return false
427
+ }
428
+
429
+ // createHostPath creates the hostPath for RRO mount test.
430
+ //
431
+ // hostPath contains a "tmpfs" directory with tmpfs mounted on it.
432
+ func createHostPathForRROMount (podID string ) (string , func ()) {
433
+ hostPath , err := os .MkdirTemp ("" , "test" + podID )
434
+ framework .ExpectNoError (err , "failed to create TempDir %q: %v" , hostPath , err )
435
+
436
+ tmpfsMntPoint := filepath .Join (hostPath , "tmpfs" )
437
+ err = os .MkdirAll (tmpfsMntPoint , 0700 )
438
+ framework .ExpectNoError (err , "failed to create tmpfs dir %q: %v" , tmpfsMntPoint , err )
439
+
440
+ err = unix .Mount ("none" , tmpfsMntPoint , "tmpfs" , 0 , "" )
441
+ framework .ExpectNoError (err , "failed to mount tmpfs on dir %q: %v" , tmpfsMntPoint , err )
442
+
443
+ clearHostPath := func () {
444
+ By ("clean up the TempDir" )
445
+ err := unix .Unmount (tmpfsMntPoint , unix .MNT_DETACH )
446
+ framework .ExpectNoError (err , "failed to unmount \" tmpfsMntPoint\" : %v" , err )
447
+ err = os .RemoveAll (hostPath )
448
+ framework .ExpectNoError (err , "failed to remove \" hostPath\" : %v" , err )
449
+ }
450
+
451
+ return hostPath , clearHostPath
452
+ }
453
+
454
+ func createRROMountContainer (
455
+ rc internalapi.RuntimeService ,
456
+ ic internalapi.ImageManagerService ,
457
+ podID string ,
458
+ podConfig * runtimeapi.PodSandboxConfig ,
459
+ hostPath , containerPath string ,
460
+ rro bool ,
461
+ ) string {
462
+ mounts := []* runtimeapi.Mount {
463
+ {
464
+ HostPath : hostPath ,
465
+ ContainerPath : containerPath ,
466
+ Readonly : true ,
467
+ RecursiveReadOnly : rro ,
468
+ SelinuxRelabel : true ,
469
+ },
470
+ }
471
+ return createMountContainer (rc , ic , podID , podConfig , mounts , false )
472
+ }
473
+
474
+ func createMountContainer (
475
+ rc internalapi.RuntimeService ,
476
+ ic internalapi.ImageManagerService ,
477
+ podID string ,
478
+ podConfig * runtimeapi.PodSandboxConfig ,
479
+ mounts []* runtimeapi.Mount ,
480
+ expectErr bool ,
481
+ ) string {
482
+ By ("create a container with volume and name" )
483
+ containerName := "test-mount-" + framework .NewUUID ()
484
+ containerConfig := & runtimeapi.ContainerConfig {
485
+ Metadata : framework .BuildContainerMetadata (containerName , framework .DefaultAttempt ),
486
+ Image : & runtimeapi.ImageSpec {Image : framework .TestContext .TestImageList .DefaultTestContainerImage },
487
+ Command : pauseCmd ,
488
+ Mounts : mounts ,
489
+ }
490
+
491
+ if expectErr {
492
+ _ , err := framework .CreateContainerWithError (rc , ic , containerConfig , podID , podConfig )
493
+ Expect (err ).To (HaveOccurred ())
494
+ return ""
495
+ }
496
+
497
+ containerID := framework .CreateContainer (rc , ic , containerConfig , podID , podConfig )
498
+
499
+ By ("verifying container status" )
500
+ resp , err := rc .ContainerStatus (context .TODO (), containerID , true )
501
+ framework .ExpectNoError (err , "unable to get container status" )
502
+ Expect (len (resp .Status .Mounts ), len (mounts ))
503
+
504
+ return containerID
505
+ }
0 commit comments