From 21e8d2958190e9813fe1122d1e7a91e8143a5193 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Thu, 16 Feb 2023 06:38:12 -0800 Subject: [PATCH] kubectl: add unit tests for kubectl debug profiles Unit test netadmin profile preserves existing capabilities. Unit test debug profiles in TestGenerateNodeDebugPod Unit test debug profiles in TestGeneratePodCopyWithDebugContainer Organize Go imports in unit tests Signed-off-by: Will Daly --- .../kubectl/pkg/cmd/debug/debug_test.go | 380 +++++++++++++++++- .../kubectl/pkg/cmd/debug/profiles_test.go | 89 ++++ 2 files changed, 462 insertions(+), 7 deletions(-) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go index 80d32166f9f..e46280b259c 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/debug_test.go @@ -22,16 +22,15 @@ import ( "testing" "time" - "github.com/spf13/cobra" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "k8s.io/utils/pointer" + "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + "k8s.io/utils/pointer" ) func TestGenerateDebugContainer(t *testing.T) { @@ -361,6 +360,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Container: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -398,6 +398,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Image: "busybox", PullPolicy: corev1.PullIfNotPresent, SameNode: true, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -435,6 +436,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Container: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -481,6 +483,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Container: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -524,6 +527,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Name: "TEST", Value: "test", }}, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -568,6 +572,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Args: []string{"/bin/echo", "one", "two", "three"}, Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -610,6 +615,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { ArgsOnly: true, Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -650,6 +656,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Container: "debugger", Args: []string{"sleep", "1d"}, PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -690,6 +697,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { CopyTo: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -728,6 +736,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { CopyTo: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -766,6 +775,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { CopyTo: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -820,6 +830,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { CopyTo: "debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -873,6 +884,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { PullPolicy: corev1.PullIfNotPresent, ShareProcesses: true, shareProcessedChanged: true, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -914,6 +926,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Container: "app", Image: "busybox", TargetNames: []string{"myapp"}, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "myapp"}, @@ -940,6 +953,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { CopyTo: "myapp-copy", Container: "app", SetImages: map[string]string{"app": "busybox"}, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -969,6 +983,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { opts: &DebugOptions{ CopyTo: "myapp-copy", SetImages: map[string]string{"*": "busybox"}, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -998,6 +1013,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { opts: &DebugOptions{ CopyTo: "myapp-copy", SetImages: map[string]string{"*": "busybox", "app": "app-debugger"}, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1032,6 +1048,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { Interactive: true, TargetNames: []string{"mypod"}, TTY: true, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "mypod"}, @@ -1075,6 +1092,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { ShareProcesses: true, TargetNames: []string{"mypod"}, TTY: true, + Profile: ProfileLegacy, }, havePod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "mypod"}, @@ -1102,12 +1120,179 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) { }, }, }, + { + name: "general profile", + opts: &DebugOptions{ + CopyTo: "debugger", + Container: "debugger", + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileGeneral, + }, + havePod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + }, + }, + NodeName: "node-1", + }, + }, + wantPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "debugger", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SYS_PTRACE"}, + }, + }, + }, + }, + ShareProcessNamespace: pointer.Bool(true), + }, + }, + }, + { + name: "baseline profile", + opts: &DebugOptions{ + CopyTo: "debugger", + Container: "debugger", + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileBaseline, + }, + havePod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + }, + }, + NodeName: "node-1", + }, + }, + wantPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "debugger", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + }, + }, + ShareProcessNamespace: pointer.Bool(true), + }, + }, + }, + { + name: "restricted profile", + opts: &DebugOptions{ + CopyTo: "debugger", + Container: "debugger", + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileRestricted, + }, + havePod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + }, + }, + NodeName: "node-1", + }, + }, + wantPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "debugger", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + RunAsNonRoot: pointer.Bool(true), + }, + }, + }, + ShareProcessNamespace: pointer.Bool(true), + }, + }, + }, + { + name: "netadmin profile", + opts: &DebugOptions{ + CopyTo: "debugger", + Container: "debugger", + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileNetadmin, + }, + havePod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + }, + }, + NodeName: "node-1", + }, + }, + wantPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "debugger", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + }, } { t.Run(tc.name, func(t *testing.T) { var err error - tc.opts.Applier, err = NewProfileApplier(ProfileLegacy) + tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile) if err != nil { - t.Fatalf("Fail to create legacy profile: %v", err) + t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err) } tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard() suffixCounter = 0 @@ -1147,6 +1332,7 @@ func TestGenerateNodeDebugPod(t *testing.T) { opts: &DebugOptions{ Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, expected: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1200,6 +1386,7 @@ func TestGenerateNodeDebugPod(t *testing.T) { Container: "custom-debugger", Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, expected: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1255,6 +1442,7 @@ func TestGenerateNodeDebugPod(t *testing.T) { Args: []string{"echo", "one", "two", "three"}, Image: "busybox", PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileLegacy, }, expected: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -1297,12 +1485,190 @@ func TestGenerateNodeDebugPod(t *testing.T) { }, }, }, + { + name: "general profile", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-XXX", + }, + }, + opts: &DebugOptions{ + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileGeneral, + }, + expected: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-debugger-node-XXX-1", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/host", + Name: "host-root", + }, + }, + }, + }, + HostIPC: true, + HostNetwork: true, + HostPID: true, + NodeName: "node-XXX", + RestartPolicy: corev1.RestartPolicyNever, + Volumes: []corev1.Volume{ + { + Name: "host-root", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Path: "/"}, + }, + }, + }, + Tolerations: []corev1.Toleration{ + { + Operator: corev1.TolerationOpExists, + }, + }, + }, + }, + }, + { + name: "baseline profile", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-XXX", + }, + }, + opts: &DebugOptions{ + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileBaseline, + }, + expected: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-debugger-node-XXX-1", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + VolumeMounts: nil, + }, + }, + HostIPC: false, + HostNetwork: false, + HostPID: false, + NodeName: "node-XXX", + RestartPolicy: corev1.RestartPolicyNever, + Volumes: nil, + Tolerations: []corev1.Toleration{ + { + Operator: corev1.TolerationOpExists, + }, + }, + }, + }, + }, + { + name: "restricted profile", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-XXX", + }, + }, + opts: &DebugOptions{ + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileRestricted, + }, + expected: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-debugger-node-XXX-1", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + VolumeMounts: nil, + }, + }, + HostIPC: false, + HostNetwork: false, + HostPID: false, + NodeName: "node-XXX", + RestartPolicy: corev1.RestartPolicyNever, + Volumes: nil, + Tolerations: []corev1.Toleration{ + { + Operator: corev1.TolerationOpExists, + }, + }, + }, + }, + }, + { + name: "netadmin profile", + node: &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-XXX", + }, + }, + opts: &DebugOptions{ + Image: "busybox", + PullPolicy: corev1.PullIfNotPresent, + Profile: ProfileNetadmin, + }, + expected: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-debugger-node-XXX-1", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "debugger", + Image: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + VolumeMounts: nil, + SecurityContext: &corev1.SecurityContext{ + Privileged: pointer.Bool(true), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + }, + }, + HostIPC: true, + HostNetwork: true, + HostPID: true, + NodeName: "node-XXX", + RestartPolicy: corev1.RestartPolicyNever, + Volumes: nil, + Tolerations: []corev1.Toleration{ + { + Operator: corev1.TolerationOpExists, + }, + }, + }, + }, + }, } { t.Run(tc.name, func(t *testing.T) { var err error - tc.opts.Applier, err = NewProfileApplier(ProfileLegacy) + tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile) if err != nil { - t.Fatalf("Fail to create legacy profile: %v", err) + t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err) } tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard() suffixCounter = 0 diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go index 1e94a7a969b..6dfe3f994aa 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/debug/profiles_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -518,6 +519,52 @@ func TestNetAdminProfile(t *testing.T) { }, }, }, + { + name: "debug by pod copy preserve existing capability", + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "app", Image: "appimage"}, + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SYS_PTRACE"}, + }, + }, + }, + }, + }, + }, + containerName: "dbg", + target: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "app", Image: "appimage"}, + }, + }, + }, + expectPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "podcopy"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "app", Image: "appimage"}, + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SYS_PTRACE", "NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + }, { name: "debug by node", pod: &corev1.Pod{ @@ -551,6 +598,48 @@ func TestNetAdminProfile(t *testing.T) { }, }, }, + { + name: "debug by node preserve existing capability", + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Privileged: pointer.BoolPtr(true), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SYS_PTRACE"}, + }, + }, + }, + }, + }, + }, + containerName: "dbg", + target: testNode, + expectPod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Spec: corev1.PodSpec{ + HostNetwork: true, + HostPID: true, + HostIPC: true, + Containers: []corev1.Container{ + { + Name: "dbg", + Image: "dbgimage", + SecurityContext: &corev1.SecurityContext{ + Privileged: pointer.BoolPtr(true), + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"SYS_PTRACE", "NET_ADMIN"}, + }, + }, + }, + }, + }, + }, + }, } for _, test := range tests {