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 }