Merge pull request #119200 from eiffel-fl/francis/sysadmin-debug-profile

kubectl debug: add sysadmin profile

Kubernetes-commit: 4163ce5017268b0ae25df327f0a210032ef1cc80
This commit is contained in:
Kubernetes Publisher 2024-01-25 10:12:39 +01:00
commit b73518af09
6 changed files with 307 additions and 10 deletions

10
go.mod
View File

@ -30,10 +30,10 @@ require (
github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.15.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.0.0-20240118211853-d5724e467262
k8s.io/api v0.0.0-20240124211858-f3648a53522e
k8s.io/apimachinery v0.0.0-20240118211638-f14778da5523
k8s.io/cli-runtime v0.0.0-20240118214801-ad54ff319bf2
k8s.io/client-go v0.0.0-20240122172058-657d7be98b25
k8s.io/client-go v0.0.0-20240124011219-8092c71d3605
k8s.io/component-base v0.0.0-20240123212339-5f9f8131aa48
k8s.io/component-helpers v0.0.0-20240118212950-9a5801419916
k8s.io/klog/v2 v2.120.1
@ -96,11 +96,11 @@ require (
)
replace (
k8s.io/api => k8s.io/api v0.0.0-20240118211853-d5724e467262
k8s.io/api => k8s.io/api v0.0.0-20240124211858-f3648a53522e
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20240118211638-f14778da5523
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20240118214801-ad54ff319bf2
k8s.io/client-go => k8s.io/client-go v0.0.0-20240122172058-657d7be98b25
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20240118211431-5ad9f43b6468
k8s.io/client-go => k8s.io/client-go v0.0.0-20240124011219-8092c71d3605
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20240123225209-c781f8765cf8
k8s.io/component-base => k8s.io/component-base v0.0.0-20240123212339-5f9f8131aa48
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20240118212950-9a5801419916
k8s.io/metrics => k8s.io/metrics v0.0.0-20240118214633-5b4611d6f391

8
go.sum
View File

@ -280,14 +280,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20240118211853-d5724e467262 h1:iP5kk4e+c89xvFLRWdUb7BAYzBV9A//0vBHc9Ob/NUM=
k8s.io/api v0.0.0-20240118211853-d5724e467262/go.mod h1:BZUGwl6J5EvsODp+6ZUA+9p7V4iWxVLcr70rnzIshpA=
k8s.io/api v0.0.0-20240124211858-f3648a53522e h1:Lv52wennNKzlcDrBtANztawHC8xaTllHm51WUKIP0Ew=
k8s.io/api v0.0.0-20240124211858-f3648a53522e/go.mod h1:BZUGwl6J5EvsODp+6ZUA+9p7V4iWxVLcr70rnzIshpA=
k8s.io/apimachinery v0.0.0-20240118211638-f14778da5523 h1:1iJCbQAZv58v4zxd0ECIIMnyYlFsPWa2hmjqGEsv/5g=
k8s.io/apimachinery v0.0.0-20240118211638-f14778da5523/go.mod h1:Oh3ZrffM1/I8O/43oAA+aoOYgSregIXHxcWJB9ZRfQ8=
k8s.io/cli-runtime v0.0.0-20240118214801-ad54ff319bf2 h1:KFiGqjF1kq6YDXgsWmsms2bu10g1M17P9HRO2lGYry4=
k8s.io/cli-runtime v0.0.0-20240118214801-ad54ff319bf2/go.mod h1:zmPMirb3vXLcTGRaL9Pw5SifCP8EY2asTp+PTgnwSYI=
k8s.io/client-go v0.0.0-20240122172058-657d7be98b25 h1:iBiouUazhDUHxrqDywgcbmARvao6UwVu+nSbIrIRh4k=
k8s.io/client-go v0.0.0-20240122172058-657d7be98b25/go.mod h1:WuuT9L6+pj4rHmL2pb22xnOdtSvjiEcpB18g9Fuk0js=
k8s.io/client-go v0.0.0-20240124011219-8092c71d3605 h1:Dw3Ctw+SS3YmJTpaYP2nhIs4XagL4ctjKY0pHxN4RT8=
k8s.io/client-go v0.0.0-20240124011219-8092c71d3605/go.mod h1:WuuT9L6+pj4rHmL2pb22xnOdtSvjiEcpB18g9Fuk0js=
k8s.io/component-base v0.0.0-20240123212339-5f9f8131aa48 h1:3HvTUZ0ry5c0P15P+glBxBj+eh8Uv2ijNvjEORH+oOQ=
k8s.io/component-base v0.0.0-20240123212339-5f9f8131aa48/go.mod h1:ANnr9YwsqK1XgjzXj9fGHEMDOp0QddDkKgQLLBPZ7Kg=
k8s.io/component-helpers v0.0.0-20240118212950-9a5801419916 h1:Ptl0rZGRIrjdZuSqSO8Dwok1SWA9bqAi/Vcc3HYA/Ks=

View File

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

View File

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

View File

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

View File

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