1
16
17
22
23 package node
24
25 import (
26 "context"
27 "fmt"
28
29 v1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/util/uuid"
32 "k8s.io/kubernetes/test/e2e/framework"
33 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
34 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
35 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
36 imageutils "k8s.io/kubernetes/test/utils/image"
37 admissionapi "k8s.io/pod-security-admission/api"
38
39 "github.com/onsi/ginkgo/v2"
40 "github.com/onsi/gomega"
41 )
42
43
44 const SeccompProcStatusField = "Seccomp:"
45
46
47 const ProcSelfStatusPath = "/proc/self/status"
48
49 func scTestPod(hostIPC bool, hostPID bool) *v1.Pod {
50 podName := "security-context-" + string(uuid.NewUUID())
51 pod := &v1.Pod{
52 ObjectMeta: metav1.ObjectMeta{
53 Name: podName,
54 Labels: map[string]string{"name": podName},
55 Annotations: map[string]string{},
56 },
57 Spec: v1.PodSpec{
58 HostIPC: hostIPC,
59 HostPID: hostPID,
60 SecurityContext: &v1.PodSecurityContext{},
61 Containers: []v1.Container{
62 {
63 Name: "test-container",
64 Image: imageutils.GetE2EImage(imageutils.BusyBox),
65 },
66 },
67 RestartPolicy: v1.RestartPolicyNever,
68 },
69 }
70
71 return pod
72 }
73
74 var _ = SIGDescribe("Security Context", func() {
75 f := framework.NewDefaultFramework("security-context")
76 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
77
78 ginkgo.It("should support pod.Spec.SecurityContext.SupplementalGroups [LinuxOnly]", func(ctx context.Context) {
79 pod := scTestPod(false, false)
80 pod.Spec.Containers[0].Command = []string{"id", "-G"}
81 pod.Spec.SecurityContext.SupplementalGroups = []int64{1234, 5678}
82 groups := []string{"1234", "5678"}
83 e2eoutput.TestContainerOutput(ctx, f, "pod.Spec.SecurityContext.SupplementalGroups", pod, 0, groups)
84 })
85
86 ginkgo.When("if the container's primary UID belongs to some groups in the image [LinuxOnly]", func() {
87 ginkgo.It("should add pod.Spec.SecurityContext.SupplementalGroups to them [LinuxOnly] in resultant supplementary groups for the container processes", func(ctx context.Context) {
88 uidInImage := int64(1000)
89 gidDefinedInImage := int64(50000)
90 supplementalGroup := int64(60000)
91 agnhost := imageutils.GetConfig(imageutils.Agnhost)
92 pod := scTestPod(false, false)
93 pod.Spec.Containers[0].Image = agnhost.GetE2EImage()
94 pod.Spec.Containers[0].Command = []string{"id", "-G"}
95 pod.Spec.SecurityContext.SupplementalGroups = []int64{int64(supplementalGroup)}
96 pod.Spec.SecurityContext.RunAsUser = &uidInImage
97
98
99
100
101
102
103
104
105
106
107 e2eoutput.TestContainerOutput(
108 ctx,
109 f,
110 "pod.Spec.SecurityContext.SupplementalGroups with pre-defined-group in the image",
111 pod, 0,
112 []string{fmt.Sprintf("%d %d %d", uidInImage, gidDefinedInImage, supplementalGroup)},
113 )
114 })
115 })
116
117 ginkgo.It("should support pod.Spec.SecurityContext.RunAsUser [LinuxOnly]", func(ctx context.Context) {
118 pod := scTestPod(false, false)
119 userID := int64(1001)
120 pod.Spec.SecurityContext.RunAsUser = &userID
121 pod.Spec.Containers[0].Command = []string{"sh", "-c", "id"}
122
123 e2eoutput.TestContainerOutput(ctx, f, "pod.Spec.SecurityContext.RunAsUser", pod, 0, []string{
124 fmt.Sprintf("uid=%v", userID),
125 fmt.Sprintf("gid=%v", 0),
126 })
127 })
128
129
135 framework.ConformanceIt("should support pod.Spec.SecurityContext.RunAsUser And pod.Spec.SecurityContext.RunAsGroup [LinuxOnly]", func(ctx context.Context) {
136 pod := scTestPod(false, false)
137 userID := int64(1001)
138 groupID := int64(2002)
139 pod.Spec.SecurityContext.RunAsUser = &userID
140 pod.Spec.SecurityContext.RunAsGroup = &groupID
141 pod.Spec.Containers[0].Command = []string{"sh", "-c", "id"}
142
143 e2eoutput.TestContainerOutput(ctx, f, "pod.Spec.SecurityContext.RunAsUser", pod, 0, []string{
144 fmt.Sprintf("uid=%v", userID),
145 fmt.Sprintf("gid=%v", groupID),
146 })
147 })
148
149 ginkgo.It("should support container.SecurityContext.RunAsUser [LinuxOnly]", func(ctx context.Context) {
150 pod := scTestPod(false, false)
151 userID := int64(1001)
152 overrideUserID := int64(1002)
153 pod.Spec.SecurityContext.RunAsUser = &userID
154 pod.Spec.Containers[0].SecurityContext = new(v1.SecurityContext)
155 pod.Spec.Containers[0].SecurityContext.RunAsUser = &overrideUserID
156 pod.Spec.Containers[0].Command = []string{"sh", "-c", "id"}
157
158 e2eoutput.TestContainerOutput(ctx, f, "pod.Spec.SecurityContext.RunAsUser", pod, 0, []string{
159 fmt.Sprintf("uid=%v", overrideUserID),
160 fmt.Sprintf("gid=%v", 0),
161 })
162 })
163
164
170 framework.ConformanceIt("should support container.SecurityContext.RunAsUser And container.SecurityContext.RunAsGroup [LinuxOnly]", func(ctx context.Context) {
171 pod := scTestPod(false, false)
172 userID := int64(1001)
173 groupID := int64(2001)
174 overrideUserID := int64(1002)
175 overrideGroupID := int64(2002)
176 pod.Spec.SecurityContext.RunAsUser = &userID
177 pod.Spec.SecurityContext.RunAsGroup = &groupID
178 pod.Spec.Containers[0].SecurityContext = new(v1.SecurityContext)
179 pod.Spec.Containers[0].SecurityContext.RunAsUser = &overrideUserID
180 pod.Spec.Containers[0].SecurityContext.RunAsGroup = &overrideGroupID
181 pod.Spec.Containers[0].Command = []string{"sh", "-c", "id"}
182
183 e2eoutput.TestContainerOutput(ctx, f, "pod.Spec.SecurityContext.RunAsUser", pod, 0, []string{
184 fmt.Sprintf("uid=%v", overrideUserID),
185 fmt.Sprintf("gid=%v", overrideGroupID),
186 })
187 })
188
189 f.It("should support volume SELinux relabeling", f.WithFlaky(), f.WithLabel("LinuxOnly"), func(ctx context.Context) {
190 testPodSELinuxLabeling(ctx, f, false, false)
191 })
192
193 f.It("should support volume SELinux relabeling when using hostIPC", f.WithFlaky(), f.WithLabel("LinuxOnly"), func(ctx context.Context) {
194 testPodSELinuxLabeling(ctx, f, true, false)
195 })
196
197 f.It("should support volume SELinux relabeling when using hostPID", f.WithFlaky(), f.WithLabel("LinuxOnly"), func(ctx context.Context) {
198 testPodSELinuxLabeling(ctx, f, false, true)
199 })
200
201 ginkgo.It("should support seccomp unconfined on the container [LinuxOnly]", func(ctx context.Context) {
202 pod := scTestPod(false, false)
203 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}}
204 pod.Spec.SecurityContext = &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}}
205 pod.Spec.Containers[0].Command = []string{"grep", SeccompProcStatusField, ProcSelfStatusPath}
206 e2eoutput.TestContainerOutput(ctx, f, "seccomp unconfined container", pod, 0, []string{"0"})
207 })
208
209 ginkgo.It("should support seccomp unconfined on the pod [LinuxOnly]", func(ctx context.Context) {
210 pod := scTestPod(false, false)
211 pod.Spec.SecurityContext = &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}}
212 pod.Spec.Containers[0].Command = []string{"grep", SeccompProcStatusField, ProcSelfStatusPath}
213 e2eoutput.TestContainerOutput(ctx, f, "seccomp unconfined pod", pod, 0, []string{"0"})
214 })
215
216 ginkgo.It("should support seccomp runtime/default [LinuxOnly]", func(ctx context.Context) {
217 pod := scTestPod(false, false)
218 pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}}
219 pod.Spec.Containers[0].Command = []string{"grep", SeccompProcStatusField, ProcSelfStatusPath}
220 e2eoutput.TestContainerOutput(ctx, f, "seccomp runtime/default", pod, 0, []string{"2"})
221 })
222
223 ginkgo.It("should support seccomp default which is unconfined [LinuxOnly]", func(ctx context.Context) {
224 pod := scTestPod(false, false)
225 pod.Spec.Containers[0].Command = []string{"grep", SeccompProcStatusField, ProcSelfStatusPath}
226 e2eoutput.TestContainerOutput(ctx, f, "seccomp default unconfined", pod, 0, []string{"0"})
227 })
228 })
229
230 func testPodSELinuxLabeling(ctx context.Context, f *framework.Framework, hostIPC bool, hostPID bool) {
231
232
233 pod := scTestPod(hostIPC, hostPID)
234 volumeName := "test-volume"
235 mountPath := "/mounted_volume"
236 pod.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
237 {
238 Name: volumeName,
239 MountPath: mountPath,
240 },
241 }
242 pod.Spec.Volumes = []v1.Volume{
243 {
244 Name: volumeName,
245 VolumeSource: v1.VolumeSource{
246 EmptyDir: &v1.EmptyDirVolumeSource{
247 Medium: v1.StorageMediumDefault,
248 },
249 },
250 },
251 }
252 pod.Spec.SecurityContext.SELinuxOptions = &v1.SELinuxOptions{
253 Level: "s0:c0,c1",
254 }
255 pod.Spec.Containers[0].Command = []string{"sleep", "6000"}
256
257 client := f.ClientSet.CoreV1().Pods(f.Namespace.Name)
258 pod, err := client.Create(ctx, pod, metav1.CreateOptions{})
259
260 framework.ExpectNoError(err, "Error creating pod %v", pod)
261 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod))
262
263 testContent := "hello"
264 testFilePath := mountPath + "/TEST"
265 tk := e2ekubectl.NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, f.Namespace.Name)
266 err = tk.WriteFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, testFilePath, testContent)
267 framework.ExpectNoError(err)
268 content, err := tk.ReadFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, testFilePath)
269 framework.ExpectNoError(err)
270 gomega.Expect(content).To(gomega.ContainSubstring(testContent))
271
272 foundPod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{})
273 framework.ExpectNoError(err)
274
275
276
277 volumeHostPath := fmt.Sprintf("%s/pods/%s/volumes/kubernetes.io~empty-dir/%s", framework.TestContext.KubeletRootDir, foundPod.UID, volumeName)
278 ginkgo.By(fmt.Sprintf("confirming a container with the same label can read the file under --kubelet-root-dir=%s", framework.TestContext.KubeletRootDir))
279 pod = scTestPod(hostIPC, hostPID)
280 pod.Spec.NodeName = foundPod.Spec.NodeName
281 volumeMounts := []v1.VolumeMount{
282 {
283 Name: volumeName,
284 MountPath: mountPath,
285 },
286 }
287 volumes := []v1.Volume{
288 {
289 Name: volumeName,
290 VolumeSource: v1.VolumeSource{
291 HostPath: &v1.HostPathVolumeSource{
292 Path: volumeHostPath,
293 },
294 },
295 },
296 }
297 pod.Spec.Containers[0].VolumeMounts = volumeMounts
298 pod.Spec.Volumes = volumes
299 pod.Spec.Containers[0].Command = []string{"cat", testFilePath}
300 pod.Spec.SecurityContext.SELinuxOptions = &v1.SELinuxOptions{
301 Level: "s0:c0,c1",
302 }
303 e2eoutput.TestContainerOutput(ctx, f, "Pod with same MCS label reading test file", pod, 0, []string{testContent})
304
305
306
307 ginkgo.By("confirming a container with a different MCS label is unable to read the file")
308 pod = scTestPod(hostIPC, hostPID)
309 pod.Spec.Volumes = volumes
310 pod.Spec.Containers[0].VolumeMounts = volumeMounts
311 pod.Spec.Containers[0].Command = []string{"sleep", "6000"}
312 pod.Spec.SecurityContext.SELinuxOptions = &v1.SELinuxOptions{
313 Level: "s0:c2,c3",
314 }
315 _, err = client.Create(ctx, pod, metav1.CreateOptions{})
316 framework.ExpectNoError(err, "Error creating pod %v", pod)
317
318 err = e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
319 framework.ExpectNoError(err, "Error waiting for pod to run %v", pod)
320
321
322 isEnforced, err := tk.ReadFileViaContainer(pod.Name, "test-container", "/sys/fs/selinux/enforce")
323 if err == nil && isEnforced == "1" {
324 _, err = tk.ReadFileViaContainer(pod.Name, "test-container", testFilePath)
325 gomega.Expect(err).To(gomega.HaveOccurred(), "expecting SELinux to not let the container with different MCS label to read the file")
326 }
327 }
328
View as plain text