...

Source file src/k8s.io/kubernetes/test/e2e/common/node/security_context.go

Documentation: k8s.io/kubernetes/test/e2e/common/node

     1  /*
     2  Copyright 2017 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 node
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/uuid"
    28  	"k8s.io/kubernetes/pkg/kubelet/events"
    29  	"k8s.io/kubernetes/test/e2e/feature"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    32  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    33  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    34  	"k8s.io/kubernetes/test/e2e/nodefeature"
    35  	imageutils "k8s.io/kubernetes/test/utils/image"
    36  	admissionapi "k8s.io/pod-security-admission/api"
    37  	"k8s.io/utils/pointer"
    38  	"k8s.io/utils/ptr"
    39  
    40  	"github.com/onsi/ginkgo/v2"
    41  	"github.com/onsi/gomega"
    42  )
    43  
    44  var (
    45  	// non-root UID used in tests.
    46  	nonRootTestUserID = int64(1000)
    47  )
    48  
    49  var _ = SIGDescribe("Security Context", func() {
    50  	f := framework.NewDefaultFramework("security-context-test")
    51  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    52  	var podClient *e2epod.PodClient
    53  	ginkgo.BeforeEach(func() {
    54  		podClient = e2epod.NewPodClient(f)
    55  	})
    56  
    57  	ginkgo.Context("When creating a pod with HostUsers", func() {
    58  		containerName := "userns-test"
    59  		makePod := func(hostUsers bool) *v1.Pod {
    60  			return &v1.Pod{
    61  				ObjectMeta: metav1.ObjectMeta{
    62  					Name: "userns-" + string(uuid.NewUUID()),
    63  				},
    64  				Spec: v1.PodSpec{
    65  					Containers: []v1.Container{
    66  						{
    67  							Name:    containerName,
    68  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    69  							Command: []string{"cat", "/proc/self/uid_map"},
    70  						},
    71  					},
    72  					RestartPolicy: v1.RestartPolicyNever,
    73  					HostUsers:     &hostUsers,
    74  				},
    75  			}
    76  		}
    77  
    78  		f.It("must create the user namespace if set to false [LinuxOnly]", feature.UserNamespacesSupport, func(ctx context.Context) {
    79  			// with hostUsers=false the pod must use a new user namespace
    80  			podClient := e2epod.PodClientNS(f, f.Namespace.Name)
    81  
    82  			createdPod1 := podClient.Create(ctx, makePod(false))
    83  			createdPod2 := podClient.Create(ctx, makePod(false))
    84  			ginkgo.DeferCleanup(func(ctx context.Context) {
    85  				ginkgo.By("delete the pods")
    86  				podClient.DeleteSync(ctx, createdPod1.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
    87  				podClient.DeleteSync(ctx, createdPod2.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
    88  			})
    89  			getLogs := func(pod *v1.Pod) (string, error) {
    90  				err := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, f.ClientSet, createdPod1.Name, f.Namespace.Name, f.Timeouts.PodStart)
    91  				if err != nil {
    92  					return "", err
    93  				}
    94  				podStatus, err := podClient.Get(ctx, pod.Name, metav1.GetOptions{})
    95  				if err != nil {
    96  					return "", err
    97  				}
    98  				return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podStatus.Name, containerName)
    99  			}
   100  
   101  			logs1, err := getLogs(createdPod1)
   102  			framework.ExpectNoError(err)
   103  			logs2, err := getLogs(createdPod2)
   104  			framework.ExpectNoError(err)
   105  
   106  			// 65536 is the size used for a user namespace.  Verify that the value is present
   107  			// in the /proc/self/uid_map file.
   108  			if !strings.Contains(logs1, "65536") || !strings.Contains(logs2, "65536") {
   109  				framework.Failf("user namespace not created")
   110  			}
   111  			if logs1 == logs2 {
   112  				framework.Failf("two different pods are running with the same user namespace configuration")
   113  			}
   114  		})
   115  
   116  		f.It("must not create the user namespace if set to true [LinuxOnly]", feature.UserNamespacesSupport, func(ctx context.Context) {
   117  			// with hostUsers=true the pod must use the host user namespace
   118  			pod := makePod(true)
   119  			// When running in the host's user namespace, the /proc/self/uid_map file content looks like:
   120  			// 0          0 4294967295
   121  			// Verify the value 4294967295 is present in the output.
   122  			e2epodoutput.TestContainerOutput(ctx, f, "read namespace", pod, 0, []string{
   123  				"4294967295",
   124  			})
   125  		})
   126  
   127  		f.It("should mount all volumes with proper permissions with hostUsers=false [LinuxOnly]", feature.UserNamespacesSupport, func(ctx context.Context) {
   128  			// Create all volume types supported: configmap, secret, downwardAPI, projected.
   129  
   130  			// Create configmap.
   131  			name := "userns-volumes-test-" + string(uuid.NewUUID())
   132  			configMap := newConfigMap(f, name)
   133  			ginkgo.By(fmt.Sprintf("Creating configMap %v/%v", f.Namespace.Name, configMap.Name))
   134  			var err error
   135  			if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
   136  				framework.Failf("unable to create test configMap %s: %v", configMap.Name, err)
   137  			}
   138  
   139  			// Create secret.
   140  			secret := secretForTest(f.Namespace.Name, name)
   141  			ginkgo.By(fmt.Sprintf("Creating secret with name %s", secret.Name))
   142  			if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
   143  				framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   144  			}
   145  
   146  			// downwardAPI definition.
   147  			downwardVolSource := &v1.DownwardAPIVolumeSource{
   148  				Items: []v1.DownwardAPIVolumeFile{
   149  					{
   150  						Path: "name",
   151  						FieldRef: &v1.ObjectFieldSelector{
   152  							APIVersion: "v1",
   153  							FieldPath:  "metadata.name",
   154  						},
   155  					},
   156  				},
   157  			}
   158  
   159  			// Create a pod with all the volumes
   160  			falseVar := false
   161  			pod := &v1.Pod{
   162  				ObjectMeta: metav1.ObjectMeta{
   163  					Name: "pod-userns-volumes-" + string(uuid.NewUUID()),
   164  				},
   165  				Spec: v1.PodSpec{
   166  					Containers: []v1.Container{
   167  						{
   168  							Name:  "userns-file-permissions",
   169  							Image: imageutils.GetE2EImage(imageutils.BusyBox),
   170  							// Print (numeric) GID of the files in /vol/.
   171  							// We limit to "type f" as kubelet uses symlinks to those files, but we
   172  							// don't care about the owner of the symlink itself, just the files.
   173  							Command: []string{"sh", "-c", "stat -c='%g' $(find /vol/ -type f)"},
   174  							VolumeMounts: []v1.VolumeMount{
   175  								{
   176  									Name:      "cfg",
   177  									MountPath: "/vol/cfg/",
   178  								},
   179  								{
   180  									Name:      "secret",
   181  									MountPath: "/vol/secret/",
   182  								},
   183  								{
   184  									Name:      "downward",
   185  									MountPath: "/vol/downward/",
   186  								},
   187  								{
   188  									Name:      "projected",
   189  									MountPath: "/vol/projected/",
   190  								},
   191  							},
   192  						},
   193  					},
   194  					Volumes: []v1.Volume{
   195  						{
   196  							Name: "cfg",
   197  							VolumeSource: v1.VolumeSource{
   198  								ConfigMap: &v1.ConfigMapVolumeSource{
   199  									LocalObjectReference: v1.LocalObjectReference{Name: configMap.Name},
   200  								},
   201  							},
   202  						},
   203  						{
   204  							Name: "secret",
   205  							VolumeSource: v1.VolumeSource{
   206  								Secret: &v1.SecretVolumeSource{
   207  									SecretName: secret.Name,
   208  								},
   209  							},
   210  						},
   211  						{
   212  							Name: "downward",
   213  							VolumeSource: v1.VolumeSource{
   214  								DownwardAPI: downwardVolSource,
   215  							},
   216  						},
   217  						{
   218  							Name: "projected",
   219  							VolumeSource: v1.VolumeSource{
   220  								Projected: &v1.ProjectedVolumeSource{
   221  									Sources: []v1.VolumeProjection{
   222  										{
   223  											DownwardAPI: &v1.DownwardAPIProjection{
   224  												Items: downwardVolSource.Items,
   225  											},
   226  										},
   227  										{
   228  											Secret: &v1.SecretProjection{
   229  												LocalObjectReference: v1.LocalObjectReference{Name: secret.Name},
   230  											},
   231  										},
   232  									},
   233  								},
   234  							},
   235  						},
   236  					},
   237  					HostUsers:     &falseVar,
   238  					RestartPolicy: v1.RestartPolicyNever,
   239  				},
   240  			}
   241  
   242  			// Expect one line for each file on all the volumes.
   243  			// Each line should be "=0" that means root inside the container is the owner of the file.
   244  			downwardAPIVolFiles := 1
   245  			projectedFiles := len(secret.Data) + downwardAPIVolFiles
   246  			e2epodoutput.TestContainerOutput(ctx, f, "check file permissions", pod, 0, []string{
   247  				strings.Repeat("=0\n", len(secret.Data)+len(configMap.Data)+downwardAPIVolFiles+projectedFiles),
   248  			})
   249  		})
   250  
   251  		f.It("should set FSGroup to user inside the container with hostUsers=false [LinuxOnly]", feature.UserNamespacesSupport, func(ctx context.Context) {
   252  			// Create configmap.
   253  			name := "userns-volumes-test-" + string(uuid.NewUUID())
   254  			configMap := newConfigMap(f, name)
   255  			ginkgo.By(fmt.Sprintf("Creating configMap %v/%v", f.Namespace.Name, configMap.Name))
   256  			var err error
   257  			if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
   258  				framework.Failf("unable to create test configMap %s: %v", configMap.Name, err)
   259  			}
   260  
   261  			// Create a pod with hostUsers=false
   262  			falseVar := false
   263  			fsGroup := int64(200)
   264  			pod := &v1.Pod{
   265  				ObjectMeta: metav1.ObjectMeta{
   266  					Name: "pod-userns-fsgroup-" + string(uuid.NewUUID()),
   267  				},
   268  				Spec: v1.PodSpec{
   269  					Containers: []v1.Container{
   270  						{
   271  							Name:  "userns-fsgroup",
   272  							Image: imageutils.GetE2EImage(imageutils.BusyBox),
   273  							// Print (numeric) GID of the files in /vol/.
   274  							// We limit to "type f" as kubelet uses symlinks to those files, but we
   275  							// don't care about the owner of the symlink itself, just the files.
   276  							Command: []string{"sh", "-c", "stat -c='%g' $(find /vol/ -type f)"},
   277  							VolumeMounts: []v1.VolumeMount{
   278  								{
   279  									Name:      "cfg",
   280  									MountPath: "/vol/cfg/",
   281  								},
   282  							},
   283  						},
   284  					},
   285  					Volumes: []v1.Volume{
   286  						{
   287  							Name: "cfg",
   288  							VolumeSource: v1.VolumeSource{
   289  								ConfigMap: &v1.ConfigMapVolumeSource{
   290  									LocalObjectReference: v1.LocalObjectReference{Name: configMap.Name},
   291  								},
   292  							},
   293  						},
   294  					},
   295  					HostUsers:     &falseVar,
   296  					RestartPolicy: v1.RestartPolicyNever,
   297  					SecurityContext: &v1.PodSecurityContext{
   298  						FSGroup: &fsGroup,
   299  					},
   300  				},
   301  			}
   302  
   303  			// Expect one line for each file on all the volumes.
   304  			// Each line should be "=200" (fsGroup) that means it was mapped to the
   305  			// right user inside the container.
   306  			e2epodoutput.TestContainerOutput(ctx, f, "check FSGroup is mapped correctly", pod, 0, []string{
   307  				strings.Repeat(fmt.Sprintf("=%v\n", fsGroup), len(configMap.Data)),
   308  			})
   309  		})
   310  	})
   311  
   312  	ginkgo.Context("When creating a container with runAsUser", func() {
   313  		makeUserPod := func(podName, image string, command []string, userid int64) *v1.Pod {
   314  			return &v1.Pod{
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Name: podName,
   317  				},
   318  				Spec: v1.PodSpec{
   319  					RestartPolicy: v1.RestartPolicyNever,
   320  					Containers: []v1.Container{
   321  						{
   322  							Image:   image,
   323  							Name:    podName,
   324  							Command: command,
   325  							SecurityContext: &v1.SecurityContext{
   326  								RunAsUser: &userid,
   327  							},
   328  						},
   329  					},
   330  				},
   331  			}
   332  		}
   333  		createAndWaitUserPod := func(ctx context.Context, userid int64) {
   334  			podName := fmt.Sprintf("busybox-user-%d-%s", userid, uuid.NewUUID())
   335  			podClient.Create(ctx, makeUserPod(podName,
   336  				framework.BusyBoxImage,
   337  				[]string{"sh", "-c", fmt.Sprintf("test $(id -u) -eq %d", userid)},
   338  				userid,
   339  			))
   340  
   341  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   342  		}
   343  
   344  		/*
   345  			Release: v1.15
   346  			Testname: Security Context, runAsUser=65534
   347  			Description: Container is created with runAsUser option by passing uid 65534 to run as unpriviledged user. Pod MUST be in Succeeded phase.
   348  			[LinuxOnly]: This test is marked as LinuxOnly since Windows does not support running as UID / GID.
   349  		*/
   350  		framework.ConformanceIt("should run the container with uid 65534 [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   351  			createAndWaitUserPod(ctx, 65534)
   352  		})
   353  
   354  		/*
   355  			Release: v1.15
   356  			Testname: Security Context, runAsUser=0
   357  			Description: Container is created with runAsUser option by passing uid 0 to run as root privileged user. Pod MUST be in Succeeded phase.
   358  			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.
   359  			[LinuxOnly]: This test is marked as LinuxOnly since Windows does not support running as UID / GID.
   360  		*/
   361  		f.It("should run the container with uid 0 [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   362  			createAndWaitUserPod(ctx, 0)
   363  		})
   364  	})
   365  
   366  	ginkgo.Context("When creating a container with runAsNonRoot", func() {
   367  		rootImage := imageutils.GetE2EImage(imageutils.BusyBox)
   368  		nonRootImage := imageutils.GetE2EImage(imageutils.NonRoot)
   369  		makeNonRootPod := func(podName, image string, userid *int64) *v1.Pod {
   370  			return &v1.Pod{
   371  				ObjectMeta: metav1.ObjectMeta{
   372  					Name: podName,
   373  				},
   374  				Spec: v1.PodSpec{
   375  					RestartPolicy: v1.RestartPolicyNever,
   376  					Containers: []v1.Container{
   377  						{
   378  							Image:   image,
   379  							Name:    podName,
   380  							Command: []string{"id", "-u"}, // Print UID and exit
   381  							SecurityContext: &v1.SecurityContext{
   382  								RunAsNonRoot: pointer.BoolPtr(true),
   383  								RunAsUser:    userid,
   384  							},
   385  						},
   386  					},
   387  				},
   388  			}
   389  		}
   390  
   391  		ginkgo.It("should run with an explicit non-root user ID [LinuxOnly]", func(ctx context.Context) {
   392  			// creates a pod with RunAsUser, which is not supported on Windows.
   393  			e2eskipper.SkipIfNodeOSDistroIs("windows")
   394  			name := "explicit-nonroot-uid"
   395  			pod := makeNonRootPod(name, rootImage, pointer.Int64Ptr(nonRootTestUserID))
   396  			podClient.Create(ctx, pod)
   397  
   398  			podClient.WaitForSuccess(ctx, name, framework.PodStartTimeout)
   399  			framework.ExpectNoError(podClient.MatchContainerOutput(ctx, name, name, "1000"))
   400  		})
   401  		ginkgo.It("should not run with an explicit root user ID [LinuxOnly]", func(ctx context.Context) {
   402  			// creates a pod with RunAsUser, which is not supported on Windows.
   403  			e2eskipper.SkipIfNodeOSDistroIs("windows")
   404  			name := "explicit-root-uid"
   405  			pod := makeNonRootPod(name, nonRootImage, pointer.Int64Ptr(0))
   406  			pod = podClient.Create(ctx, pod)
   407  
   408  			ev, err := podClient.WaitForErrorEventOrSuccess(ctx, pod)
   409  			framework.ExpectNoError(err)
   410  			gomega.Expect(ev).NotTo(gomega.BeNil())
   411  			gomega.Expect(ev.Reason).To(gomega.Equal(events.FailedToCreateContainer))
   412  		})
   413  		ginkgo.It("should run with an image specified user ID", func(ctx context.Context) {
   414  			name := "implicit-nonroot-uid"
   415  			pod := makeNonRootPod(name, nonRootImage, nil)
   416  			podClient.Create(ctx, pod)
   417  
   418  			podClient.WaitForSuccess(ctx, name, framework.PodStartTimeout)
   419  			framework.ExpectNoError(podClient.MatchContainerOutput(ctx, name, name, "1234"))
   420  		})
   421  		ginkgo.It("should not run without a specified user ID", func(ctx context.Context) {
   422  			name := "implicit-root-uid"
   423  			pod := makeNonRootPod(name, rootImage, nil)
   424  			pod = podClient.Create(ctx, pod)
   425  
   426  			ev, err := podClient.WaitForErrorEventOrSuccess(ctx, pod)
   427  			framework.ExpectNoError(err)
   428  			gomega.Expect(ev).NotTo(gomega.BeNil())
   429  			gomega.Expect(ev.Reason).To(gomega.Equal(events.FailedToCreateContainer))
   430  		})
   431  	})
   432  
   433  	ginkgo.Context("When creating a pod with readOnlyRootFilesystem", func() {
   434  		makeUserPod := func(podName, image string, command []string, readOnlyRootFilesystem bool) *v1.Pod {
   435  			return &v1.Pod{
   436  				ObjectMeta: metav1.ObjectMeta{
   437  					Name: podName,
   438  				},
   439  				Spec: v1.PodSpec{
   440  					RestartPolicy: v1.RestartPolicyNever,
   441  					Containers: []v1.Container{
   442  						{
   443  							Image:   image,
   444  							Name:    podName,
   445  							Command: command,
   446  							SecurityContext: &v1.SecurityContext{
   447  								ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
   448  							},
   449  						},
   450  					},
   451  				},
   452  			}
   453  		}
   454  		createAndWaitUserPod := func(ctx context.Context, readOnlyRootFilesystem bool) string {
   455  			podName := fmt.Sprintf("busybox-readonly-%v-%s", readOnlyRootFilesystem, uuid.NewUUID())
   456  			podClient.Create(ctx, makeUserPod(podName,
   457  				framework.BusyBoxImage,
   458  				[]string{"sh", "-c", "touch checkfile"},
   459  				readOnlyRootFilesystem,
   460  			))
   461  
   462  			if readOnlyRootFilesystem {
   463  				waitForFailure(ctx, f, podName, framework.PodStartTimeout)
   464  			} else {
   465  				podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   466  			}
   467  
   468  			return podName
   469  		}
   470  
   471  		/*
   472  			Release: v1.15
   473  			Testname: Security Context, readOnlyRootFilesystem=true.
   474  			Description: Container is configured to run with readOnlyRootFilesystem to true which will force containers to run with a read only root file system.
   475  			Write operation MUST NOT be allowed and Pod MUST be in Failed state.
   476  			At this moment we are not considering this test for Conformance due to use of SecurityContext.
   477  			[LinuxOnly]: This test is marked as LinuxOnly since Windows does not support creating containers with read-only access.
   478  		*/
   479  		f.It("should run the container with readonly rootfs when readOnlyRootFilesystem=true [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   480  			createAndWaitUserPod(ctx, true)
   481  		})
   482  
   483  		/*
   484  			Release: v1.15
   485  			Testname: Security Context, readOnlyRootFilesystem=false.
   486  			Description: Container is configured to run with readOnlyRootFilesystem to false.
   487  			Write operation MUST be allowed and Pod MUST be in Succeeded state.
   488  		*/
   489  		framework.ConformanceIt("should run the container with writable rootfs when readOnlyRootFilesystem=false", f.WithNodeConformance(), func(ctx context.Context) {
   490  			createAndWaitUserPod(ctx, false)
   491  		})
   492  	})
   493  
   494  	ginkgo.Context("When creating a pod with privileged", func() {
   495  		makeUserPod := func(podName, image string, command []string, privileged bool) *v1.Pod {
   496  			return &v1.Pod{
   497  				ObjectMeta: metav1.ObjectMeta{
   498  					Name: podName,
   499  				},
   500  				Spec: v1.PodSpec{
   501  					RestartPolicy: v1.RestartPolicyNever,
   502  					Containers: []v1.Container{
   503  						{
   504  							Image:   image,
   505  							Name:    podName,
   506  							Command: command,
   507  							SecurityContext: &v1.SecurityContext{
   508  								Privileged: &privileged,
   509  							},
   510  						},
   511  					},
   512  				},
   513  			}
   514  		}
   515  		createAndWaitUserPod := func(ctx context.Context, privileged bool) string {
   516  			podName := fmt.Sprintf("busybox-privileged-%v-%s", privileged, uuid.NewUUID())
   517  			podClient.Create(ctx, makeUserPod(podName,
   518  				framework.BusyBoxImage,
   519  				[]string{"sh", "-c", "ip link add dummy0 type dummy || true"},
   520  				privileged,
   521  			))
   522  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   523  			return podName
   524  		}
   525  		/*
   526  			Release: v1.15
   527  			Testname: Security Context, privileged=false.
   528  			Description: Create a container to run in unprivileged mode by setting pod's SecurityContext Privileged option as false. Pod MUST be in Succeeded phase.
   529  			[LinuxOnly]: This test is marked as LinuxOnly since it runs a Linux-specific command.
   530  		*/
   531  		framework.ConformanceIt("should run the container as unprivileged when false [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   532  			podName := createAndWaitUserPod(ctx, false)
   533  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podName, podName)
   534  			if err != nil {
   535  				framework.Failf("GetPodLogs for pod %q failed: %v", podName, err)
   536  			}
   537  
   538  			framework.Logf("Got logs for pod %q: %q", podName, logs)
   539  			if !strings.Contains(logs, "Operation not permitted") {
   540  				framework.Failf("unprivileged container shouldn't be able to create dummy device")
   541  			}
   542  		})
   543  
   544  		f.It("should run the container as privileged when true [LinuxOnly]", nodefeature.HostAccess, func(ctx context.Context) {
   545  			podName := createAndWaitUserPod(ctx, true)
   546  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podName, podName)
   547  			if err != nil {
   548  				framework.Failf("GetPodLogs for pod %q failed: %v", podName, err)
   549  			}
   550  
   551  			framework.Logf("Got logs for pod %q: %q", podName, logs)
   552  			if strings.Contains(logs, "Operation not permitted") {
   553  				framework.Failf("privileged container should be able to create dummy device")
   554  			}
   555  		})
   556  	})
   557  
   558  	ginkgo.Context("when creating containers with AllowPrivilegeEscalation", func() {
   559  		makeAllowPrivilegeEscalationPod := func(podName string, allowPrivilegeEscalation *bool, uid int64) *v1.Pod {
   560  			return &v1.Pod{
   561  				ObjectMeta: metav1.ObjectMeta{
   562  					Name: podName,
   563  				},
   564  				Spec: v1.PodSpec{
   565  					RestartPolicy: v1.RestartPolicyNever,
   566  					Containers: []v1.Container{
   567  						{
   568  							Image: imageutils.GetE2EImage(imageutils.Nonewprivs),
   569  							Name:  podName,
   570  							SecurityContext: &v1.SecurityContext{
   571  								AllowPrivilegeEscalation: allowPrivilegeEscalation,
   572  								RunAsUser:                &uid,
   573  							},
   574  						},
   575  					},
   576  				},
   577  			}
   578  		}
   579  		createAndMatchOutput := func(ctx context.Context, podName, output string, allowPrivilegeEscalation *bool, uid int64) error {
   580  			podClient.Create(ctx, makeAllowPrivilegeEscalationPod(podName,
   581  				allowPrivilegeEscalation,
   582  				uid,
   583  			))
   584  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   585  			return podClient.MatchContainerOutput(ctx, podName, podName, output)
   586  		}
   587  
   588  		/*
   589  			Release: v1.15
   590  			Testname: Security Context, allowPrivilegeEscalation unset, uid != 0.
   591  			Description: Configuring the allowPrivilegeEscalation unset, allows the privilege escalation operation.
   592  			A container is configured with allowPrivilegeEscalation not specified (nil) and a given uid which is not 0.
   593  			When the container is run, container's output MUST match with expected output verifying container ran with uid=0.
   594  			This e2e Can not be promoted to Conformance as it is Container Runtime dependent and not all conformant platforms will require this behavior.
   595  			[LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID, or privilege escalation.
   596  		*/
   597  		f.It("should allow privilege escalation when not explicitly set and uid != 0 [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   598  			podName := "alpine-nnp-nil-" + string(uuid.NewUUID())
   599  			if err := createAndMatchOutput(ctx, podName, "Effective uid: 0", nil, nonRootTestUserID); err != nil {
   600  				framework.Failf("Match output for pod %q failed: %v", podName, err)
   601  			}
   602  		})
   603  
   604  		/*
   605  			Release: v1.15
   606  			Testname: Security Context, allowPrivilegeEscalation=false.
   607  			Description: Configuring the allowPrivilegeEscalation to false, does not allow the privilege escalation operation.
   608  			A container is configured with allowPrivilegeEscalation=false and a given uid (1000) which is not 0.
   609  			When the container is run, container's output MUST match with expected output verifying container ran with given uid i.e. uid=1000.
   610  			[LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID, or privilege escalation.
   611  		*/
   612  		framework.ConformanceIt("should not allow privilege escalation when false [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   613  			podName := "alpine-nnp-false-" + string(uuid.NewUUID())
   614  			apeFalse := false
   615  			if err := createAndMatchOutput(ctx, podName, fmt.Sprintf("Effective uid: %d", nonRootTestUserID), &apeFalse, nonRootTestUserID); err != nil {
   616  				framework.Failf("Match output for pod %q failed: %v", podName, err)
   617  			}
   618  		})
   619  
   620  		/*
   621  			Release: v1.15
   622  			Testname: Security Context, allowPrivilegeEscalation=true.
   623  			Description: Configuring the allowPrivilegeEscalation to true, allows the privilege escalation operation.
   624  			A container is configured with allowPrivilegeEscalation=true and a given uid (1000) which is not 0.
   625  			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).
   626  			This e2e Can not be promoted to Conformance as it is Container Runtime dependent and runtime may not allow to run.
   627  			[LinuxOnly]: This test is marked LinuxOnly since Windows does not support running as UID / GID.
   628  		*/
   629  		f.It("should allow privilege escalation when true [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
   630  			podName := "alpine-nnp-true-" + string(uuid.NewUUID())
   631  			apeTrue := true
   632  			if err := createAndMatchOutput(ctx, podName, "Effective uid: 0", &apeTrue, nonRootTestUserID); err != nil {
   633  				framework.Failf("Match output for pod %q failed: %v", podName, err)
   634  			}
   635  		})
   636  	})
   637  })
   638  
   639  var _ = SIGDescribe("User Namespaces for Pod Security Standards [LinuxOnly]", func() {
   640  	f := framework.NewDefaultFramework("user-namespaces-pss-test")
   641  	f.NamespacePodSecurityEnforceLevel = admissionapi.LevelRestricted
   642  
   643  	ginkgo.Context("with UserNamespacesSupport and UserNamespacesPodSecurityStandards enabled", func() {
   644  		f.It("should allow pod", feature.UserNamespacesPodSecurityStandards, func(ctx context.Context) {
   645  			name := "pod-user-namespaces-pss-" + string(uuid.NewUUID())
   646  			pod := &v1.Pod{
   647  				ObjectMeta: metav1.ObjectMeta{Name: name},
   648  				Spec: v1.PodSpec{
   649  					RestartPolicy:   v1.RestartPolicyNever,
   650  					HostUsers:       ptr.To(false),
   651  					SecurityContext: &v1.PodSecurityContext{},
   652  					Containers: []v1.Container{
   653  						{
   654  							Name:    name,
   655  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   656  							Command: []string{"whoami"},
   657  							SecurityContext: &v1.SecurityContext{
   658  								AllowPrivilegeEscalation: ptr.To(false),
   659  								Capabilities:             &v1.Capabilities{Drop: []v1.Capability{"ALL"}},
   660  								SeccompProfile:           &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault},
   661  							},
   662  						},
   663  					},
   664  				},
   665  			}
   666  
   667  			e2epodoutput.TestContainerOutput(ctx, f, "RunAsUser-RunAsNonRoot", pod, 0, []string{"root"})
   668  		})
   669  	})
   670  })
   671  
   672  // waitForFailure waits for pod to fail.
   673  func waitForFailure(ctx context.Context, f *framework.Framework, name string, timeout time.Duration) {
   674  	gomega.Expect(e2epod.WaitForPodCondition(ctx, f.ClientSet, f.Namespace.Name, name, fmt.Sprintf("%s or %s", v1.PodSucceeded, v1.PodFailed), timeout,
   675  		func(pod *v1.Pod) (bool, error) {
   676  			switch pod.Status.Phase {
   677  			case v1.PodFailed:
   678  				return true, nil
   679  			case v1.PodSucceeded:
   680  				return true, fmt.Errorf("pod %q succeeded with reason: %q, message: %q", name, pod.Status.Reason, pod.Status.Message)
   681  			default:
   682  				return false, nil
   683  			}
   684  		},
   685  	)).To(gomega.Succeed(), "wait for pod %q to fail", name)
   686  }
   687  

View as plain text