1256 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1256 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build !windows
 | |
| 
 | |
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package server
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/containerd/containerd/containers"
 | |
| 	"github.com/containerd/containerd/contrib/apparmor"
 | |
| 	"github.com/containerd/containerd/contrib/seccomp"
 | |
| 	"github.com/containerd/containerd/mount"
 | |
| 	"github.com/containerd/containerd/oci"
 | |
| 	imagespec "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| 	libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
 | |
| 	"github.com/opencontainers/runc/libcontainer/devices"
 | |
| 	runtimespec "github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/opencontainers/selinux/go-selinux"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
 | |
| 
 | |
| 	"github.com/containerd/cri/pkg/annotations"
 | |
| 	"github.com/containerd/cri/pkg/config"
 | |
| 	"github.com/containerd/cri/pkg/containerd/opts"
 | |
| 	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
 | |
| 	ostesting "github.com/containerd/cri/pkg/os/testing"
 | |
| 	"github.com/containerd/cri/pkg/util"
 | |
| )
 | |
| 
 | |
| func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandboxConfig,
 | |
| 	*imagespec.ImageConfig, func(*testing.T, string, string, uint32, *runtimespec.Spec)) {
 | |
| 	config := &runtime.ContainerConfig{
 | |
| 		Metadata: &runtime.ContainerMetadata{
 | |
| 			Name:    "test-name",
 | |
| 			Attempt: 1,
 | |
| 		},
 | |
| 		Image: &runtime.ImageSpec{
 | |
| 			Image: "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113799",
 | |
| 		},
 | |
| 		Command:    []string{"test", "command"},
 | |
| 		Args:       []string{"test", "args"},
 | |
| 		WorkingDir: "test-cwd",
 | |
| 		Envs: []*runtime.KeyValue{
 | |
| 			{Key: "k1", Value: "v1"},
 | |
| 			{Key: "k2", Value: "v2"},
 | |
| 			{Key: "k3", Value: "v3=v3bis"},
 | |
| 			{Key: "k4", Value: "v4=v4bis=foop"},
 | |
| 		},
 | |
| 		Mounts: []*runtime.Mount{
 | |
| 			// everything default
 | |
| 			{
 | |
| 				ContainerPath: "container-path-1",
 | |
| 				HostPath:      "host-path-1",
 | |
| 			},
 | |
| 			// readOnly
 | |
| 			{
 | |
| 				ContainerPath: "container-path-2",
 | |
| 				HostPath:      "host-path-2",
 | |
| 				Readonly:      true,
 | |
| 			},
 | |
| 		},
 | |
| 		Labels:      map[string]string{"a": "b"},
 | |
| 		Annotations: map[string]string{"ca-c": "ca-d"},
 | |
| 		Linux: &runtime.LinuxContainerConfig{
 | |
| 			Resources: &runtime.LinuxContainerResources{
 | |
| 				CpuPeriod:          100,
 | |
| 				CpuQuota:           200,
 | |
| 				CpuShares:          300,
 | |
| 				MemoryLimitInBytes: 400,
 | |
| 				OomScoreAdj:        500,
 | |
| 				CpusetCpus:         "0-1",
 | |
| 				CpusetMems:         "2-3",
 | |
| 			},
 | |
| 			SecurityContext: &runtime.LinuxContainerSecurityContext{
 | |
| 				SupplementalGroups: []int64{1111, 2222},
 | |
| 				NoNewPrivs:         true,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	sandboxConfig := &runtime.PodSandboxConfig{
 | |
| 		Metadata: &runtime.PodSandboxMetadata{
 | |
| 			Name:      "test-sandbox-name",
 | |
| 			Uid:       "test-sandbox-uid",
 | |
| 			Namespace: "test-sandbox-ns",
 | |
| 			Attempt:   2,
 | |
| 		},
 | |
| 		Annotations: map[string]string{"c": "d"},
 | |
| 		Linux: &runtime.LinuxPodSandboxConfig{
 | |
| 			CgroupParent:    "/test/cgroup/parent",
 | |
| 			SecurityContext: &runtime.LinuxSandboxSecurityContext{},
 | |
| 		},
 | |
| 	}
 | |
| 	imageConfig := &imagespec.ImageConfig{
 | |
| 		Env:        []string{"ik1=iv1", "ik2=iv2", "ik3=iv3=iv3bis", "ik4=iv4=iv4bis=boop"},
 | |
| 		Entrypoint: []string{"/entrypoint"},
 | |
| 		Cmd:        []string{"cmd"},
 | |
| 		WorkingDir: "/workspace",
 | |
| 	}
 | |
| 	specCheck := func(t *testing.T, id string, sandboxID string, sandboxPid uint32, spec *runtimespec.Spec) {
 | |
| 		assert.Equal(t, relativeRootfsPath, spec.Root.Path)
 | |
| 		assert.Equal(t, []string{"test", "command", "test", "args"}, spec.Process.Args)
 | |
| 		assert.Equal(t, "test-cwd", spec.Process.Cwd)
 | |
| 		assert.Contains(t, spec.Process.Env, "k1=v1", "k2=v2", "k3=v3=v3bis", "ik4=iv4=iv4bis=boop")
 | |
| 		assert.Contains(t, spec.Process.Env, "ik1=iv1", "ik2=iv2", "ik3=iv3=iv3bis", "k4=v4=v4bis=foop")
 | |
| 
 | |
| 		t.Logf("Check cgroups bind mount")
 | |
| 		checkMount(t, spec.Mounts, "cgroup", "/sys/fs/cgroup", "cgroup", []string{"ro"}, nil)
 | |
| 
 | |
| 		t.Logf("Check bind mount")
 | |
| 		checkMount(t, spec.Mounts, "host-path-1", "container-path-1", "bind", []string{"rbind", "rprivate", "rw"}, nil)
 | |
| 		checkMount(t, spec.Mounts, "host-path-2", "container-path-2", "bind", []string{"rbind", "rprivate", "ro"}, nil)
 | |
| 
 | |
| 		t.Logf("Check resource limits")
 | |
| 		assert.EqualValues(t, *spec.Linux.Resources.CPU.Period, 100)
 | |
| 		assert.EqualValues(t, *spec.Linux.Resources.CPU.Quota, 200)
 | |
| 		assert.EqualValues(t, *spec.Linux.Resources.CPU.Shares, 300)
 | |
| 		assert.EqualValues(t, spec.Linux.Resources.CPU.Cpus, "0-1")
 | |
| 		assert.EqualValues(t, spec.Linux.Resources.CPU.Mems, "2-3")
 | |
| 		assert.EqualValues(t, *spec.Linux.Resources.Memory.Limit, 400)
 | |
| 		assert.EqualValues(t, *spec.Process.OOMScoreAdj, 500)
 | |
| 
 | |
| 		t.Logf("Check supplemental groups")
 | |
| 		assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
 | |
| 		assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
 | |
| 
 | |
| 		t.Logf("Check no_new_privs")
 | |
| 		assert.Equal(t, spec.Process.NoNewPrivileges, true)
 | |
| 
 | |
| 		t.Logf("Check cgroup path")
 | |
| 		assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
 | |
| 
 | |
| 		t.Logf("Check namespaces")
 | |
| 		assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
 | |
| 			Type: runtimespec.NetworkNamespace,
 | |
| 			Path: opts.GetNetworkNamespace(sandboxPid),
 | |
| 		})
 | |
| 		assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
 | |
| 			Type: runtimespec.IPCNamespace,
 | |
| 			Path: opts.GetIPCNamespace(sandboxPid),
 | |
| 		})
 | |
| 		assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
 | |
| 			Type: runtimespec.UTSNamespace,
 | |
| 			Path: opts.GetUTSNamespace(sandboxPid),
 | |
| 		})
 | |
