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:
parent
2855ed117e
commit
14d23cc879
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
|
@ -106,29 +107,33 @@ var (
|
|||
|
||||
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.
|
||||
type DebugOptions struct {
|
||||
Args []string
|
||||
ArgsOnly bool
|
||||
Attach bool
|
||||
AttachFunc func(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, cmdPath string, ns, podName, containerName string) error
|
||||
Container string
|
||||
CopyTo string
|
||||
Replace bool
|
||||
Env []corev1.EnvVar
|
||||
Image string
|
||||
Interactive bool
|
||||
Namespace string
|
||||
TargetNames []string
|
||||
PullPolicy corev1.PullPolicy
|
||||
Quiet bool
|
||||
SameNode bool
|
||||
SetImages map[string]string
|
||||
ShareProcesses bool
|
||||
TargetContainer string
|
||||
TTY bool
|
||||
Profile string
|
||||
Applier ProfileApplier
|
||||
Args []string
|
||||
ArgsOnly bool
|
||||
Attach bool
|
||||
AttachFunc DebugAttachFunc
|
||||
Container string
|
||||
CopyTo string
|
||||
Replace bool
|
||||
Env []corev1.EnvVar
|
||||
Image string
|
||||
Interactive bool
|
||||
Namespace string
|
||||
TargetNames []string
|
||||
PullPolicy corev1.PullPolicy
|
||||
Quiet bool
|
||||
SameNode bool
|
||||
SetImages map[string]string
|
||||
ShareProcesses bool
|
||||
TargetContainer string
|
||||
TTY bool
|
||||
Profile string
|
||||
CustomProfileFile string
|
||||
CustomProfile *corev1.Container
|
||||
Applier ProfileApplier
|
||||
|
||||
explicitNamespace 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().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".`))
|
||||
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.
|
||||
|
@ -256,6 +264,18 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
|
|||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -348,6 +368,12 @@ func (o *DebugOptions) Validate() error {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -467,6 +493,90 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
|
|||
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
|
||||
func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
|
||||
copied, dc, err := o.generatePodCopyWithDebugContainer(pod)
|
||||
|
@ -515,6 +625,13 @@ func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *co
|
|||
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]
|
||||
|
||||
return copied, ec, nil
|
||||
|
@ -574,6 +691,13 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if o.CustomProfile != nil {
|
||||
err := o.applyCustomProfile(p, cn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
|
@ -656,6 +780,13 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
|
|||
return nil, "", err
|
||||
}
|
||||
|
||||
if o.CustomProfile != nil {
|
||||
err = o.applyCustomProfile(copied, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
return copied, name, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"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) {
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
|
||||
|
|
|
@ -431,6 +431,7 @@ const (
|
|||
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
|
||||
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
|
||||
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
|
||||
DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
|
||||
)
|
||||
|
||||
// IsEnabled returns true iff environment variable is set to true.
|
||||
|
|
Loading…
Reference in New Issue