From bfae4f6bfdb047dd348841c3d3e20b00d964f9c6 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 Kubernetes-commit: 21e8d2958190e9813fe1122d1e7a91e8143a5193 --- pkg/cmd/debug/debug_test.go | 380 ++++++++++++++++++++++++++++++++- pkg/cmd/debug/profiles_test.go | 89 ++++++++ 2 files changed, 462 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/debug/debug_test.go b/pkg/cmd/debug/debug_test.go index 80d32166f..e46280b25 100644 --- a/pkg/cmd/debug/debug_test.go +++ b/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/pkg/cmd/debug/profiles_test.go b/pkg/cmd/debug/profiles_test.go index 1e94a7a96..6dfe3f994 100644 --- a/pkg/cmd/debug/profiles_test.go +++ b/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 {