github.com/moby/docker@v26.1.3+incompatible/daemon/daemon_unix_test.go (about) 1 //go:build !windows 2 3 package daemon // import "github.com/docker/docker/daemon" 4 5 import ( 6 "errors" 7 "os" 8 "path/filepath" 9 "testing" 10 11 "github.com/docker/docker/api/types/blkiodev" 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/container" 14 "github.com/docker/docker/daemon/config" 15 "github.com/docker/docker/pkg/sysinfo" 16 "github.com/opencontainers/selinux/go-selinux" 17 "golang.org/x/sys/unix" 18 "gotest.tools/v3/assert" 19 is "gotest.tools/v3/assert/cmp" 20 ) 21 22 type fakeContainerGetter struct { 23 containers map[string]*container.Container 24 } 25 26 func (f *fakeContainerGetter) GetContainer(cid string) (*container.Container, error) { 27 ctr, ok := f.containers[cid] 28 if !ok { 29 return nil, errors.New("container not found") 30 } 31 return ctr, nil 32 } 33 34 // Unix test as uses settings which are not available on Windows 35 func TestAdjustSharedNamespaceContainerName(t *testing.T) { 36 fakeID := "abcdef1234567890" 37 hostConfig := &containertypes.HostConfig{ 38 IpcMode: containertypes.IpcMode("container:base"), 39 PidMode: containertypes.PidMode("container:base"), 40 NetworkMode: containertypes.NetworkMode("container:base"), 41 } 42 containerStore := &fakeContainerGetter{} 43 containerStore.containers = make(map[string]*container.Container) 44 containerStore.containers["base"] = &container.Container{ 45 ID: fakeID, 46 } 47 48 adaptSharedNamespaceContainer(containerStore, hostConfig) 49 if hostConfig.IpcMode != containertypes.IpcMode("container:"+fakeID) { 50 t.Errorf("Expected IpcMode to be container:%s", fakeID) 51 } 52 if hostConfig.PidMode != containertypes.PidMode("container:"+fakeID) { 53 t.Errorf("Expected PidMode to be container:%s", fakeID) 54 } 55 if hostConfig.NetworkMode != containertypes.NetworkMode("container:"+fakeID) { 56 t.Errorf("Expected NetworkMode to be container:%s", fakeID) 57 } 58 } 59 60 // Unix test as uses settings which are not available on Windows 61 func TestParseSecurityOptWithDeprecatedColon(t *testing.T) { 62 opts := &container.SecurityOptions{} 63 cfg := &containertypes.HostConfig{} 64 65 // test apparmor 66 cfg.SecurityOpt = []string{"apparmor=test_profile"} 67 if err := parseSecurityOpt(opts, cfg); err != nil { 68 t.Fatalf("Unexpected parseSecurityOpt error: %v", err) 69 } 70 if opts.AppArmorProfile != "test_profile" { 71 t.Fatalf(`Unexpected AppArmorProfile, expected: "test_profile", got %q`, opts.AppArmorProfile) 72 } 73 74 // test seccomp 75 sp := "/path/to/seccomp_test.json" 76 cfg.SecurityOpt = []string{"seccomp=" + sp} 77 if err := parseSecurityOpt(opts, cfg); err != nil { 78 t.Fatalf("Unexpected parseSecurityOpt error: %v", err) 79 } 80 if opts.SeccompProfile != sp { 81 t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, opts.SeccompProfile) 82 } 83 84 // test valid label 85 cfg.SecurityOpt = []string{"label=user:USER"} 86 if err := parseSecurityOpt(opts, cfg); err != nil { 87 t.Fatalf("Unexpected parseSecurityOpt error: %v", err) 88 } 89 90 // test invalid label 91 cfg.SecurityOpt = []string{"label"} 92 if err := parseSecurityOpt(opts, cfg); err == nil { 93 t.Fatal("Expected parseSecurityOpt error, got nil") 94 } 95 96 // test invalid opt 97 cfg.SecurityOpt = []string{"test"} 98 if err := parseSecurityOpt(opts, cfg); err == nil { 99 t.Fatal("Expected parseSecurityOpt error, got nil") 100 } 101 } 102 103 func TestParseSecurityOpt(t *testing.T) { 104 t.Run("apparmor", func(t *testing.T) { 105 secOpts := &container.SecurityOptions{} 106 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 107 SecurityOpt: []string{"apparmor=test_profile"}, 108 }) 109 assert.Check(t, err) 110 assert.Equal(t, secOpts.AppArmorProfile, "test_profile") 111 }) 112 t.Run("apparmor using legacy separator", func(t *testing.T) { 113 secOpts := &container.SecurityOptions{} 114 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 115 SecurityOpt: []string{"apparmor:test_profile"}, 116 }) 117 assert.Check(t, err) 118 assert.Equal(t, secOpts.AppArmorProfile, "test_profile") 119 }) 120 t.Run("seccomp", func(t *testing.T) { 121 secOpts := &container.SecurityOptions{} 122 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 123 SecurityOpt: []string{"seccomp=/path/to/seccomp_test.json"}, 124 }) 125 assert.Check(t, err) 126 assert.Equal(t, secOpts.SeccompProfile, "/path/to/seccomp_test.json") 127 }) 128 t.Run("valid label", func(t *testing.T) { 129 secOpts := &container.SecurityOptions{} 130 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 131 SecurityOpt: []string{"label=user:USER"}, 132 }) 133 assert.Check(t, err) 134 if selinux.GetEnabled() { 135 // TODO(thaJeztah): set expected labels here (or "partial" if depends on host) 136 // assert.Check(t, is.Equal(secOpts.MountLabel, "")) 137 // assert.Check(t, is.Equal(secOpts.ProcessLabel, "")) 138 } else { 139 assert.Check(t, is.Equal(secOpts.MountLabel, "")) 140 assert.Check(t, is.Equal(secOpts.ProcessLabel, "")) 141 } 142 }) 143 t.Run("invalid label", func(t *testing.T) { 144 secOpts := &container.SecurityOptions{} 145 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 146 SecurityOpt: []string{"label"}, 147 }) 148 assert.Error(t, err, `invalid --security-opt 1: "label"`) 149 }) 150 t.Run("invalid option (no value)", func(t *testing.T) { 151 secOpts := &container.SecurityOptions{} 152 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 153 SecurityOpt: []string{"unknown"}, 154 }) 155 assert.Error(t, err, `invalid --security-opt 1: "unknown"`) 156 }) 157 t.Run("unknown option", func(t *testing.T) { 158 secOpts := &container.SecurityOptions{} 159 err := parseSecurityOpt(secOpts, &containertypes.HostConfig{ 160 SecurityOpt: []string{"unknown=something"}, 161 }) 162 assert.Error(t, err, `invalid --security-opt 2: "unknown=something"`) 163 }) 164 } 165 166 func TestParseNNPSecurityOptions(t *testing.T) { 167 daemonCfg := &configStore{Config: config.Config{NoNewPrivileges: true}} 168 daemon := &Daemon{} 169 daemon.configStore.Store(daemonCfg) 170 opts := &container.SecurityOptions{} 171 cfg := &containertypes.HostConfig{} 172 173 // test NNP when "daemon:true" and "no-new-privileges=false"" 174 cfg.SecurityOpt = []string{"no-new-privileges=false"} 175 176 if err := daemon.parseSecurityOpt(&daemonCfg.Config, opts, cfg); err != nil { 177 t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err) 178 } 179 if opts.NoNewPrivileges { 180 t.Fatalf("container.NoNewPrivileges should be FALSE: %v", opts.NoNewPrivileges) 181 } 182 183 // test NNP when "daemon:false" and "no-new-privileges=true"" 184 daemonCfg.NoNewPrivileges = false 185 cfg.SecurityOpt = []string{"no-new-privileges=true"} 186 187 if err := daemon.parseSecurityOpt(&daemonCfg.Config, opts, cfg); err != nil { 188 t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err) 189 } 190 if !opts.NoNewPrivileges { 191 t.Fatalf("container.NoNewPrivileges should be TRUE: %v", opts.NoNewPrivileges) 192 } 193 } 194 195 func TestVerifyPlatformContainerResources(t *testing.T) { 196 t.Parallel() 197 var ( 198 no = false 199 yes = true 200 ) 201 202 withMemoryLimit := func(si *sysinfo.SysInfo) { 203 si.MemoryLimit = true 204 } 205 withSwapLimit := func(si *sysinfo.SysInfo) { 206 si.SwapLimit = true 207 } 208 withOomKillDisable := func(si *sysinfo.SysInfo) { 209 si.OomKillDisable = true 210 } 211 212 tests := []struct { 213 name string 214 resources containertypes.Resources 215 sysInfo sysinfo.SysInfo 216 update bool 217 expectedWarnings []string 218 }{ 219 { 220 name: "no-oom-kill-disable", 221 resources: containertypes.Resources{}, 222 sysInfo: sysInfo(t, withMemoryLimit), 223 expectedWarnings: []string{}, 224 }, 225 { 226 name: "oom-kill-disable-disabled", 227 resources: containertypes.Resources{ 228 OomKillDisable: &no, 229 }, 230 sysInfo: sysInfo(t, withMemoryLimit), 231 expectedWarnings: []string{}, 232 }, 233 { 234 name: "oom-kill-disable-not-supported", 235 resources: containertypes.Resources{ 236 OomKillDisable: &yes, 237 }, 238 sysInfo: sysInfo(t, withMemoryLimit), 239 expectedWarnings: []string{ 240 "Your kernel does not support OomKillDisable. OomKillDisable discarded.", 241 }, 242 }, 243 { 244 name: "oom-kill-disable-without-memory-constraints", 245 resources: containertypes.Resources{ 246 OomKillDisable: &yes, 247 Memory: 0, 248 }, 249 sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit), 250 expectedWarnings: []string{ 251 "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.", 252 }, 253 }, 254 { 255 name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support", 256 resources: containertypes.Resources{ 257 OomKillDisable: &yes, 258 Memory: linuxMinMemory, 259 }, 260 sysInfo: sysInfo(t, withOomKillDisable), 261 expectedWarnings: []string{ 262 "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.", 263 "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.", 264 }, 265 }, 266 { 267 name: "oom-kill-disable-with-memory-constraints", 268 resources: containertypes.Resources{ 269 OomKillDisable: &yes, 270 Memory: linuxMinMemory, 271 }, 272 sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit), 273 expectedWarnings: []string{}, 274 }, 275 } 276 for _, tc := range tests { 277 tc := tc 278 t.Run(tc.name, func(t *testing.T) { 279 t.Parallel() 280 warnings, err := verifyPlatformContainerResources(&tc.resources, &tc.sysInfo, tc.update) 281 assert.NilError(t, err) 282 for _, w := range tc.expectedWarnings { 283 assert.Assert(t, is.Contains(warnings, w)) 284 } 285 }) 286 } 287 } 288 289 func sysInfo(t *testing.T, opts ...func(*sysinfo.SysInfo)) sysinfo.SysInfo { 290 t.Helper() 291 si := sysinfo.SysInfo{} 292 293 for _, opt := range opts { 294 opt(&si) 295 } 296 297 if si.OomKillDisable { 298 t.Log(t.Name(), "OOM disable supported") 299 } 300 return si 301 } 302 303 const ( 304 // prepare major 0x1FD(509 in decimal) and minor 0x130(304) 305 DEVNO = 0x11FD30 306 MAJOR = 509 307 MINOR = 304 308 WEIGHT = 1024 309 ) 310 311 func deviceTypeMock(t *testing.T, testAndCheck func(string)) { 312 if os.Getuid() != 0 { 313 t.Skip("root required") // for mknod 314 } 315 316 t.Parallel() 317 318 tempDir, err := os.MkdirTemp("", "tempDevDir"+t.Name()) 319 assert.NilError(t, err, "create temp file") 320 tempFile := filepath.Join(tempDir, "dev") 321 322 defer os.RemoveAll(tempDir) 323 324 if err = unix.Mknod(tempFile, unix.S_IFCHR, DEVNO); err != nil { 325 t.Fatalf("mknod error %s(%x): %v", tempFile, DEVNO, err) 326 } 327 328 testAndCheck(tempFile) 329 } 330 331 func TestGetBlkioWeightDevices(t *testing.T) { 332 deviceTypeMock(t, func(tempFile string) { 333 mockResource := containertypes.Resources{ 334 BlkioWeightDevice: []*blkiodev.WeightDevice{{Path: tempFile, Weight: WEIGHT}}, 335 } 336 337 weightDevs, err := getBlkioWeightDevices(mockResource) 338 339 assert.NilError(t, err, "getBlkioWeightDevices") 340 assert.Check(t, is.Len(weightDevs, 1), "getBlkioWeightDevices") 341 assert.Check(t, weightDevs[0].Major == MAJOR, "get major device type") 342 assert.Check(t, weightDevs[0].Minor == MINOR, "get minor device type") 343 assert.Check(t, *weightDevs[0].Weight == WEIGHT, "get device weight") 344 }) 345 } 346 347 func TestGetBlkioThrottleDevices(t *testing.T) { 348 deviceTypeMock(t, func(tempFile string) { 349 mockDevs := []*blkiodev.ThrottleDevice{{Path: tempFile, Rate: WEIGHT}} 350 351 retDevs, err := getBlkioThrottleDevices(mockDevs) 352 353 assert.NilError(t, err, "getBlkioThrottleDevices") 354 assert.Check(t, is.Len(retDevs, 1), "getBlkioThrottleDevices") 355 assert.Check(t, retDevs[0].Major == MAJOR, "get major device type") 356 assert.Check(t, retDevs[0].Minor == MINOR, "get minor device type") 357 assert.Check(t, retDevs[0].Rate == WEIGHT, "get device rate") 358 }) 359 }