123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /*
- Copyright 2017 The Kubernetes 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 common
- import (
- "fmt"
- "strings"
- "time"
- v1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/uuid"
- "k8s.io/kubernetes/pkg/kubelet/events"
- "k8s.io/kubernetes/test/e2e/framework"
- e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
- e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
- imageutils "k8s.io/kubernetes/test/utils/image"
- "k8s.io/utils/pointer"
- "github.com/onsi/ginkgo"
- "github.com/onsi/gomega"
- )
- var _ = framework.KubeDescribe("Security Context", func() {
- f := framework.NewDefaultFramework("security-context-test")
- var podClient *framework.PodClient
- ginkgo.BeforeEach(func() {
- podClient = f.PodClient()
- })
- ginkgo.Context("When creating a container with runAsUser", func() {
- makeUserPod := func(podName, image string, command []string, userid int64) *v1.Pod {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- },
- Spec: v1.PodSpec{
- RestartPolicy: v1.RestartPolicyNever,
- Containers: []v1.Container{
- {
- Image: image,
- Name: podName,
- Command: command,
- SecurityContext: &v1.SecurityContext{
- RunAsUser: &userid,
- },
- },
- },
- },
- }
- }
- createAndWaitUserPod := func(userid int64) {
- podName := fmt.Sprintf("busybox-user-%d-%s", userid, uuid.NewUUID())
- podClient.Create(makeUserPod(podName,
- framework.BusyBoxImage,
- []string{"sh", "-c", fmt.Sprintf("test $(id -u) -eq %d", userid)},
- userid,
- ))
- podClient.WaitForSuccess(podName, framework.PodStartTimeout)
- }
- /*
- Release : v1.15
- Testname: Security Context, runAsUser=65534
- Description: Container is created with runAsUser option by passing uid 65534 to run as unpriviledged user. Pod MUST be in Succeeded phase.
- [LinuxOnly]: This test is marked as LinuxOnly since Windows does not support running as UID / GID.
- */
- framework.ConformanceIt("should run the container with uid 65534 [LinuxOnly] [NodeConformance]", func() {
- createAndWaitUserPod(65534)
- })
- /*
- Release : v1.15
- Testname: Security Context, runAsUser=0
- Description: Container is created with runAsUser option by passing uid 0 to run as root priviledged user. Pod MUST be in Succeeded phase.
- This e2e can not be promoted to Conformance because a Conformant platform may not allow to run containers with 'uid 0' or running privileged operations.
- [LinuxOnly]: This test is marked as LinuxOnly since Windows does not support running as UID / GID.
- */
- ginkgo.It("should run the container with uid 0 [LinuxOnly] [NodeConformance]", func() {
- createAndWaitUserPod(0)
- })
- })
- ginkgo.Context("When creating a container with runAsNonRoot", func() {
- rootImage := imageutils.GetE2EImage(imageutils.BusyBox)
- nonRootImage := imageutils.GetE2EImage(imageutils.NonRoot)
- makeNonRootPod := func(podName, image string, userid *int64) *v1.Pod {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- },
- Spec: v1.PodSpec{
- RestartPolicy: v1.RestartPolicyNever,
- Containers: []v1.Container{
- {
- Image: image,
- Name: podName,
- Command: []string{"id", "-u"}, // Print UID and exit
- SecurityContext: &v1.SecurityContext{
- RunAsNonRoot: pointer.BoolPtr(true),
- RunAsUser: userid,
- },
- },
- },
- },
- }
- }
- ginkgo.It("should run with an explicit non-root user ID [LinuxOnly]", func() {
- // creates a pod with RunAsUser, which is not supported on Windows.
- e2eskipper.SkipIfNodeOSDistroIs("windows")
- name := "explicit-nonroot-uid"
- pod := makeNonRootPod(name, rootImage, pointer.Int64Ptr(nonRootTestUserID))
- podClient.Create(pod)
- podClient.WaitForSuccess(name, framework.PodStartTimeout)
- framework.ExpectNoError(podClient.MatchContainerOutput(name, name, "1000"))
- })
- ginkgo.It("should not run with an explicit root user ID [LinuxOnly]", func() {
- // creates a pod with RunAsUser, which is not supported on Windows.
- e2eskipper.SkipIfNodeOSDistroIs("windows")
- name := "explicit-root-uid"
- pod := makeNonRootPod(name, nonRootImage, pointer.Int64Ptr(0))
- pod = podClient.Create(pod)
- ev, err := podClient.WaitForErrorEventOrSuccess(pod)
- framework.ExpectNoError(err)
- gomega.Expect(ev).NotTo(gomega.BeNil())
- framework.ExpectEqual(ev.Reason, events.FailedToCreateContainer)
- })
- ginkgo.It("should run with an image specified user ID", func() {
- name := "implicit-nonroot-uid"
- pod := makeNonRootPod(name, nonRootImage, nil)
- podClient.Create(pod)
- podClient.WaitForSuccess(name, framework.PodStartTimeout)
- framework.ExpectNoError(podClient.MatchContainerOutput(name, name, "1234"))
- })
- ginkgo.It("should not run without a specified user ID", func() {
- name := "implicit-root-uid"
- pod := makeNonRootPod(name, rootImage, nil)
- pod = podClient.Create(pod)
- ev, err := podClient.WaitForErrorEventOrSuccess(pod)
- framework.ExpectNoError(err)
- gomega.Expect(ev).NotTo(gomega.BeNil())
- framework.ExpectEqual(ev.Reason, events.FailedToCreateContainer)
- })
- })
- ginkgo.Context("When creating a pod with readOnlyRootFilesystem", func() {
- makeUserPod := func(podName, image string, command []string, readOnlyRootFilesystem bool) *v1.Pod {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- },
- Spec: v1.PodSpec{
- RestartPolicy: v1.RestartPolicyNever,
- Containers: []v1.Container{
- {
- Image: image,
- Name: podName,
- Command: command,
- SecurityContext: &v1.SecurityContext{
- ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
- },
- },
- },
- },
- }
- }
- createAndWaitUserPod := func(readOnlyRootFilesystem bool) string {
- podName := fmt.Sprintf("busybox-readonly-%v-%s", readOnlyRootFilesystem, uuid.NewUUID())
- podClient.Create(makeUserPod(podName,
- framework.BusyBoxImage,
- []string{"sh", "-c", "touch checkfile"},
- readOnlyRootFilesystem,
- ))
- if readOnlyRootFilesystem {
- waitForFailure(f, podName, framework.PodStartTimeout)
- } else {
- podClient.WaitForSuccess(podName, framework.PodStartTimeout)
- }
- return podName
- }
- /*
- Release : v1.15
- Testname: Security Context, readOnlyRootFilesystem=true.
- Description: Container is configured to run with readOnlyRootFilesystem to true which will force containers to run with a read only root file system.
- Write operation MUST NOT be allowed and Pod MUST be in Failed state.
- At this moment we are not considering this test for Conformance due to use of SecurityContext.
- [LinuxOnly]: This test is marked as LinuxOnly since Windows does not support creating containers with read-only access.
- */
- ginkgo.It("should run the container with readonly rootfs when readOnlyRootFilesystem=true [LinuxOnly] [NodeConformance]", func() {
- createAndWaitUserPod(true)
- })
- /*
- Release : v1.15
- Testname: Security Context, readOnlyRootFilesystem=false.
- Description: Container is configured to run with readOnlyRootFilesystem to false.
- Write operation MUST be allowed and Pod MUST be in Succeeded state.
- */
- framework.ConformanceIt("should run the container with writable rootfs when readOnlyRootFilesystem=false [NodeConformance]", func() {
- createAndWaitUserPod(false)
- })
- })
- ginkgo.Context("When creating a pod with privileged", func() {
- makeUserPod := func(podName, image string, command []string, privileged bool) *v1.Pod {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- },
- Spec: v1.PodSpec{
- RestartPolicy: v1.RestartPolicyNever,
- Containers: []v1.Container{
- {
- Image: image,
- Name: podName,
- Command: command,
- SecurityContext: &v1.SecurityContext{
- Privileged: &privileged,
- },
- },
- },
- },
- }
- }
- createAndWaitUserPod := func(privileged bool) string {
- podName := fmt.Sprintf("busybox-privileged-%v-%s", privileged, uuid.NewUUID())
- podClient.Create(makeUserPod(podName,
- framework.BusyBoxImage,
- []string{"sh", "-c", "ip link add dummy0 type dummy || true"},
- privileged,
- ))
- podClient.WaitForSuccess(podName, framework.PodStartTimeout)
- return podName
- }
- /*
- Release : v1.15
- Testname: Security Context, privileged=false.
- Description: Create a container to run in unprivileged mode by setting pod's SecurityContext Privileged option as false. Pod MUST be in Succeeded phase.
- [LinuxOnly]: This test is marked as LinuxOnly since it runs a Linux-specific command.
- */
- framework.ConformanceIt("should run the container as unprivileged when false [LinuxOnly] [NodeConformance]", func() {
- podName := createAndWaitUserPod(false)
- logs, err := e2epod.GetPodLogs(f.ClientSet, f.Namespace.Name, podName, podName)
- if err != nil {
- framework.Failf("GetPodLogs for pod %q failed: %v", podName, err)
- }
- framework.Logf("Got logs for pod %q: %q", podName, logs)
- if !strings.Contains(logs, "Operation not permitted") {
- framework.Failf("unprivileged container shouldn't be able to create dummy device")
- }
- })
- ginkgo.It("should run the container as privileged when true [LinuxOnly] [NodeFeature:HostAccess]", func() {
- podName := createAndWaitUserPod(true)
- logs, err := e2epod.GetPodLogs(f.ClientSet, f.Namespace.Name, podName, podName)
- if err != nil {
- framework.Failf("GetPodLogs for pod %q failed: %v", podName, err)
- }
- framework.Logf("Got logs for pod %q: %q", podName, logs)
- if strings.Contains(logs, "Operation not permitted") {
- framework.Failf("privileged container should be able to create dummy device")
- }
- })
- })
- ginkgo.Context("when creating containers with AllowPrivilegeEscalation", func() {
- makeAllowPrivilegeEscalationPod := func(podName string, allowPrivilegeEscalation *bool, uid int64) *v1.Pod {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- },
- Spec: v1.PodSpec{
- RestartPolicy: v1.RestartPolicyNever,
- Containers: []v1.Container{
- {
- Image: imageutils.GetE2EImage(imageutils.Nonewprivs),
- Name: podName,
- SecurityContext: &v1.SecurityContext{
- AllowPrivilegeEscalation: allowPrivilegeEscalation,
- RunAsUser: &uid,
- },
- },
- },
- },
- }
- }
- createAndMatchOutput := func(podName, output string, allowPrivilegeEscalation *bool, uid int64) error {
- podClient.Create(makeAllowPrivilegeEscalationPod(podName,
- allowPrivilegeEscalation,
- uid,
- ))
- podClient.WaitForSuccess(podName, framework.PodStartTimeout)
- return podClient.MatchContainerOutput(podName, podName, output)
- }
- /*
- Release : v1.15
- Testname: Security Context, allowPrivilegeEscalation unset, uid != 0.
- Description: Configuring the allowPrivilegeEscalation unset, allows the privilege escalation operation.
- A container is configured with allowPrivilegeEscalation not specified (nil) and a given uid which is not 0.
- When the container is run, container's output MUST match with expected output verifying container ran with uid=0.
- This e2e Can not be promoted to Conformance as it is Container Runtime dependent and not all conformant platforms will require this behavior.
- [LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID, or privilege escalation.
- */
- ginkgo.It("should allow privilege escalation when not explicitly set and uid != 0 [LinuxOnly] [NodeConformance]", func() {
- podName := "alpine-nnp-nil-" + string(uuid.NewUUID())
- if err := createAndMatchOutput(podName, "Effective uid: 0", nil, nonRootTestUserID); err != nil {
- framework.Failf("Match output for pod %q failed: %v", podName, err)
- }
- })
- /*
- Release : v1.15
- Testname: Security Context, allowPrivilegeEscalation=false.
- Description: Configuring the allowPrivilegeEscalation to false, does not allow the privilege escalation operation.
- A container is configured with allowPrivilegeEscalation=false and a given uid (1000) which is not 0.
- When the container is run, container's output MUST match with expected output verifying container ran with given uid i.e. uid=1000.
- [LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID, or privilege escalation.
- */
- framework.ConformanceIt("should not allow privilege escalation when false [LinuxOnly] [NodeConformance]", func() {
- podName := "alpine-nnp-false-" + string(uuid.NewUUID())
- apeFalse := false
- if err := createAndMatchOutput(podName, fmt.Sprintf("Effective uid: %d", nonRootTestUserID), &apeFalse, nonRootTestUserID); err != nil {
- framework.Failf("Match output for pod %q failed: %v", podName, err)
- }
- })
- /*
- Release : v1.15
- Testname: Security Context, allowPrivilegeEscalation=true.
- Description: Configuring the allowPrivilegeEscalation to true, allows the privilege escalation operation.
- A container is configured with allowPrivilegeEscalation=true and a given uid (1000) which is not 0.
- When the container is run, container's output MUST match with expected output verifying container ran with uid=0 (making use of the privilege escalation).
- This e2e Can not be promoted to Conformance as it is Container Runtime dependent and runtime may not allow to run.
- [LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID.
- */
- ginkgo.It("should allow privilege escalation when true [LinuxOnly] [NodeConformance]", func() {
- podName := "alpine-nnp-true-" + string(uuid.NewUUID())
- apeTrue := true
- if err := createAndMatchOutput(podName, "Effective uid: 0", &apeTrue, nonRootTestUserID); err != nil {
- framework.Failf("Match output for pod %q failed: %v", podName, err)
- }
- })
- })
- })
- // waitForFailure waits for pod to fail.
- func waitForFailure(f *framework.Framework, name string, timeout time.Duration) {
- gomega.Expect(e2epod.WaitForPodCondition(f.ClientSet, f.Namespace.Name, name, fmt.Sprintf("%s or %s", v1.PodSucceeded, v1.PodFailed), timeout,
- func(pod *v1.Pod) (bool, error) {
- switch pod.Status.Phase {
- case v1.PodFailed:
- return true, nil
- case v1.PodSucceeded:
- return true, fmt.Errorf("pod %q successed with reason: %q, message: %q", name, pod.Status.Reason, pod.Status.Message)
- default:
- return false, nil
- }
- },
- )).To(gomega.Succeed(), "wait for pod %q to fail", name)
- }
|