github.com/apptainer/singularity@v3.1.1+incompatible/cmd/singularity/security_test.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 // +build seccomp 7 8 package main 9 10 import ( 11 "os" 12 "strings" 13 "testing" 14 15 "github.com/sylabs/singularity/internal/pkg/buildcfg" 16 "github.com/sylabs/singularity/internal/pkg/test" 17 ) 18 19 // testSecurityUnpriv tests security flag fuctionality for singularity exec without elevated privileges 20 func testSecurityUnpriv(t *testing.T) { 21 tests := []struct { 22 name string 23 image string 24 action string 25 argv []string 26 opts 27 exit int 28 expectSuccess bool 29 }{ 30 // taget UID/GID 31 {"Set_uid", imagePath, "exec", []string{"id", "-u", "|", "grep", "99"}, opts{security: []string{"uid:99"}}, 1, false}, 32 {"Set_gid", imagePath, "exec", []string{"id", "-g", "|", "grep", "99"}, opts{security: []string{"gid:99"}}, 1, false}, 33 // seccomp from json file 34 {"SecComp_BlackList", imagePath, "exec", []string{"mkdir", "/tmp/foo"}, opts{security: []string{"seccomp:./testdata/seccomp-profile.json"}}, 1, false}, 35 {"SecComp_true", imagePath, "exec", []string{"true"}, opts{security: []string{"seccomp:./testdata/seccomp-profile.json"}}, 0, true}, 36 // capabilities 37 {"capabilities_keep_true", imagePath, "exec", []string{"ping", "-c", "1", "8.8.8.8"}, opts{keepPrivs: true}, 1, false}, 38 {"capabilities_keep-false", imagePath, "exec", []string{"ping", "-c", "1", "8.8.8.8"}, opts{keepPrivs: false}, 1, false}, 39 {"capabilities_drop", imagePath, "exec", []string{"ping", "-c", "1", "8.8.8.8"}, opts{dropCaps: "CAP_NET_RAW"}, 1, false}, 40 } 41 42 for _, tt := range tests { 43 t.Run("unpriv "+tt.name, test.WithoutPrivilege(func(t *testing.T) { 44 stdout, stderr, exitCode, err := imageExec(t, tt.action, tt.opts, tt.image, tt.argv) 45 if tt.expectSuccess && (exitCode != 0) { 46 t.Log(stdout, stderr, exitCode) 47 t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.argv, " "), err) 48 } else if !tt.expectSuccess && (exitCode != 1) { 49 t.Log(stdout, stderr, exitCode) 50 t.Fatalf("unexpected success running '%v'", strings.Join(tt.argv, " ")) 51 } 52 })) 53 } 54 } 55 56 // testSecurityPriv tests security flag fuctionality for singularity exec with elevated privileges 57 func testSecurityPriv(t *testing.T) { 58 tests := []struct { 59 name string 60 image string 61 action string 62 argv []string 63 opts 64 exit int 65 expectSuccess bool 66 }{ 67 // taget UID/GID 68 {"Set_uid", imagePath, "exec", []string{"id", "-u", "|", "grep", "99"}, opts{security: []string{"uid:99"}}, 1, false}, 69 {"Set_gid", imagePath, "exec", []string{"id", "-g", "|", "grep", "99"}, opts{security: []string{"gid:99"}}, 1, false}, 70 // seccomp from json file 71 {"SecComp_BlackList", imagePath, "exec", []string{"mkdir", "/tmp/foo"}, opts{security: []string{"seccomp:./testdata/seccomp-profile.json"}}, 1, false}, 72 {"SecComp_true", imagePath, "exec", []string{"true"}, opts{security: []string{"seccomp:./testdata/seccomp-profile.json"}}, 0, true}, 73 // capabilities 74 {"capabilities_keep", imagePath, "exec", []string{"ping", "-c", "1", "8.8.8.8"}, opts{keepPrivs: true}, 0, true}, 75 {"capabilities_drop", imagePath, "exec", []string{"ping", "-c", "1", "8.8.8.8"}, opts{dropCaps: "CAP_NET_RAW"}, 1, false}, 76 } 77 78 for _, tt := range tests { 79 t.Run("priv "+tt.name, test.WithPrivilege(func(t *testing.T) { 80 stdout, stderr, exitCode, err := imageExec(t, tt.action, tt.opts, tt.image, tt.argv) 81 if tt.expectSuccess && (exitCode != 0) { 82 t.Log(stdout, stderr, exitCode) 83 t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.argv, " "), err) 84 } else if !tt.expectSuccess && (exitCode != 1) { 85 t.Log(stdout, stderr, exitCode) 86 t.Fatalf("unexpected success running '%v'", strings.Join(tt.argv, " ")) 87 } 88 })) 89 } 90 } 91 92 // testSecurityConfOwnership tests checks on config files ownerships 93 func testSecurityConfOwnership(t *testing.T) { 94 configFile := buildcfg.SYSCONFDIR + "/singularity/singularity.conf" 95 // Change file ownership (do not try this at home) 96 err := os.Chown(configFile, 1001, 0) 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 // try to run 102 t.Run("non_root_config", test.WithoutPrivilege(func(t *testing.T) { 103 _, stderr, exitCode, err := imageExec(t, "exec", opts{}, imagePath, []string{"/bin/true"}) 104 if exitCode != 1 { 105 t.Log(stderr, err) 106 t.Fatalf("unexpected success running /bin/true") 107 } 108 })) 109 110 // return file ownership to normal 111 err = os.Chown(configFile, 0, 0) 112 if err != nil { 113 t.Fatal(err) 114 } 115 } 116 117 func TestSecurity(t *testing.T) { 118 test.EnsurePrivilege(t) 119 opts := buildOpts{ 120 force: true, 121 sandbox: false, 122 } 123 if b, err := imageBuild(opts, imagePath, "../../examples/busybox/Singularity"); err != nil { 124 t.Log(string(b)) 125 t.Fatalf("unexpected failure: %v", err) 126 } 127 defer os.Remove(imagePath) 128 129 // Security 130 t.Run("Security_unpriv", testSecurityPriv) 131 t.Run("Security_priv", testSecurityUnpriv) 132 t.Run("Security_config_ownerships", testSecurityConfOwnership) 133 134 }