@@ -19,15 +19,19 @@ package container
19
19
import (
20
20
"errors"
21
21
"fmt"
22
+ "io"
22
23
"os"
23
24
"path/filepath"
25
+ "strconv"
24
26
"strings"
27
+ "syscall"
25
28
"testing"
26
29
27
30
"github.com/opencontainers/go-digest"
28
31
"gotest.tools/v3/assert"
29
32
30
33
"github.com/containerd/containerd/v2/defaults"
34
+ "github.com/containerd/nerdctl/mod/tigron/expect"
31
35
"github.com/containerd/nerdctl/mod/tigron/require"
32
36
"github.com/containerd/nerdctl/mod/tigron/test"
33
37
@@ -325,3 +329,184 @@ func TestCreateFromOCIArchive(t *testing.T) {
325
329
base .Cmd ("create" , "--rm" , "--name" , containerName , fmt .Sprintf ("oci-archive://%s" , tarPath )).AssertOK ()
326
330
base .Cmd ("start" , "--attach" , containerName ).AssertOutContains ("test-nerdctl-create-from-oci-archive" )
327
331
}
332
+
333
+ func TestUsernsMappingCreateCmd (t * testing.T ) {
334
+ nerdtest .Setup ()
335
+
336
+ testCase := & test.Case {
337
+ Require : require .All (
338
+ nerdtest .AllowModifyUserns ,
339
+ nerdtest .RemapIDs ,
340
+ require .Not (nerdtest .Docker )),
341
+ NoParallel : true ,
342
+ Setup : func (data test.Data , helpers test.Helpers ) {
343
+ data .Labels ().Set ("validUserns" , "nerdctltestuser" )
344
+ data .Labels ().Set ("expectedHostUID" , "123456789" )
345
+ data .Labels ().Set ("invalidUserns" , "invaliduser" )
346
+ },
347
+ SubTests : []* test.Case {
348
+ {
349
+ Description : "Test container create with valid Userns" ,
350
+ NoParallel : true , // Changes system config so running in non parallel mode
351
+ Setup : func (data test.Data , helpers test.Helpers ) {
352
+ err := appendUsernsConfig (data .Labels ().Get ("validUserns" ), data .Labels ().Get ("expectedHostUID" ), helpers )
353
+ assert .NilError (t , err , "Failed to append Userns config" )
354
+ },
355
+ Cleanup : func (data test.Data , helpers test.Helpers ) {
356
+ removeUsernsConfig (t , data .Labels ().Get ("validUserns" ), helpers )
357
+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
358
+ },
359
+ Command : func (data test.Data , helpers test.Helpers ) test.TestableCommand {
360
+ helpers .Ensure ("create" , "--tty" , "--userns-remap" , data .Labels ().Get ("validUserns" ), "--name" , data .Identifier (), testutil .NginxAlpineImage )
361
+ return helpers .Command ("start" , data .Identifier ())
362
+ },
363
+ Expected : func (data test.Data , helpers test.Helpers ) * test.Expected {
364
+ return & test.Expected {
365
+ ExitCode : 0 ,
366
+ Output : func (stdout string , info string , t * testing.T ) {
367
+ actualHostUID , err := getContainerHostUID (helpers , data .Identifier ())
368
+ assert .NilError (t , err , "Failed to get container host UID" )
369
+ assert .Assert (t , actualHostUID == data .Labels ().Get ("expectedHostUID" ), info )
370
+ },
371
+ }
372
+ },
373
+ },
374
+ {
375
+ Description : "Test container create failure with valid Userns and privileged flag" ,
376
+ NoParallel : true , // Changes system config so running in non parallel mode
377
+ Setup : func (data test.Data , helpers test.Helpers ) {
378
+ err := appendUsernsConfig (data .Labels ().Get ("validUserns" ), data .Labels ().Get ("expectedHostUID" ), helpers )
379
+ assert .NilError (t , err , "Failed to append Userns config" )
380
+ },
381
+ Cleanup : func (data test.Data , helpers test.Helpers ) {
382
+ removeUsernsConfig (t , data .Labels ().Get ("validUserns" ), helpers )
383
+ },
384
+ Command : func (data test.Data , helpers test.Helpers ) test.TestableCommand {
385
+ return helpers .Command ("create" , "--tty" , "--privileged" , "--userns-remap" , data .Labels ().Get ("validUserns" ), "--name" , data .Identifier (), testutil .NginxAlpineImage )
386
+ },
387
+ Expected : func (data test.Data , helpers test.Helpers ) * test.Expected {
388
+ return & test.Expected {
389
+ ExitCode : 1 ,
390
+ }
391
+ },
392
+ },
393
+ {
394
+ Description : "Test container create with invalid Userns" ,
395
+ NoParallel : true , // Changes system config so running in non parallel mode
396
+ Cleanup : func (data test.Data , helpers test.Helpers ) {
397
+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
398
+ },
399
+ Command : func (data test.Data , helpers test.Helpers ) test.TestableCommand {
400
+ return helpers .Command ("create" , "--tty" , "--userns-remap" , data .Labels ().Get ("invalidUserns" ), "--name" , data .Identifier (), testutil .NginxAlpineImage )
401
+ },
402
+ Expected : func (data test.Data , helpers test.Helpers ) * test.Expected {
403
+ return & test.Expected {
404
+ ExitCode : 1 ,
405
+ }
406
+ },
407
+ },
408
+ },
409
+ }
410
+ testCase .Run (t )
411
+ }
412
+
413
+ func getContainerHostUID (helpers test.Helpers , containerName string ) (string , error ) {
414
+ result := helpers .Capture ("inspect" , "--format" , "{{.State.Pid}}" , containerName )
415
+ pidStr := strings .TrimSpace (result )
416
+ pid , err := strconv .Atoi (pidStr )
417
+ if err != nil {
418
+ return "" , fmt .Errorf ("invalid PID: %v" , err )
419
+ }
420
+
421
+ stat , err := os .Stat (fmt .Sprintf ("/proc/%d" , pid ))
422
+ if err != nil {
423
+ return "" , fmt .Errorf ("failed to stat process: %v" , err )
424
+ }
425
+
426
+ uid := int (stat .Sys ().(* syscall.Stat_t ).Uid )
427
+ return strconv .Itoa (uid ), nil
428
+ }
429
+
430
+ func appendUsernsConfig (userns string , hostUID string , helpers test.Helpers ) error {
431
+ addUser (userns , hostUID , helpers )
432
+ entry := fmt .Sprintf ("%s:%s:65536\n " , userns , hostUID )
433
+ tempDir := helpers .T ().TempDir ()
434
+ files := []string {"subuid" , "subgid" }
435
+ for _ , file := range files {
436
+
437
+ fileBak := filepath .Join (tempDir , file )
438
+ defer os .Remove (fileBak )
439
+ d , err := os .Create (fileBak )
440
+ if err != nil {
441
+ return fmt .Errorf ("failed to create %s: %w" , fileBak , err )
442
+ }
443
+
444
+ s , err := os .Open (filepath .Join ("/etc" , file ))
445
+ if err != nil {
446
+ return fmt .Errorf ("failed to open %s: %w" , file , err )
447
+ }
448
+ defer s .Close ()
449
+
450
+ _ , err = io .Copy (d , s )
451
+ if err != nil {
452
+ return fmt .Errorf ("failed to copy %s to %s: %w" , file , fileBak , err )
453
+ }
454
+
455
+ f , err := os .OpenFile (fmt .Sprintf ("/etc/%s" , file ), os .O_APPEND | os .O_WRONLY , 0644 )
456
+ if err != nil {
457
+ return fmt .Errorf ("failed to open %s: %w" , file , err )
458
+ }
459
+ defer f .Close ()
460
+
461
+ if _ , err := f .WriteString (entry ); err != nil {
462
+ return fmt .Errorf ("failed to write to %s: %w" , file , err )
463
+ }
464
+ }
465
+ return nil
466
+ }
467
+
468
+ func addUser (username string , hostID string , helpers test.Helpers ) {
469
+ helpers .Custom ("groupadd" , "-g" , hostID , username ).Run (& test.Expected {
470
+ ExitCode : 0 })
471
+ helpers .Custom ("useradd" , "-u" , hostID , "-g" , hostID , "-s" , "/bin/false" , username ).Run (& test.Expected {
472
+ ExitCode : 0 })
473
+ }
474
+
475
+ func removeUsernsConfig (t * testing.T , userns string , helpers test.Helpers ) {
476
+ delUser (userns , helpers )
477
+ delGroup (userns , helpers )
478
+ tempDir := helpers .T ().TempDir ()
479
+ files := []string {"subuid" , "subgid" }
480
+ for _ , file := range files {
481
+ fileBak := filepath .Join (tempDir , file )
482
+ s , err := os .Open (fileBak )
483
+ if err != nil {
484
+ t .Logf ("failed to open %s, Error: %s" , fileBak , err )
485
+ continue
486
+ }
487
+ defer s .Close ()
488
+
489
+ d , err := os .Open (filepath .Join ("/etc/%s" , file ))
490
+ if err != nil {
491
+ t .Logf ("failed to open %s, Error: %s" , file , err )
492
+ continue
493
+
494
+ }
495
+ defer d .Close ()
496
+
497
+ _ , err = io .Copy (d , s )
498
+ if err != nil {
499
+ t .Logf ("failed to restore. Copy %s to %s failed, Error %s" , fileBak , file , err )
500
+ continue
501
+ }
502
+
503
+ }
504
+ }
505
+
506
+ func delUser (username string , helpers test.Helpers ) {
507
+ helpers .Custom ("userdel" , username ).Run (& test.Expected {ExitCode : expect .ExitCodeNoCheck })
508
+ }
509
+
510
+ func delGroup (groupname string , helpers test.Helpers ) {
511
+ helpers .Custom ("groupdel" , groupname ).Run (& test.Expected {ExitCode : expect .ExitCodeNoCheck })
512
+ }
0 commit comments