k8s.io/kubernetes@v1.29.3/test/e2e/windows/security_context.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package windows
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/onsi/ginkgo/v2"
    26  	"github.com/onsi/gomega"
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/fields"
    30  	"k8s.io/apimachinery/pkg/util/uuid"
    31  	clientset "k8s.io/client-go/kubernetes"
    32  	"k8s.io/kubernetes/pkg/kubelet/events"
    33  	"k8s.io/kubernetes/test/e2e/feature"
    34  	"k8s.io/kubernetes/test/e2e/framework"
    35  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    36  	e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    37  	testutils "k8s.io/kubernetes/test/utils"
    38  	imageutils "k8s.io/kubernetes/test/utils/image"
    39  	admissionapi "k8s.io/pod-security-admission/api"
    40  )
    41  
    42  const runAsUserNameContainerName = "run-as-username-container"
    43  
    44  var _ = sigDescribe(feature.Windows, "SecurityContext", skipUnlessWindows(func() {
    45  	f := framework.NewDefaultFramework("windows-run-as-username")
    46  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    47  
    48  	ginkgo.It("should be able create pods and run containers with a given username", func(ctx context.Context) {
    49  		ginkgo.By("Creating 2 pods: 1 with the default user, and one with a custom one.")
    50  		podDefault := runAsUserNamePod(nil)
    51  		e2eoutput.TestContainerOutput(ctx, f, "check default user", podDefault, 0, []string{"ContainerUser"})
    52  
    53  		podUserName := runAsUserNamePod(toPtr("ContainerAdministrator"))
    54  		e2eoutput.TestContainerOutput(ctx, f, "check set user", podUserName, 0, []string{"ContainerAdministrator"})
    55  	})
    56  
    57  	ginkgo.It("should not be able to create pods with unknown usernames at Pod level", func(ctx context.Context) {
    58  		ginkgo.By("Creating a pod with an invalid username")
    59  		podInvalid := e2epod.NewPodClient(f).Create(ctx, runAsUserNamePod(toPtr("FooLish")))
    60  
    61  		failedSandboxEventSelector := fields.Set{
    62  			"involvedObject.kind":      "Pod",
    63  			"involvedObject.name":      podInvalid.Name,
    64  			"involvedObject.namespace": podInvalid.Namespace,
    65  			"reason":                   events.FailedCreatePodSandBox,
    66  		}.AsSelector().String()
    67  		hcsschimError := "The user name or password is incorrect."
    68  
    69  		// Hostprocess updated the cri to pass RunAsUserName to sandbox: https://github.com/kubernetes/kubernetes/pull/99576/commits/51a02fdb80cb7ba042a66362eb76facd2fd82401
    70  		// Some runtimes might use that and set the username on the podsandbox. Containerd 1.6+ is known to do this.
    71  		// If there is an error when creating the pod sandbox then the pod stays in pending state by design
    72  		// See https://github.com/kubernetes/kubernetes/issues/104635
    73  		// Not all runtimes use the sandbox information.  This means the test needs to check if the pod
    74  		// sandbox failed or workload pod failed.
    75  		framework.Logf("Waiting for pod %s to enter the error state.", podInvalid.Name)
    76  		gomega.Eventually(ctx, func(ctx context.Context) bool {
    77  			failedSandbox, err := eventOccurred(ctx, f.ClientSet, podInvalid.Namespace, failedSandboxEventSelector, hcsschimError)
    78  			if err != nil {
    79  				framework.Logf("Error retrieving events for pod. Ignoring...")
    80  			}
    81  			if failedSandbox {
    82  				framework.Logf("Found Expected Event 'Failed to Create Pod Sandbox' with message containing: %s", hcsschimError)
    83  				return true
    84  			}
    85  
    86  			framework.Logf("No Sandbox error found. Looking for failure in workload pods")
    87  			pod, err := e2epod.NewPodClient(f).Get(ctx, podInvalid.Name, metav1.GetOptions{})
    88  			if err != nil {
    89  				framework.Logf("Error retrieving pod: %s", err)
    90  				return false
    91  			}
    92  
    93  			podTerminatedReason := testutils.TerminatedContainers(pod)[runAsUserNameContainerName]
    94  			podFailedToStart := podTerminatedReason == "ContainerCannotRun" || podTerminatedReason == "StartError"
    95  			if pod.Status.Phase == v1.PodFailed && podFailedToStart {
    96  				framework.Logf("Found terminated workload Pod that could not start")
    97  				return true
    98  			}
    99  
   100  			return false
   101  		}, framework.PodStartTimeout, 1*time.Second).Should(gomega.BeTrue())
   102  	})
   103  
   104  	ginkgo.It("should not be able to create pods with unknown usernames at Container level", func(ctx context.Context) {
   105  		ginkgo.By("Creating a pod with an invalid username at container level and pod running as ContainerUser")
   106  		p := runAsUserNamePod(toPtr("FooLish"))
   107  		p.Spec.SecurityContext.WindowsOptions.RunAsUserName = toPtr("ContainerUser")
   108  		podInvalid := e2epod.NewPodClient(f).Create(ctx, p)
   109  
   110  		framework.Logf("Waiting for pod %s to enter the error state.", podInvalid.Name)
   111  		framework.ExpectNoError(e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, podInvalid.Name, "", f.Namespace.Name))
   112  
   113  		podInvalid, _ = e2epod.NewPodClient(f).Get(ctx, podInvalid.Name, metav1.GetOptions{})
   114  		podTerminatedReason := testutils.TerminatedContainers(podInvalid)[runAsUserNameContainerName]
   115  		if podTerminatedReason != "ContainerCannotRun" && podTerminatedReason != "StartError" {
   116  			framework.Failf("The container terminated reason was supposed to be: 'ContainerCannotRun' or 'StartError', not: '%q'", podTerminatedReason)
   117  		}
   118  	})
   119  
   120  	ginkgo.It("should override SecurityContext username if set", func(ctx context.Context) {
   121  		ginkgo.By("Creating a pod with 2 containers with different username configurations.")
   122  
   123  		pod := runAsUserNamePod(toPtr("ContainerAdministrator"))
   124  		pod.Spec.Containers[0].SecurityContext.WindowsOptions.RunAsUserName = toPtr("ContainerUser")
   125  		pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
   126  			Name:    "run-as-username-new-container",
   127  			Image:   imageutils.GetE2EImage(imageutils.NonRoot),
   128  			Command: []string{"cmd", "/S", "/C", "echo %username%"},
   129  		})
   130  
   131  		e2eoutput.TestContainerOutput(ctx, f, "check overridden username", pod, 0, []string{"ContainerUser"})
   132  		e2eoutput.TestContainerOutput(ctx, f, "check pod SecurityContext username", pod, 1, []string{"ContainerAdministrator"})
   133  	})
   134  
   135  	ginkgo.It("should ignore Linux Specific SecurityContext if set", func(ctx context.Context) {
   136  		ginkgo.By("Creating a pod with SELinux options")
   137  		// It is sufficient to show that the pod comes up here. Since we're stripping the SELinux and other linux
   138  		// security contexts in apiserver and not updating the pod object in the apiserver, we cannot validate the
   139  		// pod object to not have those security contexts. However the pod coming to running state is a sufficient
   140  		// enough condition for us to validate since prior to https://github.com/kubernetes/kubernetes/pull/93475
   141  		// the pod would have failed to come up.
   142  		windowsPodWithSELinux := createTestPod(f, windowsBusyBoximage, windowsOS)
   143  		windowsPodWithSELinux.Spec.Containers[0].Args = []string{"test-webserver-with-selinux"}
   144  		windowsPodWithSELinux.Spec.SecurityContext = &v1.PodSecurityContext{}
   145  		containerUserName := "ContainerAdministrator"
   146  		windowsPodWithSELinux.Spec.SecurityContext.SELinuxOptions = &v1.SELinuxOptions{Level: "s0:c24,c9"}
   147  		windowsPodWithSELinux.Spec.Containers[0].SecurityContext = &v1.SecurityContext{
   148  			SELinuxOptions: &v1.SELinuxOptions{Level: "s0:c24,c9"},
   149  			WindowsOptions: &v1.WindowsSecurityContextOptions{RunAsUserName: &containerUserName}}
   150  		windowsPodWithSELinux.Spec.Tolerations = []v1.Toleration{{Key: "os", Value: "Windows"}}
   151  		windowsPodWithSELinux, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(ctx,
   152  			windowsPodWithSELinux, metav1.CreateOptions{})
   153  		framework.ExpectNoError(err)
   154  		framework.Logf("Created pod %v", windowsPodWithSELinux)
   155  		framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, windowsPodWithSELinux.Name,
   156  			f.Namespace.Name), "failed to wait for pod %s to be running", windowsPodWithSELinux.Name)
   157  	})
   158  
   159  	ginkgo.It("should not be able to create pods with containers running as ContainerAdministrator when runAsNonRoot is true", func(ctx context.Context) {
   160  		ginkgo.By("Creating a pod")
   161  
   162  		p := runAsUserNamePod(toPtr("ContainerAdministrator"))
   163  		p.Spec.SecurityContext.RunAsNonRoot = &trueVar
   164  
   165  		podInvalid, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(ctx, p, metav1.CreateOptions{})
   166  		framework.ExpectNoError(err, "Error creating pod")
   167  
   168  		ginkgo.By("Waiting for pod to finish")
   169  		event, err := e2epod.NewPodClient(f).WaitForErrorEventOrSuccess(ctx, podInvalid)
   170  		framework.ExpectNoError(err)
   171  		gomega.Expect(event).ToNot(gomega.BeNil(), "event should not be empty")
   172  		framework.Logf("Got event: %v", event)
   173  		expectedEventError := "container's runAsUserName (ContainerAdministrator) which will be regarded as root identity and will break non-root policy"
   174  		gomega.Expect(event.Message).Should(gomega.ContainSubstring(expectedEventError), "Event error should indicate non-root policy caused container to not start")
   175  	})
   176  
   177  	ginkgo.It("should not be able to create pods with containers running as CONTAINERADMINISTRATOR when runAsNonRoot is true", func(ctx context.Context) {
   178  		ginkgo.By("Creating a pod")
   179  
   180  		p := runAsUserNamePod(toPtr("CONTAINERADMINISTRATOR"))
   181  		p.Spec.SecurityContext.RunAsNonRoot = &trueVar
   182  
   183  		podInvalid, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(ctx, p, metav1.CreateOptions{})
   184  		framework.ExpectNoError(err, "Error creating pod")
   185  
   186  		ginkgo.By("Waiting for pod to finish")
   187  		event, err := e2epod.NewPodClient(f).WaitForErrorEventOrSuccess(ctx, podInvalid)
   188  		framework.ExpectNoError(err)
   189  		gomega.Expect(event).ToNot(gomega.BeNil(), "event should not be empty")
   190  		framework.Logf("Got event: %v", event)
   191  		expectedEventError := "container's runAsUserName (CONTAINERADMINISTRATOR) which will be regarded as root identity and will break non-root policy"
   192  		gomega.Expect(event.Message).Should(gomega.ContainSubstring(expectedEventError), "Event error should indicate non-root policy caused container to not start")
   193  	})
   194  }))
   195  
   196  func runAsUserNamePod(username *string) *v1.Pod {
   197  	podName := "run-as-username-" + string(uuid.NewUUID())
   198  	return &v1.Pod{
   199  		ObjectMeta: metav1.ObjectMeta{
   200  			Name: podName,
   201  		},
   202  		Spec: v1.PodSpec{
   203  			NodeSelector: map[string]string{"kubernetes.io/os": "windows"},
   204  			Containers: []v1.Container{
   205  				{
   206  					Name:    runAsUserNameContainerName,
   207  					Image:   imageutils.GetE2EImage(imageutils.NonRoot),
   208  					Command: []string{"cmd", "/S", "/C", "echo %username%"},
   209  					SecurityContext: &v1.SecurityContext{
   210  						WindowsOptions: &v1.WindowsSecurityContextOptions{
   211  							RunAsUserName: username,
   212  						},
   213  					},
   214  				},
   215  			},
   216  			SecurityContext: &v1.PodSecurityContext{
   217  				WindowsOptions: &v1.WindowsSecurityContextOptions{
   218  					RunAsUserName: username,
   219  				},
   220  			},
   221  			RestartPolicy: v1.RestartPolicyNever,
   222  		},
   223  	}
   224  }
   225  
   226  func toPtr(s string) *string {
   227  	return &s
   228  }
   229  
   230  func eventOccurred(ctx context.Context, c clientset.Interface, namespace, eventSelector, msg string) (bool, error) {
   231  	options := metav1.ListOptions{FieldSelector: eventSelector}
   232  
   233  	events, err := c.CoreV1().Events(namespace).List(ctx, options)
   234  	if err != nil {
   235  		return false, fmt.Errorf("got error while getting events: %w", err)
   236  	}
   237  	for _, event := range events.Items {
   238  		if strings.Contains(event.Message, msg) {
   239  			return true, nil
   240  		}
   241  	}
   242  	return false, nil
   243  }