mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
kubectl debug: add sysadmin profile
Add the sysadmin profile from KEP 1441 [1]. Signed-off-by: Francis Laniel <flaniel@linux.microsoft.com> [1]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/1441-kubectl-debug#debugging-profiles
This commit is contained in:
parent
2f270bd996
commit
4d6c0ba518
@ -192,7 +192,7 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVar(&o.ShareProcesses, "share-processes", o.ShareProcesses, i18n.T("When used with '--copy-to', enable process namespace sharing in the copy."))
|
||||
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(`Debugging profile. Options are "legacy", "general", "baseline", "netadmin", or "restricted".`))
|
||||
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
|
||||
}
|
||||
|
||||
// Complete finishes run-time initialization of debug.DebugOptions.
|
||||
|
@ -316,6 +316,25 @@ func TestGenerateDebugContainer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sysadmin profile",
|
||||
opts: &DebugOptions{
|
||||
Image: "busybox",
|
||||
PullPolicy: corev1.PullIfNotPresent,
|
||||
Profile: ProfileSysadmin,
|
||||
},
|
||||
expected: &corev1.EphemeralContainer{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "debugger-1",
|
||||
Image: "busybox",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
|
@ -54,6 +54,8 @@ const (
|
||||
ProfileRestricted = "restricted"
|
||||
// ProfileNetadmin offers elevated privileges for network debugging.
|
||||
ProfileNetadmin = "netadmin"
|
||||
// ProfileSysadmin offers elevated privileges for debugging.
|
||||
ProfileSysadmin = "sysadmin"
|
||||
)
|
||||
|
||||
type ProfileApplier interface {
|
||||
@ -74,6 +76,8 @@ func NewProfileApplier(profile string) (ProfileApplier, error) {
|
||||
return &restrictedProfile{}, nil
|
||||
case ProfileNetadmin:
|
||||
return &netadminProfile{}, nil
|
||||
case ProfileSysadmin:
|
||||
return &sysadminProfile{}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown profile: %s", profile)
|
||||
@ -94,6 +98,9 @@ type restrictedProfile struct {
|
||||
type netadminProfile struct {
|
||||
}
|
||||
|
||||
type sysadminProfile struct {
|
||||
}
|
||||
|
||||
func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
switch target.(type) {
|
||||
case *corev1.Pod:
|
||||
@ -212,6 +219,29 @@ func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sysadmin profile: %s", err)
|
||||
}
|
||||
|
||||
setPrivileged(pod, containerName)
|
||||
|
||||
switch style {
|
||||
case node:
|
||||
useHostNamespaces(pod)
|
||||
mountRootPartition(pod, containerName)
|
||||
|
||||
case podCopy:
|
||||
// to mimic general, default and baseline
|
||||
shareProcessNamespace(pod)
|
||||
case ephemeral:
|
||||
// no additional modifications needed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeLabelsAndProbes removes labels from the pod and remove probes
|
||||
// from all containers of the pod.
|
||||
func removeLabelsAndProbes(p *corev1.Pod) {
|
||||
@ -271,6 +301,20 @@ func clearSecurityContext(p *corev1.Pod, containerName string) {
|
||||
})
|
||||
}
|
||||
|
||||
// setPrivileged configures the containers as privileged.
|
||||
func setPrivileged(p *corev1.Pod, containerName string) {
|
||||
podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
|
||||
if c.Name != containerName {
|
||||
return true
|
||||
}
|
||||
if c.SecurityContext == nil {
|
||||
c.SecurityContext = &corev1.SecurityContext{}
|
||||
}
|
||||
c.SecurityContext.Privileged = pointer.Bool(true)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// disallowRoot configures the container to run as a non-root user.
|
||||
func disallowRoot(p *corev1.Pod, containerName string) {
|
||||
podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
|
||||
|
@ -678,3 +678,237 @@ func TestNetAdminProfile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysAdminProfile(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "dbg", Image: "dbgimage",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *corev1.Pod
|
||||
containerName string
|
||||
target runtime.Object
|
||||
expectPod *corev1.Pod
|
||||
expectErr error
|
||||
}{
|
||||
{
|
||||
name: "nil target",
|
||||
pod: pod,
|
||||
containerName: "dbg",
|
||||
target: nil,
|
||||
expectErr: fmt.Errorf("sysadmin profile: objects of type <nil> are not supported"),
|
||||
},
|
||||
{
|
||||
name: "debug by ephemeral container",
|
||||
pod: pod,
|
||||
containerName: "dbg",
|
||||
target: pod,
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "dbg", Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debug by pod copy",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debug by pod copy preserve existing capability",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debug by node",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: testNode,
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
HostPID: true,
|
||||
HostIPC: true,
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "host-root",
|
||||
VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/"}},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{Name: "host-root", MountPath: "/host"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debug by node preserve existing capability",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: testNode,
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
HostPID: true,
|
||||
HostIPC: true,
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "host-root",
|
||||
VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/"}},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{Name: "host-root", MountPath: "/host"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := (&sysadminProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
if (err == nil) != (test.expectErr == nil) || (err != nil && test.expectErr != nil && err.Error() != test.expectErr.Error()) {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(test.expectPod, test.pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user