Merge pull request #120346 from ardaguclu/custom-debug-profile
kubectl debug: Add custom debug profiles on top of static profiles
This commit is contained in:
		@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
@@ -106,12 +107,14 @@ var (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var nameSuffixFunc = utilrand.String
 | 
					var nameSuffixFunc = utilrand.String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DebugAttachFunc func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DebugOptions holds the options for an invocation of kubectl debug.
 | 
					// DebugOptions holds the options for an invocation of kubectl debug.
 | 
				
			||||||
type DebugOptions struct {
 | 
					type DebugOptions struct {
 | 
				
			||||||
	Args              []string
 | 
						Args              []string
 | 
				
			||||||
	ArgsOnly          bool
 | 
						ArgsOnly          bool
 | 
				
			||||||
	Attach            bool
 | 
						Attach            bool
 | 
				
			||||||
	AttachFunc      func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
 | 
						AttachFunc        DebugAttachFunc
 | 
				
			||||||
	Container         string
 | 
						Container         string
 | 
				
			||||||
	CopyTo            string
 | 
						CopyTo            string
 | 
				
			||||||
	Replace           bool
 | 
						Replace           bool
 | 
				
			||||||
@@ -128,6 +131,8 @@ type DebugOptions struct {
 | 
				
			|||||||
	TargetContainer   string
 | 
						TargetContainer   string
 | 
				
			||||||
	TTY               bool
 | 
						TTY               bool
 | 
				
			||||||
	Profile           string
 | 
						Profile           string
 | 
				
			||||||
 | 
						CustomProfileFile string
 | 
				
			||||||
 | 
						CustomProfile     *corev1.Container
 | 
				
			||||||
	Applier           ProfileApplier
 | 
						Applier           ProfileApplier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	explicitNamespace     bool
 | 
						explicitNamespace     bool
 | 
				
			||||||
@@ -193,6 +198,9 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
 | 
				
			|||||||
	cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
 | 
						cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
 | 
				
			||||||
	cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
 | 
						cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
 | 
				
			||||||
	cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
 | 
						cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
 | 
				
			||||||
 | 
						if cmdutil.DebugCustomProfile.IsEnabled() {
 | 
				
			||||||
 | 
							cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON file containing a partial container spec to customize built-in debug profiles."))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Complete finishes run-time initialization of debug.DebugOptions.
 | 
					// Complete finishes run-time initialization of debug.DebugOptions.
 | 
				
			||||||
@@ -256,6 +264,18 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
 | 
				
			|||||||
		o.Applier = applier
 | 
							o.Applier = applier
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if o.CustomProfileFile != "" {
 | 
				
			||||||
 | 
							customProfileBytes, err := os.ReadFile(o.CustomProfileFile)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("must pass a container spec json file for custom profile: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = json.Unmarshal(customProfileBytes, &o.CustomProfile)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clientConfig, err := restClientGetter.ToRESTConfig()
 | 
						clientConfig, err := restClientGetter.ToRESTConfig()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -348,6 +368,12 @@ func (o *DebugOptions) Validate() error {
 | 
				
			|||||||
		return fmt.Errorf("WarningPrinter can not be used without initialization")
 | 
							return fmt.Errorf("WarningPrinter can not be used without initialization")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if o.CustomProfile != nil {
 | 
				
			||||||
 | 
							if o.CustomProfile.Name != "" || len(o.CustomProfile.Command) > 0 || o.CustomProfile.Image != "" || o.CustomProfile.Lifecycle != nil || len(o.CustomProfile.VolumeDevices) > 0 {
 | 
				
			||||||
 | 
								return fmt.Errorf("name, command, image, lifecycle and volume devices are not modifiable via custom profile")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -467,6 +493,90 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
 | 
				
			|||||||
	return result, debugContainer.Name, nil
 | 
						return result, debugContainer.Name, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// applyCustomProfile applies given partial container json file on to the profile
 | 
				
			||||||
 | 
					// incorporated debug pod.
 | 
				
			||||||
 | 
					func (o *DebugOptions) applyCustomProfile(debugPod *corev1.Pod, containerName string) error {
 | 
				
			||||||
 | 
						o.CustomProfile.Name = containerName
 | 
				
			||||||
 | 
						customJS, err := json.Marshal(o.CustomProfile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to marshall custom profile: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var index int
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						for i, val := range debugPod.Spec.Containers {
 | 
				
			||||||
 | 
							if val.Name == containerName {
 | 
				
			||||||
 | 
								index = i
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to find the %s container in the pod %s", containerName, debugPod.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var debugContainerJS []byte
 | 
				
			||||||
 | 
						debugContainerJS, err = json.Marshal(debugPod.Spec.Containers[index])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to marshall container: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error creating three way patch to add debug container: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = json.Unmarshal(patchedContainer, &debugPod.Spec.Containers[index])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to unmarshall patched container to container: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// applyCustomProfileEphemeral applies given partial container json file on to the profile
 | 
				
			||||||
 | 
					// incorporated ephemeral container of the pod.
 | 
				
			||||||
 | 
					func (o *DebugOptions) applyCustomProfileEphemeral(debugPod *corev1.Pod, containerName string) error {
 | 
				
			||||||
 | 
						o.CustomProfile.Name = containerName
 | 
				
			||||||
 | 
						customJS, err := json.Marshal(o.CustomProfile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to marshall custom profile: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var index int
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						for i, val := range debugPod.Spec.EphemeralContainers {
 | 
				
			||||||
 | 
							if val.Name == containerName {
 | 
				
			||||||
 | 
								index = i
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to find the %s ephemeral container in the pod %s", containerName, debugPod.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var debugContainerJS []byte
 | 
				
			||||||
 | 
						debugContainerJS, err = json.Marshal(debugPod.Spec.EphemeralContainers[index])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to marshall ephemeral container:%w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						patchedContainer, err := strategicpatch.StrategicMergePatch(debugContainerJS, customJS, corev1.Container{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error creating three way patch to add debug container: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = json.Unmarshal(patchedContainer, &debugPod.Spec.EphemeralContainers[index])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to unmarshall patched container to ephemeral container: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// debugByCopy runs a copy of the target Pod with a debug container added or an original container modified
 | 
					// debugByCopy runs a copy of the target Pod with a debug container added or an original container modified
 | 
				
			||||||
func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
 | 
					func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
 | 
				
			||||||
	copied, dc, err := o.generatePodCopyWithDebugContainer(pod)
 | 
						copied, dc, err := o.generatePodCopyWithDebugContainer(pod)
 | 
				
			||||||
@@ -515,6 +625,13 @@ func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *co
 | 
				
			|||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if o.CustomProfile != nil {
 | 
				
			||||||
 | 
							err := o.applyCustomProfileEphemeral(copied, ec.Name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ec = &copied.Spec.EphemeralContainers[len(copied.Spec.EphemeralContainers)-1]
 | 
						ec = &copied.Spec.EphemeralContainers[len(copied.Spec.EphemeralContainers)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return copied, ec, nil
 | 
						return copied, ec, nil
 | 
				
			||||||
@@ -574,6 +691,13 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if o.CustomProfile != nil {
 | 
				
			||||||
 | 
							err := o.applyCustomProfile(p, cn)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return p, nil
 | 
						return p, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -656,6 +780,13 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
 | 
				
			|||||||
		return nil, "", err
 | 
							return nil, "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if o.CustomProfile != nil {
 | 
				
			||||||
 | 
							err = o.applyCustomProfile(copied, name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return copied, name, nil
 | 
						return copied, name, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,8 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmdutil "k8s.io/kubectl/pkg/cmd/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/go-cmp/cmp"
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
	"github.com/google/go-cmp/cmp/cmpopts"
 | 
						"github.com/google/go-cmp/cmp/cmpopts"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
@@ -1755,6 +1757,689 @@ func TestGenerateNodeDebugPod(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tc := range []struct {
 | 
				
			||||||
 | 
							name     string
 | 
				
			||||||
 | 
							node     *corev1.Node
 | 
				
			||||||
 | 
							opts     *DebugOptions
 | 
				
			||||||
 | 
							expected *corev1.Pod
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "baseline profile",
 | 
				
			||||||
 | 
								node: &corev1.Node{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "node-XXX",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileBaseline,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "node-debugger-node-XXX-1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:                     "debugger",
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												VolumeMounts:             nil,
 | 
				
			||||||
 | 
												Stdin:                    true,
 | 
				
			||||||
 | 
												TTY:                      false,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										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,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "node-debugger-node-XXX-1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:                     "debugger",
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												VolumeMounts:             nil,
 | 
				
			||||||
 | 
												Stdin:                    true,
 | 
				
			||||||
 | 
												TTY:                      false,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													RunAsNonRoot: pointer.Bool(true),
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													AllowPrivilegeEscalation: pointer.Bool(false),
 | 
				
			||||||
 | 
													SeccompProfile:           &corev1.SeccompProfile{Type: "RuntimeDefault"},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										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,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										Env: []corev1.EnvVar{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:  "TEST_KEY",
 | 
				
			||||||
 | 
												Value: "TEST_VALUE",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:                     "debugger",
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												Env: []corev1.EnvVar{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Name:  "TEST_KEY",
 | 
				
			||||||
 | 
														Value: "TEST_VALUE",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												VolumeMounts: nil,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:       true,
 | 
				
			||||||
 | 
										HostNetwork:   true,
 | 
				
			||||||
 | 
										HostPID:       true,
 | 
				
			||||||
 | 
										NodeName:      "node-XXX",
 | 
				
			||||||
 | 
										RestartPolicy: corev1.RestartPolicyNever,
 | 
				
			||||||
 | 
										Volumes:       nil,
 | 
				
			||||||
 | 
										Tolerations: []corev1.Toleration{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Operator: corev1.TolerationOpExists,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "sysadmin profile",
 | 
				
			||||||
 | 
								node: &corev1.Node{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "node-XXX",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileSysadmin,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										Env: []corev1.EnvVar{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:  "TEST_KEY",
 | 
				
			||||||
 | 
												Value: "TEST_VALUE",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										VolumeMounts: []corev1.VolumeMount{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:      "host-root",
 | 
				
			||||||
 | 
												ReadOnly:  true,
 | 
				
			||||||
 | 
												MountPath: "/host",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:                     "debugger",
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												Env: []corev1.EnvVar{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Name:  "TEST_KEY",
 | 
				
			||||||
 | 
														Value: "TEST_VALUE",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												VolumeMounts: []corev1.VolumeMount{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Name:      "host-root",
 | 
				
			||||||
 | 
														ReadOnly:  true,
 | 
				
			||||||
 | 
														MountPath: "/host",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													Privileged: pointer.Bool(true),
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										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,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
 | 
				
			||||||
 | 
									var err error
 | 
				
			||||||
 | 
									tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									pod, err := tc.opts.generateNodeDebugPod(tc.node)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to generate node debug pod: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.expected.Name = pod.Name
 | 
				
			||||||
 | 
									if diff := cmp.Diff(tc.expected, pod); diff != "" {
 | 
				
			||||||
 | 
										t.Error("unexpected diff in generated object: (-want +got):\n", diff)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tc := range []struct {
 | 
				
			||||||
 | 
							name     string
 | 
				
			||||||
 | 
							copyPod  *corev1.Pod
 | 
				
			||||||
 | 
							opts     *DebugOptions
 | 
				
			||||||
 | 
							expected *corev1.Pod
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "baseline profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileBaseline,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												VolumeMounts:             nil,
 | 
				
			||||||
 | 
												Stdin:                    true,
 | 
				
			||||||
 | 
												TTY:                      false,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:               false,
 | 
				
			||||||
 | 
										HostNetwork:           false,
 | 
				
			||||||
 | 
										HostPID:               false,
 | 
				
			||||||
 | 
										Volumes:               nil,
 | 
				
			||||||
 | 
										ShareProcessNamespace: pointer.Bool(true),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "restricted profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileRestricted,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												VolumeMounts:             nil,
 | 
				
			||||||
 | 
												Stdin:                    true,
 | 
				
			||||||
 | 
												TTY:                      false,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													AllowPrivilegeEscalation: pointer.Bool(false),
 | 
				
			||||||
 | 
													RunAsNonRoot:             pointer.Bool(false),
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													SeccompProfile: &corev1.SeccompProfile{
 | 
				
			||||||
 | 
														Type:             corev1.SeccompProfileTypeRuntimeDefault,
 | 
				
			||||||
 | 
														LocalhostProfile: nil,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:               false,
 | 
				
			||||||
 | 
										HostNetwork:           false,
 | 
				
			||||||
 | 
										HostPID:               false,
 | 
				
			||||||
 | 
										Volumes:               nil,
 | 
				
			||||||
 | 
										ShareProcessNamespace: pointer.Bool(true),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "sysadmin profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileRestricted,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Image:                    "busybox",
 | 
				
			||||||
 | 
												ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
												TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
												VolumeMounts:             nil,
 | 
				
			||||||
 | 
												Stdin:                    true,
 | 
				
			||||||
 | 
												TTY:                      false,
 | 
				
			||||||
 | 
												SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
													AllowPrivilegeEscalation: pointer.Bool(false),
 | 
				
			||||||
 | 
													RunAsNonRoot:             pointer.Bool(false),
 | 
				
			||||||
 | 
													Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
														Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													SeccompProfile: &corev1.SeccompProfile{
 | 
				
			||||||
 | 
														Type:             corev1.SeccompProfileTypeRuntimeDefault,
 | 
				
			||||||
 | 
														LocalhostProfile: nil,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:               false,
 | 
				
			||||||
 | 
										HostNetwork:           false,
 | 
				
			||||||
 | 
										HostPID:               false,
 | 
				
			||||||
 | 
										Volumes:               nil,
 | 
				
			||||||
 | 
										ShareProcessNamespace: pointer.Bool(true),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
 | 
				
			||||||
 | 
									var err error
 | 
				
			||||||
 | 
									tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to generate node debug pod: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.expected.Spec.Containers[0].Name = dc
 | 
				
			||||||
 | 
									if diff := cmp.Diff(tc.expected, pod); diff != "" {
 | 
				
			||||||
 | 
										t.Error("unexpected diff in generated object: (-want +got):\n", diff)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tc := range []struct {
 | 
				
			||||||
 | 
							name     string
 | 
				
			||||||
 | 
							copyPod  *corev1.Pod
 | 
				
			||||||
 | 
							opts     *DebugOptions
 | 
				
			||||||
 | 
							expected *corev1.Pod
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "baseline profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileBaseline,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										EphemeralContainers: []corev1.EphemeralContainer{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												EphemeralContainerCommon: corev1.EphemeralContainerCommon{
 | 
				
			||||||
 | 
													Name:                     "debugger-1",
 | 
				
			||||||
 | 
													Image:                    "busybox",
 | 
				
			||||||
 | 
													ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
													TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
													VolumeMounts:             nil,
 | 
				
			||||||
 | 
													Stdin:                    true,
 | 
				
			||||||
 | 
													TTY:                      false,
 | 
				
			||||||
 | 
													SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
														RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
														Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
															Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:     false,
 | 
				
			||||||
 | 
										HostNetwork: false,
 | 
				
			||||||
 | 
										HostPID:     false,
 | 
				
			||||||
 | 
										Volumes:     nil,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "restricted profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileRestricted,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										EphemeralContainers: []corev1.EphemeralContainer{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												EphemeralContainerCommon: corev1.EphemeralContainerCommon{
 | 
				
			||||||
 | 
													Name:                     "debugger-1",
 | 
				
			||||||
 | 
													Image:                    "busybox",
 | 
				
			||||||
 | 
													ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
													TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
													VolumeMounts:             nil,
 | 
				
			||||||
 | 
													Stdin:                    true,
 | 
				
			||||||
 | 
													TTY:                      false,
 | 
				
			||||||
 | 
													SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
														AllowPrivilegeEscalation: pointer.Bool(false),
 | 
				
			||||||
 | 
														RunAsNonRoot:             pointer.Bool(false),
 | 
				
			||||||
 | 
														Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
															Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
														SeccompProfile: &corev1.SeccompProfile{
 | 
				
			||||||
 | 
															Type:             corev1.SeccompProfileTypeRuntimeDefault,
 | 
				
			||||||
 | 
															LocalhostProfile: nil,
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:     false,
 | 
				
			||||||
 | 
										HostNetwork: false,
 | 
				
			||||||
 | 
										HostPID:     false,
 | 
				
			||||||
 | 
										Volumes:     nil,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "sysadmin profile",
 | 
				
			||||||
 | 
								copyPod: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								opts: &DebugOptions{
 | 
				
			||||||
 | 
									SameNode:   true,
 | 
				
			||||||
 | 
									Image:      "busybox",
 | 
				
			||||||
 | 
									PullPolicy: corev1.PullIfNotPresent,
 | 
				
			||||||
 | 
									Profile:    ProfileRestricted,
 | 
				
			||||||
 | 
									CustomProfile: &corev1.Container{
 | 
				
			||||||
 | 
										ImagePullPolicy: corev1.PullNever,
 | 
				
			||||||
 | 
										Stdin:           true,
 | 
				
			||||||
 | 
										TTY:             false,
 | 
				
			||||||
 | 
										SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
											Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
												Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											RunAsNonRoot: pointer.Bool(false),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										ServiceAccountName: "test",
 | 
				
			||||||
 | 
										NodeName:           "test-node",
 | 
				
			||||||
 | 
										EphemeralContainers: []corev1.EphemeralContainer{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												EphemeralContainerCommon: corev1.EphemeralContainerCommon{
 | 
				
			||||||
 | 
													Name:                     "debugger-1",
 | 
				
			||||||
 | 
													Image:                    "busybox",
 | 
				
			||||||
 | 
													ImagePullPolicy:          corev1.PullNever,
 | 
				
			||||||
 | 
													TerminationMessagePolicy: corev1.TerminationMessageReadFile,
 | 
				
			||||||
 | 
													VolumeMounts:             nil,
 | 
				
			||||||
 | 
													Stdin:                    true,
 | 
				
			||||||
 | 
													TTY:                      false,
 | 
				
			||||||
 | 
													SecurityContext: &corev1.SecurityContext{
 | 
				
			||||||
 | 
														AllowPrivilegeEscalation: pointer.Bool(false),
 | 
				
			||||||
 | 
														RunAsNonRoot:             pointer.Bool(false),
 | 
				
			||||||
 | 
														Capabilities: &corev1.Capabilities{
 | 
				
			||||||
 | 
															Drop: []corev1.Capability{"ALL"},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
														SeccompProfile: &corev1.SeccompProfile{
 | 
				
			||||||
 | 
															Type:             corev1.SeccompProfileTypeRuntimeDefault,
 | 
				
			||||||
 | 
															LocalhostProfile: nil,
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										HostIPC:     false,
 | 
				
			||||||
 | 
										HostNetwork: false,
 | 
				
			||||||
 | 
										HostPID:     false,
 | 
				
			||||||
 | 
										Volumes:     nil,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
 | 
				
			||||||
 | 
									var err error
 | 
				
			||||||
 | 
									tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										t.Fatalf("Fail to generate node debug pod: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
 | 
				
			||||||
 | 
									if diff := cmp.Diff(tc.expected, pod); diff != "" {
 | 
				
			||||||
 | 
										t.Error("unexpected diff in generated object: (-want +got):\n", diff)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCompleteAndValidate(t *testing.T) {
 | 
					func TestCompleteAndValidate(t *testing.T) {
 | 
				
			||||||
	tf := cmdtesting.NewTestFactory().WithNamespace("test")
 | 
						tf := cmdtesting.NewTestFactory().WithNamespace("test")
 | 
				
			||||||
	ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
 | 
						ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -431,6 +431,7 @@ const (
 | 
				
			|||||||
	OpenAPIV3Patch          FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
 | 
						OpenAPIV3Patch          FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
 | 
				
			||||||
	RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
 | 
						RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
 | 
				
			||||||
	PortForwardWebsockets   FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
 | 
						PortForwardWebsockets   FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
 | 
				
			||||||
 | 
						DebugCustomProfile      FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsEnabled returns true iff environment variable is set to true.
 | 
					// IsEnabled returns true iff environment variable is set to true.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user