Add custom debug profiles on top of static profiles

This PR adds `custom` flag to let user customizes debug resources.
`custom` flag accepts partial container spec in json format.

Kubernetes-commit: af2dadcb18a0411a0f9aa286c743801efdb81049
This commit is contained in:
Arda Güçlü 2023-09-01 11:46:01 +03:00 committed by Kubernetes Publisher
parent 2855ed117e
commit 14d23cc879
3 changed files with 838 additions and 21 deletions

View File

@ -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,29 +107,33 @@ 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
Env []corev1.EnvVar Env []corev1.EnvVar
Image string Image string
Interactive bool Interactive bool
Namespace string Namespace string
TargetNames []string TargetNames []string
PullPolicy corev1.PullPolicy PullPolicy corev1.PullPolicy
Quiet bool Quiet bool
SameNode bool SameNode bool
SetImages map[string]string SetImages map[string]string
ShareProcesses bool ShareProcesses bool
TargetContainer string TargetContainer string
TTY bool TTY bool
Profile string Profile string
Applier ProfileApplier CustomProfileFile string
CustomProfile *corev1.Container
Applier ProfileApplier
explicitNamespace bool explicitNamespace bool
attachChanged bool attachChanged 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
} }

View File

@ -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()

View File

@ -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.