| 		assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
 | |
| 			Type: runtimespec.PIDNamespace,
 | |
| 			Path: opts.GetPIDNamespace(sandboxPid),
 | |
| 		})
 | |
| 
 | |
| 		t.Logf("Check PodSandbox annotations")
 | |
| 		assert.Contains(t, spec.Annotations, annotations.SandboxID)
 | |
| 		assert.EqualValues(t, spec.Annotations[annotations.SandboxID], sandboxID)
 | |
| 
 | |
| 		assert.Contains(t, spec.Annotations, annotations.ContainerType)
 | |
| 		assert.EqualValues(t, spec.Annotations[annotations.ContainerType], annotations.ContainerTypeContainer)
 | |
| 	}
 | |
| 	return config, sandboxConfig, imageConfig, specCheck
 | |
| }
 | |
| 
 | |
| func TestContainerCapabilities(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		capability *runtime.Capability
 | |
| 		includes   []string
 | |
| 		excludes   []string
 | |
| 	}{
 | |
| 		"should be able to add/drop capabilities": {
 | |
| 			capability: &runtime.Capability{
 | |
| 				AddCapabilities:  []string{"SYS_ADMIN"},
 | |
| 				DropCapabilities: []string{"CHOWN"},
 | |
| 			},
 | |
| 			includes: []string{"CAP_SYS_ADMIN"},
 | |
| 			excludes: []string{"CAP_CHOWN"},
 | |
| 		},
 | |
| 		"should be able to add all capabilities": {
 | |
| 			capability: &runtime.Capability{
 | |
| 				AddCapabilities: []string{"ALL"},
 | |
| 			},
 | |
| 			includes: oci.GetAllCapabilities(),
 | |
| 		},
 | |
| 		"should be able to drop all capabilities": {
 | |
| 			capability: &runtime.Capability{
 | |
| 				DropCapabilities: []string{"ALL"},
 | |
| 			},
 | |
| 			excludes: oci.GetAllCapabilities(),
 | |
| 		},
 | |
| 		"should be able to drop capabilities with add all": {
 | |
| 			capability: &runtime.Capability{
 | |
| 				AddCapabilities:  []string{"ALL"},
 | |
| 				DropCapabilities: []string{"CHOWN"},
 | |
| 			},
 | |
| 			includes: util.SubtractStringSlice(oci.GetAllCapabilities(), "CAP_CHOWN"),
 | |
| 			excludes: []string{"CAP_CHOWN"},
 | |
| 		},
 | |
| 		"should be able to add capabilities with drop all": {
 | |
| 			capability: &runtime.Capability{
 | |
| 				AddCapabilities:  []string{"SYS_ADMIN"},
 | |
| 				DropCapabilities: []string{"ALL"},
 | |
| 			},
 | |
| 			includes: []string{"CAP_SYS_ADMIN"},
 | |
| 			excludes: util.SubtractStringSlice(oci.GetAllCapabilities(), "CAP_SYS_ADMIN"),
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 		ociRuntime := config.Runtime{}
 | |
| 		c := newTestCRIService()
 | |
| 
 | |
| 		containerConfig.Linux.SecurityContext.Capabilities = test.capability
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		if selinux.GetEnabled() {
 | |
| 			assert.NotEqual(t, "", spec.Process.SelinuxLabel)
 | |
| 			assert.NotEqual(t, "", spec.Linux.MountLabel)
 | |
| 		}
 | |
| 
 | |
| 		specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		for _, include := range test.includes {
 | |
| 			assert.Contains(t, spec.Process.Capabilities.Bounding, include)
 | |
| 			assert.Contains(t, spec.Process.Capabilities.Effective, include)
 | |
| 			assert.Contains(t, spec.Process.Capabilities.Inheritable, include)
 | |
| 			assert.Contains(t, spec.Process.Capabilities.Permitted, include)
 | |
| 		}
 | |
| 		for _, exclude := range test.excludes {
 | |
| 			assert.NotContains(t, spec.Process.Capabilities.Bounding, exclude)
 | |
| 			assert.NotContains(t, spec.Process.Capabilities.Effective, exclude)
 | |
| 			assert.NotContains(t, spec.Process.Capabilities.Inheritable, exclude)
 | |
| 			assert.NotContains(t, spec.Process.Capabilities.Permitted, exclude)
 | |
| 		}
 | |
| 		assert.Empty(t, spec.Process.Capabilities.Ambient)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestContainerSpecTty(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	for _, tty := range []bool{true, false} {
 | |
| 		containerConfig.Tty = tty
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		assert.Equal(t, tty, spec.Process.Terminal)
 | |
| 		if tty {
 | |
| 			assert.Contains(t, spec.Process.Env, "TERM=xterm")
 | |
| 		} else {
 | |
| 			assert.NotContains(t, spec.Process.Env, "TERM=xterm")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestContainerSpecDefaultPath(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	expectedDefault := "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	for _, pathenv := range []string{"", "PATH=/usr/local/bin/games"} {
 | |
| 		expected := expectedDefault
 | |
| 		if pathenv != "" {
 | |
| 			imageConfig.Env = append(imageConfig.Env, pathenv)
 | |
| 			expected = pathenv
 | |
| 		}
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		assert.Contains(t, spec.Process.Env, expected)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestContainerSpecReadonlyRootfs(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	for _, readonly := range []bool{true, false} {
 | |
| 		containerConfig.Linux.SecurityContext.ReadonlyRootfs = readonly
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		assert.Equal(t, readonly, spec.Root.Readonly)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestContainerSpecWithExtraMounts(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	mountInConfig := &runtime.Mount{
 | |
| 		// Test cleanpath
 | |
| 		ContainerPath: "test-container-path/",
 | |
| 		HostPath:      "test-host-path",
 | |
| 		Readonly:      false,
 | |
| 	}
 | |
| 	containerConfig.Mounts = append(containerConfig.Mounts, mountInConfig)
 | |
| 	extraMounts := []*runtime.Mount{
 | |
| 		{
 | |
| 			ContainerPath: "test-container-path",
 | |
| 			HostPath:      "test-host-path-extra",
 | |
| 			Readonly:      true,
 | |
| 		},
 | |
| 		{
 | |
| 			ContainerPath: "/sys",
 | |
| 			HostPath:      "test-sys-extra",
 | |
| 			Readonly:      false,
 | |
| 		},
 | |
| 		{
 | |
| 			ContainerPath: "/dev",
 | |
| 			HostPath:      "test-dev-extra",
 | |
| 			Readonly:      false,
 | |
| 		},
 | |
| 	}
 | |
| 	spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, extraMounts, ociRuntime)
 | |
| 	require.NoError(t, err)
 | |
| 	specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 	var mounts, sysMounts, devMounts []runtimespec.Mount
 | |
| 	for _, m := range spec.Mounts {
 | |
| 		if strings.HasPrefix(m.Destination, "test-container-path") {
 | |
| 			mounts = append(mounts, m)
 | |
| 		} else if m.Destination == "/sys" {
 | |
| 			sysMounts = append(sysMounts, m)
 | |
| 		} else if strings.HasPrefix(m.Destination, "/dev") {
 | |
| 			devMounts = append(devMounts, m)
 | |
| 		}
 | |
| 	}
 | |
| 	t.Logf("CRI mount should override extra mount")
 | |
| 	require.Len(t, mounts, 1)
 | |
| 	assert.Equal(t, "test-host-path", mounts[0].Source)
 | |
| 	assert.Contains(t, mounts[0].Options, "rw")
 | |
| 
 | |
| 	t.Logf("Extra mount should override default mount")
 | |
| 	require.Len(t, sysMounts, 1)
 | |
| 	assert.Equal(t, "test-sys-extra", sysMounts[0].Source)
 | |
| 	assert.Contains(t, sysMounts[0].Options, "rw")
 | |
| 
 | |
| 	t.Logf("Dev mount should override all default dev mounts")
 | |
| 	require.Len(t, devMounts, 1)
 | |
| 	assert.Equal(t, "test-dev-extra", devMounts[0].Source)
 | |
| 	assert.Contains(t, devMounts[0].Options, "rw")
 | |
| }
 | |
| 
 | |
| func TestContainerAndSandboxPrivileged(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		containerPrivileged bool
 | |
| 		sandboxPrivileged   bool
 | |
| 		expectError         bool
 | |
| 	}{
 | |
| 		"privileged container in non-privileged sandbox should fail": {
 | |
| 			containerPrivileged: true,
 | |
| 			sandboxPrivileged:   false,
 | |
| 			expectError:         true,
 | |
| 		},
 | |
| 		"privileged container in privileged sandbox should be fine": {
 | |
| 			containerPrivileged: true,
 | |
| 			sandboxPrivileged:   true,
 | |
| 			expectError:         false,
 | |
| 		},
 | |
| 		"non-privileged container in privileged sandbox should be fine": {
 | |
| 			containerPrivileged: false,
 | |
| 			sandboxPrivileged:   true,
 | |
| 			expectError:         false,
 | |
| 		},
 | |
| 		"non-privileged container in non-privileged sandbox should be fine": {
 | |
| 			containerPrivileged: false,
 | |
| 			sandboxPrivileged:   false,
 | |
| 			expectError:         false,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		containerConfig.Linux.SecurityContext.Privileged = test.containerPrivileged
 | |
| 		sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
 | |
| 			Privileged: test.sandboxPrivileged,
 | |
| 		}
 | |
| 		_, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		if test.expectError {
 | |
| 			assert.Error(t, err)
 | |
| 		} else {
 | |
| 			assert.NoError(t, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestContainerMounts(t *testing.T) {
 | |
| 	const testSandboxID = "test-id"
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		statFn          func(string) (os.FileInfo, error)
 | |
| 		criMounts       []*runtime.Mount
 | |
| 		securityContext *runtime.LinuxContainerSecurityContext
 | |
| 		expectedMounts  []*runtime.Mount
 | |
| 	}{
 | |
| 		"should setup ro mount when rootfs is read-only": {
 | |
| 			securityContext: &runtime.LinuxContainerSecurityContext{
 | |
| 				ReadonlyRootfs: true,
 | |
| 			},
 | |
| 			expectedMounts: []*runtime.Mount{
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hostname",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hostname"),
 | |
| 					Readonly:      true,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hosts",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hosts"),
 | |
| 					Readonly:      true,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: resolvConfPath,
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "resolv.conf"),
 | |
| 					Readonly:      true,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/dev/shm",
 | |
| 					HostPath:      filepath.Join(testStateDir, sandboxesDir, testSandboxID, "shm"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		"should setup rw mount when rootfs is read-write": {
 | |
| 			securityContext: &runtime.LinuxContainerSecurityContext{},
 | |
| 			expectedMounts: []*runtime.Mount{
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hostname",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hostname"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hosts",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hosts"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: resolvConfPath,
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "resolv.conf"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/dev/shm",
 | |
| 					HostPath:      filepath.Join(testStateDir, sandboxesDir, testSandboxID, "shm"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		"should use host /dev/shm when host ipc is set": {
 | |
| 			securityContext: &runtime.LinuxContainerSecurityContext{
 | |
| 				NamespaceOptions: &runtime.NamespaceOption{Ipc: runtime.NamespaceMode_NODE},
 | |
| 			},
 | |
| 			expectedMounts: []*runtime.Mount{
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hostname",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hostname"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hosts",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hosts"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: resolvConfPath,
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "resolv.conf"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/dev/shm",
 | |
| 					HostPath:      "/dev/shm",
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		"should skip container mounts if already mounted by CRI": {
 | |
| 			criMounts: []*runtime.Mount{
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hostname",
 | |
| 					HostPath:      "/test-etc-hostname",
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hosts",
 | |
| 					HostPath:      "/test-etc-host",
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: resolvConfPath,
 | |
| 					HostPath:      "test-resolv-conf",
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/dev/shm",
 | |
| 					HostPath:      "test-dev-shm",
 | |
| 				},
 | |
| 			},
 | |
| 			securityContext: &runtime.LinuxContainerSecurityContext{},
 | |
| 			expectedMounts:  nil,
 | |
| 		},
 | |
| 		"should skip hostname mount if the old sandbox doesn't have hostname file": {
 | |
| 			statFn: func(path string) (os.FileInfo, error) {
 | |
| 				assert.Equal(t, filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hostname"), path)
 | |
| 				return nil, errors.New("random error")
 | |
| 			},
 | |
| 			securityContext: &runtime.LinuxContainerSecurityContext{},
 | |
| 			expectedMounts: []*runtime.Mount{
 | |
| 				{
 | |
| 					ContainerPath: "/etc/hosts",
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "hosts"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: resolvConfPath,
 | |
| 					HostPath:      filepath.Join(testRootDir, sandboxesDir, testSandboxID, "resolv.conf"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 				{
 | |
| 					ContainerPath: "/dev/shm",
 | |
| 					HostPath:      filepath.Join(testStateDir, sandboxesDir, testSandboxID, "shm"),
 | |
| 					Readonly:      false,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	} {
 | |
| 		config := &runtime.ContainerConfig{
 | |
| 			Metadata: &runtime.ContainerMetadata{
 | |
| 				Name:    "test-name",
 | |
| 				Attempt: 1,
 | |
| 			},
 | |
| 			Mounts: test.criMounts,
 | |
| 			Linux: &runtime.LinuxContainerConfig{
 | |
| 				SecurityContext: test.securityContext,
 | |
| 			},
 | |
| 		}
 | |
| 		c := newTestCRIService()
 | |
| 		c.os.(*ostesting.FakeOS).StatFn = test.statFn
 | |
| 		mounts := c.containerMounts(testSandboxID, config)
 | |
| 		assert.Equal(t, test.expectedMounts, mounts, desc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPrivilegedBindMount(t *testing.T) {
 | |
| 	testPid := uint32(1234)
 | |
| 	c := newTestCRIService()
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		privileged         bool
 | |
| 		expectedSysFSRO    bool
 | |
| 		expectedCgroupFSRO bool
 | |
| 	}{
 | |
| 		"sysfs and cgroupfs should mount as 'ro' by default": {
 | |
| 			expectedSysFSRO:    true,
 | |
| 			expectedCgroupFSRO: true,
 | |
| 		},
 | |
| 		"sysfs and cgroupfs should not mount as 'ro' if privileged": {
 | |
| 			privileged:         true,
 | |
| 			expectedSysFSRO:    false,
 | |
| 			expectedCgroupFSRO: false,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 
 | |
| 		containerConfig.Linux.SecurityContext.Privileged = test.privileged
 | |
| 		sandboxConfig.Linux.SecurityContext.Privileged = test.privileged
 | |
| 
 | |
| 		spec, err := c.containerSpec(t.Name(), testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 
 | |
| 		assert.NoError(t, err)
 | |
| 		if test.expectedSysFSRO {
 | |
| 			checkMount(t, spec.Mounts, "sysfs", "/sys", "sysfs", []string{"ro"}, []string{"rw"})
 | |
| 		} else {
 | |
| 			checkMount(t, spec.Mounts, "sysfs", "/sys", "sysfs", []string{"rw"}, []string{"ro"})
 | |
| 		}
 | |
| 		if test.expectedCgroupFSRO {
 | |
| 			checkMount(t, spec.Mounts, "cgroup", "/sys/fs/cgroup", "cgroup", []string{"ro"}, []string{"rw"})
 | |
| 		} else {
 | |
| 			checkMount(t, spec.Mounts, "cgroup", "/sys/fs/cgroup", "cgroup", []string{"rw"}, []string{"ro"})
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMountPropagation(t *testing.T) {
 | |
| 
 | |
| 	sharedLookupMountFn := func(string) (mount.Info, error) {
 | |
| 		return mount.Info{
 | |
| 			Mountpoint: "host-path",
 | |
| 			Optional:   "shared:",
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	slaveLookupMountFn := func(string) (mount.Info, error) {
 | |
| 		return mount.Info{
 | |
| 			Mountpoint: "host-path",
 | |
| 			Optional:   "master:",
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	othersLookupMountFn := func(string) (mount.Info, error) {
 | |
| 		return mount.Info{
 | |
| 			Mountpoint: "host-path",
 | |
| 			Optional:   "others",
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		criMount          *runtime.Mount
 | |
| 		fakeLookupMountFn func(string) (mount.Info, error)
 | |
| 		optionsCheck      []string
 | |
| 		expectErr         bool
 | |
| 	}{
 | |
| 		"HostPath should mount as 'rprivate' if propagation is MountPropagation_PROPAGATION_PRIVATE": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation_PROPAGATION_PRIVATE,
 | |
| 			},
 | |
| 			fakeLookupMountFn: nil,
 | |
| 			optionsCheck:      []string{"rbind", "rprivate"},
 | |
| 			expectErr:         false,
 | |
| 		},
 | |
| 		"HostPath should mount as 'rslave' if propagation is MountPropagation_PROPAGATION_HOST_TO_CONTAINER": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
 | |
| 			},
 | |
| 			fakeLookupMountFn: slaveLookupMountFn,
 | |
| 			optionsCheck:      []string{"rbind", "rslave"},
 | |
| 			expectErr:         false,
 | |
| 		},
 | |
| 		"HostPath should mount as 'rshared' if propagation is MountPropagation_PROPAGATION_BIDIRECTIONAL": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL,
 | |
| 			},
 | |
| 			fakeLookupMountFn: sharedLookupMountFn,
 | |
| 			optionsCheck:      []string{"rbind", "rshared"},
 | |
| 			expectErr:         false,
 | |
| 		},
 | |
| 		"HostPath should mount as 'rprivate' if propagation is illegal": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation(42),
 | |
| 			},
 | |
| 			fakeLookupMountFn: nil,
 | |
| 			optionsCheck:      []string{"rbind", "rprivate"},
 | |
| 			expectErr:         false,
 | |
| 		},
 | |
| 		"Expect an error if HostPath isn't shared and mount propagation is MountPropagation_PROPAGATION_BIDIRECTIONAL": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL,
 | |
| 			},
 | |
| 			fakeLookupMountFn: slaveLookupMountFn,
 | |
| 			expectErr:         true,
 | |
| 		},
 | |
| 		"Expect an error if HostPath isn't slave or shared and mount propagation is MountPropagation_PROPAGATION_HOST_TO_CONTAINER": {
 | |
| 			criMount: &runtime.Mount{
 | |
| 				ContainerPath: "container-path",
 | |
| 				HostPath:      "host-path",
 | |
| 				Propagation:   runtime.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
 | |
| 			},
 | |
| 			fakeLookupMountFn: othersLookupMountFn,
 | |
| 			expectErr:         true,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		c := newTestCRIService()
 | |
| 		c.os.(*ostesting.FakeOS).LookupMountFn = test.fakeLookupMountFn
 | |
| 		config, _, _, _ := getCreateContainerTestData()
 | |
| 
 | |
| 		var spec runtimespec.Spec
 | |
| 		spec.Linux = &runtimespec.Linux{}
 | |
| 
 | |
| 		err := opts.WithMounts(c.os, config, []*runtime.Mount{test.criMount}, "")(context.Background(), nil, nil, &spec)
 | |
| 		if test.expectErr {
 | |
| 			require.Error(t, err)
 | |
| 		} else {
 | |
| 			require.NoError(t, err)
 | |
| 			checkMount(t, spec.Mounts, test.criMount.HostPath, test.criMount.ContainerPath, "bind", test.optionsCheck, nil)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPidNamespace(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testPid := uint32(1234)
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		pidNS    runtime.NamespaceMode
 | |
| 		expected runtimespec.LinuxNamespace
 | |
| 	}{
 | |
| 		"node namespace mode": {
 | |
| 			pidNS: runtime.NamespaceMode_NODE,
 | |
| 			expected: runtimespec.LinuxNamespace{
 | |
| 				Type: runtimespec.PIDNamespace,
 | |
| 				Path: opts.GetPIDNamespace(testPid),
 | |
| 			},
 | |
| 		},
 | |
| 		"container namespace mode": {
 | |
| 			pidNS: runtime.NamespaceMode_CONTAINER,
 | |
| 			expected: runtimespec.LinuxNamespace{
 | |
| 				Type: runtimespec.PIDNamespace,
 | |
| 			},
 | |
| 		},
 | |
| 		"pod namespace mode": {
 | |
| 			pidNS: runtime.NamespaceMode_POD,
 | |
| 			expected: runtimespec.LinuxNamespace{
 | |
| 				Type: runtimespec.PIDNamespace,
 | |
| 				Path: opts.GetPIDNamespace(testPid),
 | |
| 			},
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		containerConfig.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{Pid: test.pidNS}
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		assert.Contains(t, spec.Linux.Namespaces, test.expected)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestNoDefaultRunMount(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testPid := uint32(1234)
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 
 | |
| 	spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 	assert.NoError(t, err)
 | |
| 	for _, mount := range spec.Mounts {
 | |
| 		assert.NotEqual(t, "/run", mount.Destination)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGenerateSeccompSpecOpts(t *testing.T) {
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		profile        string
 | |
| 		privileged     bool
 | |
| 		disable        bool
 | |
| 		specOpts       oci.SpecOpts
 | |
| 		expectErr      bool
 | |
| 		defaultProfile string
 | |
| 	}{
 | |
| 		"should return error if seccomp is specified when seccomp is not supported": {
 | |
| 			profile:   runtimeDefault,
 | |
| 			disable:   true,
 | |
| 			expectErr: true,
 | |
| 		},
 | |
| 		"should not return error if seccomp is not specified when seccomp is not supported": {
 | |
| 			profile: "",
 | |
| 			disable: true,
 | |
| 		},
 | |
| 		"should not return error if seccomp is unconfined when seccomp is not supported": {
 | |
| 			profile: unconfinedProfile,
 | |
| 			disable: true,
 | |
| 		},
 | |
| 		"should not set seccomp when privileged is true": {
 | |
| 			profile:    seccompDefaultProfile,
 | |
| 			privileged: true,
 | |
| 		},
 | |
| 		"should not set seccomp when seccomp is unconfined": {
 | |
| 			profile: unconfinedProfile,
 | |
| 		},
 | |
| 		"should not set seccomp when seccomp is not specified": {
 | |
| 			profile: "",
 | |
| 		},
 | |
| 		"should set default seccomp when seccomp is runtime/default": {
 | |
| 			profile:  runtimeDefault,
 | |
| 			specOpts: seccomp.WithDefaultProfile(),
 | |
| 		},
 | |
| 		"should set default seccomp when seccomp is docker/default": {
 | |
| 			profile:  dockerDefault,
 | |
| 			specOpts: seccomp.WithDefaultProfile(),
 | |
| 		},
 | |
| 		"should set specified profile when local profile is specified": {
 | |
| 			profile:  profileNamePrefix + "test-profile",
 | |
| 			specOpts: seccomp.WithProfile("test-profile"),
 | |
| 		},
 | |
| 		"should return error if specified profile is invalid": {
 | |
| 			profile:   "test-profile",
 | |
| 			expectErr: true,
 | |
| 		},
 | |
| 		"should use default profile when seccomp is empty": {
 | |
| 			defaultProfile: profileNamePrefix + "test-profile",
 | |
| 			specOpts:       seccomp.WithProfile("test-profile"),
 | |
| 		},
 | |
| 		"should fallback to docker/default when seccomp is empty and default is runtime/default": {
 | |
| 			defaultProfile: runtimeDefault,
 | |
| 			specOpts:       seccomp.WithDefaultProfile(),
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(fmt.Sprintf("TestCase %q", desc), func(t *testing.T) {
 | |
| 			cri := &criService{}
 | |
| 			cri.config.UnsetSeccompProfile = test.defaultProfile
 | |
| 			specOpts, err := cri.generateSeccompSpecOpts(test.profile, test.privileged, !test.disable)
 | |
| 			assert.Equal(t,
 | |
| 				reflect.ValueOf(test.specOpts).Pointer(),
 | |
| 				reflect.ValueOf(specOpts).Pointer())
 | |
| 			if test.expectErr {
 | |
| 				assert.Error(t, err)
 | |
| 			} else {
 | |
| 				assert.NoError(t, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGenerateApparmorSpecOpts(t *testing.T) {
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		profile    string
 | |
| 		privileged bool
 | |
| 		disable    bool
 | |
| 		specOpts   oci.SpecOpts
 | |
| 		expectErr  bool
 | |
| 	}{
 | |
| 		"should return error if apparmor is specified when apparmor is not supported": {
 | |
| 			profile:   runtimeDefault,
 | |
| 			disable:   true,
 | |
| 			expectErr: true,
 | |
| 		},
 | |
| 		"should not return error if apparmor is not specified when apparmor is not supported": {
 | |
| 			profile: "",
 | |
| 			disable: true,
 | |
| 		},
 | |
| 		"should set default apparmor when apparmor is not specified": {
 | |
| 			profile:  "",
 | |
| 			specOpts: apparmor.WithDefaultProfile(appArmorDefaultProfileName),
 | |
| 		},
 | |
| 		"should not apparmor when apparmor is not specified and privileged is true": {
 | |
| 			profile:    "",
 | |
| 			privileged: true,
 | |
| 		},
 | |
| 		"should not return error if apparmor is unconfined when apparmor is not supported": {
 | |
| 			profile: unconfinedProfile,
 | |
| 			disable: true,
 | |
| 		},
 | |
| 		"should not apparmor when apparmor is unconfined": {
 | |
| 			profile: unconfinedProfile,
 | |
| 		},
 | |
| 		"should not apparmor when apparmor is unconfined and privileged is true": {
 | |
| 			profile:    unconfinedProfile,
 | |
| 			privileged: true,
 | |
| 		},
 | |
| 		"should set default apparmor when apparmor is runtime/default": {
 | |
| 			profile:  runtimeDefault,
 | |
| 			specOpts: apparmor.WithDefaultProfile(appArmorDefaultProfileName),
 | |
| 		},
 | |
| 		"should not apparmor when apparmor is default and privileged is true": {
 | |
| 			profile:    runtimeDefault,
 | |
| 			privileged: true,
 | |
| 		},
 | |
| 		// TODO (mikebrow) add success with exising defined profile tests
 | |
| 		"should return error when undefined local profile is specified": {
 | |
| 			profile:   profileNamePrefix + "test-profile",
 | |
| 			expectErr: true,
 | |
| 		},
 | |
| 		"should return error when undefined local profile is specified and privileged is true": {
 | |
| 			profile:    profileNamePrefix + "test-profile",
 | |
| 			privileged: true,
 | |
| 			expectErr:  true,
 | |
| 		},
 | |
| 		"should return error if specified profile is invalid": {
 | |
| 			profile:   "test-profile",
 | |
| 			expectErr: true,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		specOpts, err := generateApparmorSpecOpts(test.profile, test.privileged, !test.disable)
 | |
| 		assert.Equal(t,
 | |
| 			reflect.ValueOf(test.specOpts).Pointer(),
 | |
| 			reflect.ValueOf(specOpts).Pointer())
 | |
| 		if test.expectErr {
 | |
| 			assert.Error(t, err)
 | |
| 		} else {
 | |
| 			assert.NoError(t, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMaskedAndReadonlyPaths(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 
 | |
| 	defaultSpec, err := oci.GenerateSpec(ctrdutil.NamespacedContext(), nil, &containers.Container{ID: testID})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		disableProcMount bool
 | |
| 		masked           []string
 | |
| 		readonly         []string
 | |
| 		expectedMasked   []string
 | |
| 		expectedReadonly []string
 | |
| 		privileged       bool
 | |
| 	}{
 | |
| 		"should apply default if not specified when disable_proc_mount = true": {
 | |
| 			disableProcMount: true,
 | |
| 			masked:           nil,
 | |
| 			readonly:         nil,
 | |
| 			expectedMasked:   defaultSpec.Linux.MaskedPaths,
 | |
| 			expectedReadonly: defaultSpec.Linux.ReadonlyPaths,
 | |
| 			privileged:       false,
 | |
| 		},
 | |
| 		"should always apply CRI specified paths when disable_proc_mount = false": {
 | |
| 			disableProcMount: false,
 | |
| 			masked:           nil,
 | |
| 			readonly:         nil,
 | |
| 			expectedMasked:   nil,
 | |
| 			expectedReadonly: nil,
 | |
| 			privileged:       false,
 | |
| 		},
 | |
| 		"should be able to specify empty paths": {
 | |
| 			masked:           []string{},
 | |
| 			readonly:         []string{},
 | |
| 			expectedMasked:   []string{},
 | |
| 			expectedReadonly: []string{},
 | |
| 			privileged:       false,
 | |
| 		},
 | |
| 		"should apply CRI specified paths": {
 | |
| 			masked:           []string{"/proc"},
 | |
| 			readonly:         []string{"/sys"},
 | |
| 			expectedMasked:   []string{"/proc"},
 | |
| 			expectedReadonly: []string{"/sys"},
 | |
| 			privileged:       false,
 | |
| 		},
 | |
| 		"default should be nil for privileged": {
 | |
| 			expectedMasked:   nil,
 | |
| 			expectedReadonly: nil,
 | |
| 			privileged:       true,
 | |
| 		},
 | |
| 		"should be able to specify empty paths, esp. if privileged": {
 | |
| 			masked:           []string{},
 | |
| 			readonly:         []string{},
 | |
| 			expectedMasked:   nil,
 | |
| 			expectedReadonly: nil,
 | |
| 			privileged:       true,
 | |
| 		},
 | |
| 		"should not apply CRI specified paths if privileged": {
 | |
| 			masked:           []string{"/proc"},
 | |
| 			readonly:         []string{"/sys"},
 | |
| 			expectedMasked:   nil,
 | |
| 			expectedReadonly: nil,
 | |
| 			privileged:       true,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		c.config.DisableProcMount = test.disableProcMount
 | |
| 		containerConfig.Linux.SecurityContext.MaskedPaths = test.masked
 | |
| 		containerConfig.Linux.SecurityContext.ReadonlyPaths = test.readonly
 | |
| 		containerConfig.Linux.SecurityContext.Privileged = test.privileged
 | |
| 		sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
 | |
| 			Privileged: test.privileged,
 | |
| 		}
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		if !test.privileged { // specCheck presumes an unprivileged container
 | |
| 			specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		}
 | |
| 		assert.Equal(t, test.expectedMasked, spec.Linux.MaskedPaths)
 | |
| 		assert.Equal(t, test.expectedReadonly, spec.Linux.ReadonlyPaths)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestHostname(t *testing.T) {
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	c.os.(*ostesting.FakeOS).HostnameFn = func() (string, error) {
 | |
| 		return "real-hostname", nil
 | |
| 	}
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		hostname    string
 | |
| 		networkNs   runtime.NamespaceMode
 | |
| 		expectedEnv string
 | |
| 	}{
 | |
| 		"should add HOSTNAME=sandbox.Hostname for pod network namespace": {
 | |
| 			hostname:    "test-hostname",
 | |
| 			networkNs:   runtime.NamespaceMode_POD,
 | |
| 			expectedEnv: "HOSTNAME=test-hostname",
 | |
| 		},
 | |
| 		"should add HOSTNAME=sandbox.Hostname for host network namespace": {
 | |
| 			hostname:    "test-hostname",
 | |
| 			networkNs:   runtime.NamespaceMode_NODE,
 | |
| 			expectedEnv: "HOSTNAME=test-hostname",
 | |
| 		},
 | |
| 		"should add HOSTNAME=os.Hostname for host network namespace if sandbox.Hostname is not set": {
 | |
| 			hostname:    "",
 | |
| 			networkNs:   runtime.NamespaceMode_NODE,
 | |
| 			expectedEnv: "HOSTNAME=real-hostname",
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 		sandboxConfig.Hostname = test.hostname
 | |
| 		sandboxConfig.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
 | |
| 			NamespaceOptions: &runtime.NamespaceOption{Network: test.networkNs},
 | |
| 		}
 | |
| 		spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		require.NoError(t, err)
 | |
| 		specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 		assert.Contains(t, spec.Process.Env, test.expectedEnv)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestDisableCgroup(t *testing.T) {
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	c := newTestCRIService()
 | |
| 	c.config.DisableCgroup = true
 | |
| 	spec, err := c.containerSpec("test-id", "sandbox-id", 1234, "", "container-name", containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Log("resource limit should not be set")
 | |
| 	assert.Nil(t, spec.Linux.Resources.Memory)
 | |
| 	assert.Nil(t, spec.Linux.Resources.CPU)
 | |
| 
 | |
| 	t.Log("cgroup path should be empty")
 | |
| 	assert.Empty(t, spec.Linux.CgroupsPath)
 | |
| }
 | |
| 
 | |
| func TestGenerateUserString(t *testing.T) {
 | |
| 	type testcase struct {
 | |
| 		// the name of the test case
 | |
| 		name string
 | |
| 
 | |
| 		u        string
 | |
| 		uid, gid *runtime.Int64Value
 | |
| 
 | |
| 		result        string
 | |
| 		expectedError bool
 | |
| 	}
 | |
| 	testcases := []testcase{
 | |
| 		{
 | |
| 			name:   "Empty",
 | |
| 			result: "",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "Username Only",
 | |
| 			u:      "testuser",
 | |
| 			result: "testuser",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "Username, UID",
 | |
| 			u:      "testuser",
 | |
| 			uid:    &runtime.Int64Value{Value: 1},
 | |
| 			result: "testuser",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "Username, UID, GID",
 | |
| 			u:      "testuser",
 | |
| 			uid:    &runtime.Int64Value{Value: 1},
 | |
| 			gid:    &runtime.Int64Value{Value: 10},
 | |
| 			result: "testuser:10",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "Username, GID",
 | |
| 			u:      "testuser",
 | |
| 			gid:    &runtime.Int64Value{Value: 10},
 | |
| 			result: "testuser:10",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "UID only",
 | |
| 			uid:    &runtime.Int64Value{Value: 1},
 | |
| 			result: "1",
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "UID, GID",
 | |
| 			uid:    &runtime.Int64Value{Value: 1},
 | |
| 			gid:    &runtime.Int64Value{Value: 10},
 | |
| 			result: "1:10",
 | |
| 		},
 | |
| 		{
 | |
| 			name:          "GID only",
 | |
| 			gid:           &runtime.Int64Value{Value: 10},
 | |
| 			result:        "",
 | |
| 			expectedError: true,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range testcases {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			r, err := generateUserString(tc.u, tc.uid, tc.gid)
 | |
| 			if tc.expectedError {
 | |
| 				assert.Error(t, err)
 | |
| 			} else {
 | |
| 				assert.NoError(t, err)
 | |
| 			}
 | |
| 			assert.Equal(t, tc.result, r)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPrivilegedDevices(t *testing.T) {
 | |
| 	testPid := uint32(1234)
 | |
| 	c := newTestCRIService()
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
 | |
| 
 | |
| 	for desc, test := range map[string]struct {
 | |
| 		privileged                   bool
 | |
| 		privilegedWithoutHostDevices bool
 | |
| 		expectHostDevices            bool
 | |
| 	}{
 | |
| 		"expect no host devices when privileged is false": {
 | |
| 			privileged:                   false,
 | |
| 			privilegedWithoutHostDevices: false,
 | |
| 			expectHostDevices:            false,
 | |
| 		},
 | |
| 		"expect no host devices when privileged is false and privilegedWithoutHostDevices is true": {
 | |
| 			privileged:                   false,
 | |
| 			privilegedWithoutHostDevices: true,
 | |
| 			expectHostDevices:            false,
 | |
| 		},
 | |
| 		"expect host devices when privileged is true": {
 | |
| 			privileged:                   true,
 | |
| 			privilegedWithoutHostDevices: false,
 | |
| 			expectHostDevices:            true,
 | |
| 		},
 | |
| 		"expect no host devices when privileged is true and privilegedWithoutHostDevices is true": {
 | |
| 			privileged:                   true,
 | |
| 			privilegedWithoutHostDevices: true,
 | |
| 			expectHostDevices:            false,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Logf("TestCase %q", desc)
 | |
| 
 | |
| 		containerConfig.Linux.SecurityContext.Privileged = test.privileged
 | |
| 		sandboxConfig.Linux.SecurityContext.Privileged = test.privileged
 | |
| 
 | |
| 		ociRuntime := config.Runtime{
 | |
| 			PrivilegedWithoutHostDevices: test.privilegedWithoutHostDevices,
 | |
| 		}
 | |
| 		spec, err := c.containerSpec(t.Name(), testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 		assert.NoError(t, err)
 | |
| 
 | |
| 		hostDevicesRaw, err := devices.HostDevices()
 | |
| 		assert.NoError(t, err)
 | |
| 		var hostDevices []*libcontainerconfigs.Device
 | |
| 		for _, dev := range hostDevicesRaw {
 | |
| 			// https://github.com/containerd/cri/pull/1521#issuecomment-652807951
 | |
| 			if dev.DeviceRule.Major != 0 {
 | |
| 				hostDevices = append(hostDevices, dev)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if test.expectHostDevices {
 | |
| 			assert.Len(t, spec.Linux.Devices, len(hostDevices))
 | |
| 		} else {
 | |
| 			assert.Empty(t, spec.Linux.Devices)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestBaseOCISpec(t *testing.T) {
 | |
| 	c := newTestCRIService()
 | |
| 	baseLimit := int64(100)
 | |
| 	c.baseOCISpecs = map[string]*oci.Spec{
 | |
| 		"/etc/containerd/cri-base.json": {
 | |
| 			Process: &runtimespec.Process{
 | |
| 				User: runtimespec.User{AdditionalGids: []uint32{9999}},
 | |
| 				Capabilities: &runtimespec.LinuxCapabilities{
 | |
| 					Permitted: []string{"CAP_SETUID"},
 | |
| 				},
 | |
| 			},
 | |
| 			Linux: &runtimespec.Linux{
 | |
| 				Resources: &runtimespec.LinuxResources{
 | |
| 					Memory: &runtimespec.LinuxMemory{Limit: &baseLimit}, // Will be overwritten by `getCreateContainerTestData`
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	ociRuntime := config.Runtime{}
 | |
| 	ociRuntime.BaseRuntimeSpec = "/etc/containerd/cri-base.json"
 | |
| 
 | |
| 	testID := "test-id"
 | |
| 	testSandboxID := "sandbox-id"
 | |
| 	testContainerName := "container-name"
 | |
| 	testPid := uint32(1234)
 | |
| 	containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
 | |
| 
 | |
| 	spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
 | |
| 	assert.NoError(t, err)
 | |
| 
 | |
| 	specCheck(t, testID, testSandboxID, testPid, spec)
 | |
| 
 | |
| 	assert.Contains(t, spec.Process.User.AdditionalGids, uint32(9999))
 | |
| 	assert.Len(t, spec.Process.User.AdditionalGids, 3)
 | |
| 
 | |
| 	assert.Contains(t, spec.Process.Capabilities.Permitted, "CAP_SETUID")
 | |
| 	assert.Len(t, spec.Process.Capabilities.Permitted, 1)
 | |
| 
 | |
| 	assert.Equal(t, *spec.Linux.Resources.Memory.Limit, containerConfig.Linux.Resources.MemoryLimitInBytes)
 | |
| }
 | 
