From 2c9f66dcae1afdb256494f287dcac0e7abcc3b72 Mon Sep 17 00:00:00 2001 From: Mike Brown Date: Tue, 12 Sep 2017 09:26:27 -0500 Subject: [PATCH] adds tests for seccomp Signed-off-by: Mike Brown adds support for seccomp testing Signed-off-by: Mike Brown --- pkg/validate/security_context.go | 248 ++++++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 1 deletion(-) diff --git a/pkg/validate/security_context.go b/pkg/validate/security_context.go index aa35c06573..a90875d8d3 100644 --- a/pkg/validate/security_context.go +++ b/pkg/validate/security_context.go @@ -18,6 +18,7 @@ package validate import ( "fmt" + "io/ioutil" "net" "os" "os/exec" @@ -34,7 +35,19 @@ import ( ) const ( - nginxContainerImage string = "nginx" + nginxContainerImage string = "nginx" + localhost string = "localhost/" + seccompBlockHostNameProfile = ` +{ + "defaultAction": "SCMP_ACT_ALLOW", + "syscalls": [ + { + "name": "sethostname", + "action": "SCMP_ACT_ERRNO" + } + ] +} +` ) var _ = framework.KubeDescribe("Security Context", func() { @@ -402,6 +415,146 @@ var _ = framework.KubeDescribe("Security Context", func() { }) }) + Context("runtime should support seccomp security context", func() { + var podID string + var podConfig *runtimeapi.PodSandboxConfig + sysCaps := []string{"ALL"} + + AfterEach(func() { + By("stop PodSandbox") + rc.StopPodSandbox(podID) + By("delete PodSandbox") + rc.RemovePodSandbox(podID) + }) + + It("runtime should support an seccomp profile that blocks setting hostname with no sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with seccompBlockHostNameProfile and test") + containerID := createSeccompContainerWithProfile(rc, ic, podID, podConfig, + "container-with-block-hostname-seccomp-profile-test-", + seccompBlockHostNameProfile, nil, false, localhost, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + + It("runtime should not support a custom seccomp profile without using localhost/ as a prefix", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with seccompBlockHostNameProfile and test") + _ = createSeccompContainerWithProfile(rc, ic, podID, podConfig, + "container-with-block-hostname-seccomp-profile-test-", + seccompBlockHostNameProfile, nil, false, "", false) + }) + + It("runtime should ignore a seccomp profile that blocks setting hostname when privileged", func() { + By("create privileged pod") + podID, podConfig = createPrivilegedPodSandbox(rc, true) + By("create privileged container with seccompBlockHostNameProfile and test") + containerID := createSeccompContainerWithProfile(rc, ic, podID, podConfig, + "container-with-block-hostname-seccomp-profile-test-", + seccompBlockHostNameProfile, nil, true, localhost, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, true) + }) + + It("runtime should block setting hostname with no seccomp profile specified", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container without seccomp profile and test") + containerID := framework.CreateDefaultContainer(rc, ic, podID, podConfig, + "container-with-no-seccomp-profile-test-") + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + + It("runtime should support setting hostname with docker/default seccomp profile and sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with docker/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-dockerdefault-seccomp-profile-test-", "docker/default", sysCaps, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, true) + }) + + It("runtime should support setting hostname with runtime/default seccomp profile and sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with runtime/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-runtimedefault-seccomp-profile-test-", "runtime/default", sysCaps, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, true) + }) + + It("runtime should block sethostname with docker/default seccomp profile and no sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with docker/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-dockerdefault-seccomp-profile-test-", "docker/default", nil, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + + It("runtime should block sethostname with runtime/default seccomp profile and no sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with runtime/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-runtimedefault-seccomp-profile-test-", "runtime/default", nil, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + + It("runtime should block sethostname with unconfined seccomp profile and no sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with runtime/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-runtimedefault-seccomp-profile-test-", "unconfined", nil, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + + It("runtime should block sethostname with nil seccomp profile and no sysCaps", func() { + By("create pod") + podID, podConfig = framework.CreatePodSandboxForContainer(rc) + By("create container with runtime/default seccomp profile and test") + containerID := createSeccompContainer(rc, ic, podID, podConfig, + "container-with-runtimedefault-seccomp-profile-test-", "", nil, false, true) + startContainer(rc, containerID) + Eventually(func() runtimeapi.ContainerState { + return getContainerStatus(rc, containerID).State + }, time.Minute, time.Second*4).Should(Equal(runtimeapi.ContainerState_CONTAINER_RUNNING)) + checkSetHostname(rc, containerID, false) + }) + }) }) // createRunAsUserContainer creates the container with specified RunAsUser in ContainerConfig. @@ -637,3 +790,96 @@ func createAndCheckHostNetwork(rc internalapi.RuntimeService, ic internalapi.Ima return podID } + +// createSeccompContainerWithProfile creates container with specified seccompcontainer in ContainerConfig. +func createSeccompContainerWithProfile(rc internalapi.RuntimeService, + ic internalapi.ImageManagerService, + podID string, + podConfig *runtimeapi.PodSandboxConfig, + prefix, profileContent string, + caps []string, + privileged bool, + localhostprefix string, + expectContainerCreateToPass bool) string { + By("create seccomp profile for container") + profile := "cri-tools-seccomp-profile.json" + f, err := ioutil.TempFile("/tmp", profile) + framework.ExpectNoError(err, "failed to open temp file: %v", err) + defer f.Close() + + _, err = f.WriteString(profileContent) + framework.ExpectNoError(err, "failed to write profiles to file: %v", err) + + return createSeccompContainer(rc, ic, podID, podConfig, prefix, localhostprefix+f.Name(), caps, privileged, expectContainerCreateToPass) +} + +// createSeccompContainer creates container with the specified seccomp profile. +func createSeccompContainer(rc internalapi.RuntimeService, + ic internalapi.ImageManagerService, + podID string, + podConfig *runtimeapi.PodSandboxConfig, + prefix string, + profile string, + caps []string, + privileged bool, + expectContainerCreateToPass bool) string { + By("create " + profile + " Seccomp container") + containerName := prefix + framework.NewUUID() + containerConfig := &runtimeapi.ContainerConfig{ + Metadata: framework.BuildContainerMetadata(containerName, framework.DefaultAttempt), + Image: &runtimeapi.ImageSpec{Image: framework.DefaultContainerImage}, + Command: []string{"sleep", "60"}, + Linux: &runtimeapi.LinuxContainerConfig{ + SecurityContext: &runtimeapi.LinuxContainerSecurityContext{ + Privileged: privileged, + Capabilities: &runtimeapi.Capability{ + AddCapabilities: caps, + }, + SeccompProfilePath: profile, + }, + }, + } + if expectContainerCreateToPass { + return framework.CreateContainer(rc, ic, containerConfig, podID, podConfig) + } else { + return failCreatingContainer(rc, ic, containerConfig, podID, podConfig) + } +} + +// failCreatingContainer creates a container with the prefix of containerName +// and expectation of failure in the create step. +func failCreatingContainer(rc internalapi.RuntimeService, + ic internalapi.ImageManagerService, + config *runtimeapi.ContainerConfig, + podID string, + podConfig *runtimeapi.PodSandboxConfig) string { + // Pull the image if it does not exist. (don't fail for inability to pull image) + imageName := config.Image.Image + if !strings.Contains(imageName, ":") { + imageName = imageName + ":latest" + } + status := framework.ImageStatus(ic, imageName) + if status == nil { + framework.PullPublicImage(ic, imageName) + } + By("Create container.") + containerID, err := rc.CreateContainer(podID, config, podConfig) + msg := fmt.Sprintf("create should fail with err %v", err) + Expect(err).To(HaveOccurred(), msg) + return containerID +} + +// checkSetHostname checks if the hostname can be set in the container. +func checkSetHostname(rc internalapi.RuntimeService, containerID string, setable bool) { + By("set hostname in container to determine whether sethostname is blocked") + + cmd := []string{"hostname", "ANewHostName"} + stdout, stderr, err := rc.ExecSync(containerID, cmd, time.Duration(defaultExecSyncTimeout)*time.Second) + msg := fmt.Sprintf("cmd %v, stdout %q, stderr %q", cmd, stdout, stderr) + + if setable { + Expect(err).NotTo(HaveOccurred(), msg) + } else { + Expect(err).To(HaveOccurred(), msg) + } +}