k8s.io/kubernetes@v1.29.3/test/e2e_node/security_context_test.go (about)

     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 e2enode
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"os/exec"
    24  	"strings"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"k8s.io/apimachinery/pkg/util/uuid"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    32  	"k8s.io/kubernetes/test/e2e/nodefeature"
    33  	imageutils "k8s.io/kubernetes/test/utils/image"
    34  	admissionapi "k8s.io/pod-security-admission/api"
    35  
    36  	"github.com/onsi/ginkgo/v2"
    37  )
    38  
    39  var _ = SIGDescribe("Security Context", func() {
    40  	f := framework.NewDefaultFramework("security-context-test")
    41  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    42  	var podClient *e2epod.PodClient
    43  	ginkgo.BeforeEach(func() {
    44  		podClient = e2epod.NewPodClient(f)
    45  	})
    46  
    47  	f.Context(framework.WithNodeConformance(), "[LinuxOnly] Container PID namespace sharing", func() {
    48  		ginkgo.It("containers in pods using isolated PID namespaces should all receive PID 1", func(ctx context.Context) {
    49  			ginkgo.By("Create a pod with isolated PID namespaces.")
    50  			e2epod.NewPodClient(f).CreateSync(ctx, &v1.Pod{
    51  				ObjectMeta: metav1.ObjectMeta{Name: "isolated-pid-ns-test-pod"},
    52  				Spec: v1.PodSpec{
    53  					Containers: []v1.Container{
    54  						{
    55  							Name:    "test-container-1",
    56  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    57  							Command: []string{"/bin/top"},
    58  						},
    59  						{
    60  							Name:    "test-container-2",
    61  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    62  							Command: []string{"/bin/sleep"},
    63  							Args:    []string{"10000"},
    64  						},
    65  					},
    66  				},
    67  			})
    68  
    69  			ginkgo.By("Check if both containers receive PID 1.")
    70  			pid1 := e2epod.ExecCommandInContainer(f, "isolated-pid-ns-test-pod", "test-container-1", "/bin/pidof", "top")
    71  			pid2 := e2epod.ExecCommandInContainer(f, "isolated-pid-ns-test-pod", "test-container-2", "/bin/pidof", "sleep")
    72  			if pid1 != "1" || pid2 != "1" {
    73  				framework.Failf("PIDs of different containers are not all 1: test-container-1=%v, test-container-2=%v", pid1, pid2)
    74  			}
    75  		})
    76  
    77  		ginkgo.It("processes in containers sharing a pod namespace should be able to see each other", func(ctx context.Context) {
    78  			ginkgo.By("Create a pod with shared PID namespace.")
    79  			e2epod.NewPodClient(f).CreateSync(ctx, &v1.Pod{
    80  				ObjectMeta: metav1.ObjectMeta{Name: "shared-pid-ns-test-pod"},
    81  				Spec: v1.PodSpec{
    82  					ShareProcessNamespace: &[]bool{true}[0],
    83  					Containers: []v1.Container{
    84  						{
    85  							Name:    "test-container-1",
    86  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    87  							Command: []string{"/bin/top"},
    88  						},
    89  						{
    90  							Name:    "test-container-2",
    91  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    92  							Command: []string{"/bin/sleep"},
    93  							Args:    []string{"10000"},
    94  						},
    95  					},
    96  				},
    97  			})
    98  
    99  			ginkgo.By("Check if the process in one container is visible to the process in the other.")
   100  			pid1 := e2epod.ExecCommandInContainer(f, "shared-pid-ns-test-pod", "test-container-1", "/bin/pidof", "top")
   101  			pid2 := e2epod.ExecCommandInContainer(f, "shared-pid-ns-test-pod", "test-container-2", "/bin/pidof", "top")
   102  			if pid1 != pid2 {
   103  				framework.Failf("PIDs are not the same in different containers: test-container-1=%v, test-container-2=%v", pid1, pid2)
   104  			}
   105  		})
   106  	})
   107  
   108  	ginkgo.Context("when creating a pod in the host PID namespace", func() {
   109  		makeHostPidPod := func(podName, image string, command []string, hostPID bool) *v1.Pod {
   110  			return &v1.Pod{
   111  				ObjectMeta: metav1.ObjectMeta{
   112  					Name: podName,
   113  				},
   114  				Spec: v1.PodSpec{
   115  					RestartPolicy: v1.RestartPolicyNever,
   116  					HostPID:       hostPID,
   117  					Containers: []v1.Container{
   118  						{
   119  							Image:   image,
   120  							Name:    podName,
   121  							Command: command,
   122  						},
   123  					},
   124  				},
   125  			}
   126  		}
   127  		createAndWaitHostPidPod := func(ctx context.Context, podName string, hostPID bool) {
   128  			podClient.Create(ctx, makeHostPidPod(podName,
   129  				busyboxImage,
   130  				[]string{"sh", "-c", "pidof nginx || true"},
   131  				hostPID,
   132  			))
   133  
   134  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   135  		}
   136  
   137  		nginxPid := ""
   138  		ginkgo.BeforeEach(func(ctx context.Context) {
   139  			nginxPodName := "nginx-hostpid-" + string(uuid.NewUUID())
   140  			podClient.CreateSync(ctx, makeHostPidPod(nginxPodName,
   141  				imageutils.GetE2EImage(imageutils.Nginx),
   142  				nil,
   143  				true,
   144  			))
   145  
   146  			output := e2epod.ExecShellInContainer(f, nginxPodName, nginxPodName,
   147  				"cat /var/run/nginx.pid")
   148  			nginxPid = strings.TrimSpace(output)
   149  		})
   150  
   151  		f.It("should show its pid in the host PID namespace", nodefeature.HostAccess, func(ctx context.Context) {
   152  			busyboxPodName := "busybox-hostpid-" + string(uuid.NewUUID())
   153  			createAndWaitHostPidPod(ctx, busyboxPodName, true)
   154  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName)
   155  			if err != nil {
   156  				framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err)
   157  			}
   158  
   159  			pids := strings.TrimSpace(logs)
   160  			framework.Logf("Got nginx's pid %q from pod %q", pids, busyboxPodName)
   161  			if pids == "" {
   162  				framework.Failf("nginx's pid should be seen by hostpid containers")
   163  			}
   164  
   165  			pidSets := sets.NewString(strings.Split(pids, " ")...)
   166  			if !pidSets.Has(nginxPid) {
   167  				framework.Failf("nginx's pid should be seen by hostpid containers")
   168  			}
   169  		})
   170  
   171  		f.It("should not show its pid in the non-hostpid containers", nodefeature.HostAccess, func(ctx context.Context) {
   172  			busyboxPodName := "busybox-non-hostpid-" + string(uuid.NewUUID())
   173  			createAndWaitHostPidPod(ctx, busyboxPodName, false)
   174  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName)
   175  			if err != nil {
   176  				framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err)
   177  			}
   178  
   179  			pids := strings.TrimSpace(logs)
   180  			framework.Logf("Got nginx's pid %q from pod %q", pids, busyboxPodName)
   181  			pidSets := sets.NewString(strings.Split(pids, " ")...)
   182  			if pidSets.Has(nginxPid) {
   183  				framework.Failf("nginx's pid should not be seen by non-hostpid containers")
   184  			}
   185  		})
   186  	})
   187  
   188  	ginkgo.Context("when creating a pod in the host IPC namespace", func() {
   189  		makeHostIPCPod := func(podName, image string, command []string, hostIPC bool) *v1.Pod {
   190  			return &v1.Pod{
   191  				ObjectMeta: metav1.ObjectMeta{
   192  					Name: podName,
   193  				},
   194  				Spec: v1.PodSpec{
   195  					RestartPolicy: v1.RestartPolicyNever,
   196  					HostIPC:       hostIPC,
   197  					Containers: []v1.Container{
   198  						{
   199  							Image:   image,
   200  							Name:    podName,
   201  							Command: command,
   202  						},
   203  					},
   204  				},
   205  			}
   206  		}
   207  		createAndWaitHostIPCPod := func(ctx context.Context, podName string, hostNetwork bool) {
   208  			podClient.Create(ctx, makeHostIPCPod(podName,
   209  				imageutils.GetE2EImage(imageutils.IpcUtils),
   210  				[]string{"sh", "-c", "ipcs -m | awk '{print $2}'"},
   211  				hostNetwork,
   212  			))
   213  
   214  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   215  		}
   216  
   217  		hostSharedMemoryID := ""
   218  		ginkgo.BeforeEach(func() {
   219  			output, err := exec.Command("sh", "-c", "ipcmk -M 1048576 | awk '{print $NF}'").Output()
   220  			if err != nil {
   221  				framework.Failf("Failed to create the shared memory on the host: %v", err)
   222  			}
   223  			hostSharedMemoryID = strings.TrimSpace(string(output))
   224  			framework.Logf("Got host shared memory ID %q", hostSharedMemoryID)
   225  		})
   226  
   227  		f.It("should show the shared memory ID in the host IPC containers", nodefeature.HostAccess, func(ctx context.Context) {
   228  			ipcutilsPodName := "ipcutils-hostipc-" + string(uuid.NewUUID())
   229  			createAndWaitHostIPCPod(ctx, ipcutilsPodName, true)
   230  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, ipcutilsPodName, ipcutilsPodName)
   231  			if err != nil {
   232  				framework.Failf("GetPodLogs for pod %q failed: %v", ipcutilsPodName, err)
   233  			}
   234  
   235  			podSharedMemoryIDs := strings.TrimSpace(logs)
   236  			framework.Logf("Got shared memory IDs %q from pod %q", podSharedMemoryIDs, ipcutilsPodName)
   237  			if !strings.Contains(podSharedMemoryIDs, hostSharedMemoryID) {
   238  				framework.Failf("hostIPC container should show shared memory IDs on host")
   239  			}
   240  		})
   241  
   242  		f.It("should not show the shared memory ID in the non-hostIPC containers", nodefeature.HostAccess, func(ctx context.Context) {
   243  			ipcutilsPodName := "ipcutils-non-hostipc-" + string(uuid.NewUUID())
   244  			createAndWaitHostIPCPod(ctx, ipcutilsPodName, false)
   245  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, ipcutilsPodName, ipcutilsPodName)
   246  			if err != nil {
   247  				framework.Failf("GetPodLogs for pod %q failed: %v", ipcutilsPodName, err)
   248  			}
   249  
   250  			podSharedMemoryIDs := strings.TrimSpace(logs)
   251  			framework.Logf("Got shared memory IDs %q from pod %q", podSharedMemoryIDs, ipcutilsPodName)
   252  			if strings.Contains(podSharedMemoryIDs, hostSharedMemoryID) {
   253  				framework.Failf("non-hostIPC container should not show shared memory IDs on host")
   254  			}
   255  		})
   256  
   257  		ginkgo.AfterEach(func() {
   258  			if hostSharedMemoryID != "" {
   259  				_, err := exec.Command("sh", "-c", fmt.Sprintf("ipcrm -m %q", hostSharedMemoryID)).Output()
   260  				if err != nil {
   261  					framework.Failf("Failed to remove shared memory %q on the host: %v", hostSharedMemoryID, err)
   262  				}
   263  			}
   264  		})
   265  	})
   266  
   267  	ginkgo.Context("when creating a pod in the host network namespace", func() {
   268  		makeHostNetworkPod := func(podName, image string, command []string, hostNetwork bool) *v1.Pod {
   269  			return &v1.Pod{
   270  				ObjectMeta: metav1.ObjectMeta{
   271  					Name: podName,
   272  				},
   273  				Spec: v1.PodSpec{
   274  					RestartPolicy: v1.RestartPolicyNever,
   275  					HostNetwork:   hostNetwork,
   276  					Containers: []v1.Container{
   277  						{
   278  							Image:   image,
   279  							Name:    podName,
   280  							Command: command,
   281  						},
   282  					},
   283  				},
   284  			}
   285  		}
   286  		listListeningPortsCommand := []string{"sh", "-c", "netstat -ln"}
   287  		createAndWaitHostNetworkPod := func(ctx context.Context, podName string, hostNetwork bool) {
   288  			podClient.Create(ctx, makeHostNetworkPod(podName,
   289  				busyboxImage,
   290  				listListeningPortsCommand,
   291  				hostNetwork,
   292  			))
   293  
   294  			podClient.WaitForSuccess(ctx, podName, framework.PodStartTimeout)
   295  		}
   296  
   297  		listeningPort := ""
   298  		var l net.Listener
   299  		var err error
   300  		ginkgo.BeforeEach(func() {
   301  			l, err = net.Listen("tcp", ":0")
   302  			if err != nil {
   303  				framework.Failf("Failed to open a new tcp port: %v", err)
   304  			}
   305  			addr := strings.Split(l.Addr().String(), ":")
   306  			listeningPort = addr[len(addr)-1]
   307  			framework.Logf("Opened a new tcp port %q", listeningPort)
   308  		})
   309  
   310  		f.It("should listen on same port in the host network containers", nodefeature.HostAccess, func(ctx context.Context) {
   311  			busyboxPodName := "busybox-hostnetwork-" + string(uuid.NewUUID())
   312  			createAndWaitHostNetworkPod(ctx, busyboxPodName, true)
   313  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName)
   314  			if err != nil {
   315  				framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err)
   316  			}
   317  
   318  			framework.Logf("Got logs for pod %q: %q", busyboxPodName, logs)
   319  			if !strings.Contains(logs, listeningPort) {
   320  				framework.Failf("host-networked container should listening on same port as host")
   321  			}
   322  		})
   323  
   324  		f.It("shouldn't show the same port in the non-hostnetwork containers", nodefeature.HostAccess, func(ctx context.Context) {
   325  			busyboxPodName := "busybox-non-hostnetwork-" + string(uuid.NewUUID())
   326  			createAndWaitHostNetworkPod(ctx, busyboxPodName, false)
   327  			logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName)
   328  			if err != nil {
   329  				framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err)
   330  			}
   331  
   332  			framework.Logf("Got logs for pod %q: %q", busyboxPodName, logs)
   333  			if strings.Contains(logs, listeningPort) {
   334  				framework.Failf("non-hostnetworked container shouldn't show the same port as host")
   335  			}
   336  		})
   337  
   338  		ginkgo.AfterEach(func() {
   339  			if l != nil {
   340  				l.Close()
   341  			}
   342  		})
   343  	})
   344  })