1
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
70
71
72
73
74
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
138
139
140
141
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 }
244
View as plain text