diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index 00d7be003bf..fdcd4dd7743 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -56,8 +56,12 @@ type FakeDockerClient struct { } func NewFakeDockerClient() *FakeDockerClient { + return NewFakeDockerClientWithVersion("1.8.1", "1.20") +} + +func NewFakeDockerClientWithVersion(version, apiVersion string) *FakeDockerClient { return &FakeDockerClient{ - VersionInfo: docker.Env{"Version=1.8.1", "ApiVersion=1.20"}, + VersionInfo: docker.Env{fmt.Sprintf("Version=%s", version), fmt.Sprintf("ApiVersion=%s", apiVersion)}, Errors: make(map[string]error), RemovedImages: sets.String{}, ContainerMap: make(map[string]*docker.Container), diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 7bd47ce82bf..4b3f9ca50da 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -79,13 +79,23 @@ const ( // networking). Must match the value returned by docker inspect -f // '{{.HostConfig.NetworkMode}}'. namespaceModeHost = "host" + + // Remote API version for docker daemon version v1.10 + // https://docs.docker.com/engine/reference/api/docker_remote_api/ + dockerV110APIVersion = "1.22" ) -// DockerManager implements the Runtime interface. -var _ kubecontainer.Runtime = &DockerManager{} +var ( + // DockerManager implements the Runtime interface. + _ kubecontainer.Runtime = &DockerManager{} -// TODO: make this a TTL based pull (if image older than X policy, pull) -var podInfraContainerImagePullPolicy = api.PullIfNotPresent + // TODO: make this a TTL based pull (if image older than X policy, pull) + podInfraContainerImagePullPolicy = api.PullIfNotPresent + + // Default set of security options. Seccomp is disabled by default until + // github issue #20870 is resolved. + defaultSecurityOpt = []string{"seccomp:unconfined"} +) type DockerManager struct { client DockerInterface @@ -488,6 +498,11 @@ func (dm *DockerManager) runContainer( ContainerName: container.Name, } + securityOpts, err := dm.defaultSecurityOpt() + if err != nil { + return kubecontainer.ContainerID{}, err + } + // Pod information is recorded on the container as labels to preserve it in the event the pod is deleted // while the Kubelet is down and there is no information available to recover the pod. // TODO: keep these labels up to date if the pod changes @@ -551,9 +566,10 @@ func (dm *DockerManager) runContainer( PidMode: pidMode, ReadonlyRootfs: readOnlyRootFilesystem(container), // Memory and CPU are set here for newer versions of Docker (1.6+). - Memory: memoryLimit, - MemorySwap: -1, - CPUShares: cpuShares, + Memory: memoryLimit, + MemorySwap: -1, + CPUShares: cpuShares, + SecurityOpt: securityOpts, } if dm.cpuCFSQuota { @@ -934,6 +950,22 @@ func (dm *DockerManager) nativeExecSupportExists() (bool, error) { return false, err } +func (dm *DockerManager) defaultSecurityOpt() ([]string, error) { + version, err := dm.APIVersion() + if err != nil { + return nil, err + } + // seccomp is to be disabled on docker versions >= v1.10 + result, err := version.Compare(dockerV110APIVersion) + if err != nil { + return nil, err + } + if result >= 0 { + return defaultSecurityOpt, nil + } + return nil, nil +} + func (dm *DockerManager) getRunInContainerCommand(containerID kubecontainer.ContainerID, cmd []string) (*exec.Cmd, error) { args := append([]string{"exec"}, cmd...) command := exec.Command("/usr/sbin/nsinit", args...) diff --git a/pkg/kubelet/dockertools/manager_test.go b/pkg/kubelet/dockertools/manager_test.go index 4165b9060be..927978eb59d 100644 --- a/pkg/kubelet/dockertools/manager_test.go +++ b/pkg/kubelet/dockertools/manager_test.go @@ -82,8 +82,8 @@ func (f *fakeRuntimeHelper) GetClusterDNS(pod *api.Pod) ([]string, []string, err return nil, nil, fmt.Errorf("not implemented") } -func newTestDockerManagerWithHTTPClient(fakeHTTPClient *fakeHTTP) (*DockerManager, *FakeDockerClient) { - fakeDocker := NewFakeDockerClient() +func newTestDockerManagerWithHTTPClientWithVersion(fakeHTTPClient *fakeHTTP, version, apiVersion string) (*DockerManager, *FakeDockerClient) { + fakeDocker := NewFakeDockerClientWithVersion(version, apiVersion) fakeRecorder := &record.FakeRecorder{} containerRefManager := kubecontainer.NewRefManager() networkPlugin, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", network.NewFakeHost(nil)) @@ -104,6 +104,10 @@ func newTestDockerManagerWithHTTPClient(fakeHTTPClient *fakeHTTP) (*DockerManage return dockerManager, fakeDocker } +func newTestDockerManagerWithHTTPClient(fakeHTTPClient *fakeHTTP) (*DockerManager, *FakeDockerClient) { + return newTestDockerManagerWithHTTPClientWithVersion(fakeHTTPClient, "1.8.1", "1.20") +} + func newTestDockerManager() (*DockerManager, *FakeDockerClient) { return newTestDockerManagerWithHTTPClient(&fakeHTTP{}) } @@ -634,7 +638,7 @@ func TestSyncPodCreateNetAndContainer(t *testing.T) { if len(fakeDocker.Created) != 2 || !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() } @@ -670,13 +674,13 @@ func TestSyncPodCreatesNetAndContainerPullsImage(t *testing.T) { fakeDocker.Lock() if !reflect.DeepEqual(puller.ImagesPulled, []string{"pod_infra_image", "something"}) { - t.Errorf("Unexpected pulled containers: %v", puller.ImagesPulled) + t.Errorf("unexpected pulled containers: %v", puller.ImagesPulled) } if len(fakeDocker.Created) != 2 || !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() } @@ -713,7 +717,7 @@ func TestSyncPodWithPodInfraCreatesContainer(t *testing.T) { fakeDocker.Lock() if len(fakeDocker.Created) != 1 || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() } @@ -1151,7 +1155,7 @@ func TestGetRestartCount(t *testing.T) { runSyncPod(t, dm, fakeDocker, pod, nil, false) status, err := dm.GetPodStatus(pod.UID, pod.Name, pod.Namespace) if err != nil { - t.Fatalf("Unexpected error %v", err) + t.Fatalf("unexpected error %v", err) } cs := status.FindContainerStatusByName(containerName) if cs == nil { @@ -1166,7 +1170,7 @@ func TestGetRestartCount(t *testing.T) { killOneContainer := func(pod *api.Pod) { status, err := dm.GetPodStatus(pod.UID, pod.Name, pod.Namespace) if err != nil { - t.Fatalf("Unexpected error %v", err) + t.Fatalf("unexpected error %v", err) } cs := status.FindContainerStatusByName(containerName) if cs == nil { @@ -1234,11 +1238,11 @@ func TestGetTerminationMessagePath(t *testing.T) { containerList := fakeDocker.ContainerList if len(containerList) != 2 { // One for infra container, one for container "bar" - t.Fatalf("Unexpected container list length %d", len(containerList)) + t.Fatalf("unexpected container list length %d", len(containerList)) } inspectResult, err := dm.client.InspectContainer(containerList[0].ID) if err != nil { - t.Fatalf("Unexpected inspect error: %v", err) + t.Fatalf("unexpected inspect error: %v", err) } containerInfo := getContainerInfoFromLabel(inspectResult.Config.Labels) terminationMessagePath := containerInfo.TerminationMessagePath @@ -1290,11 +1294,11 @@ func TestSyncPodWithPodInfraCreatesContainerCallsHandler(t *testing.T) { fakeDocker.Lock() if len(fakeDocker.Created) != 1 || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() if fakeHTTPClient.url != "http://foo:8080/bar" { - t.Errorf("Unexpected handler: %q", fakeHTTPClient.url) + t.Errorf("unexpected handler: %q", fakeHTTPClient.url) } } @@ -1347,7 +1351,7 @@ func TestSyncPodEventHandlerFails(t *testing.T) { } dockerName, _, err := ParseDockerName(fakeDocker.Stopped[0]) if err != nil { - t.Errorf("Unexpected error: %v", err) + t.Errorf("unexpected error: %v", err) } if dockerName.ContainerName != "bar" { t.Errorf("Wrong stopped container, expected: bar, get: %q", dockerName.ContainerName) @@ -1417,7 +1421,7 @@ func TestSyncPodWithTerminationLog(t *testing.T) { if len(fakeDocker.Created) != 2 || !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) @@ -1426,10 +1430,10 @@ func TestSyncPodWithTerminationLog(t *testing.T) { } parts := strings.Split(newContainer.HostConfig.Binds[0], ":") if !matchString(t, testPodContainerDir+"/[a-f0-9]", parts[0]) { - t.Errorf("Unexpected host path: %s", parts[0]) + t.Errorf("unexpected host path: %s", parts[0]) } if parts[1] != "/dev/somepath" { - t.Errorf("Unexpected container path: %s", parts[1]) + t.Errorf("unexpected container path: %s", parts[1]) } } @@ -1464,7 +1468,7 @@ func TestSyncPodWithHostNetwork(t *testing.T) { if len(fakeDocker.Created) != 2 || !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } fakeDocker.Unlock() @@ -1687,7 +1691,7 @@ func TestSyncPodWithPullPolicy(t *testing.T) { assert.Equal(t, []string{"pod_infra_image", "pull_always_image", "pull_if_not_present_image"}, pulledImageSorted) if len(fakeDocker.Created) != 5 { - t.Errorf("Unexpected containers created %v", fakeDocker.Created) + t.Errorf("unexpected containers created %v", fakeDocker.Created) } } @@ -1781,3 +1785,81 @@ func verifySyncResults(t *testing.T, expectedResults []*kubecontainer.SyncResult } } } + +func TestSeccompIsDisabledWithDockerV110(t *testing.T) { + dm, fakeDocker := newTestDockerManagerWithHTTPClientWithVersion(&fakeHTTP{}, "1.10.1", "1.22") + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + assert.Contains(t, newContainer.HostConfig.SecurityOpt, "seccomp:unconfined", "Pods with Docker versions >= 1.10 must have seccomp disabled.") +} + +func TestSecurityOptsAreNilWithDockerV19(t *testing.T) { + dm, fakeDocker := newTestDockerManagerWithHTTPClientWithVersion(&fakeHTTP{}, "1.9.1", "1.21") + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + assert.NotContains(t, newContainer.HostConfig.SecurityOpt, "seccomp:unconfined", "Pods with Docker versions < 1.10 must not have seccomp disabled by default") +}