Merge pull request #115712 from wedaly/kubectl-netadmin-debug-profile
kubectl debug: add netadmin profile Kubernetes-commit: 6a5c88b9dd0a0e9ef0115d1bbcb196284777832b
This commit is contained in:
commit
cd833457db
4
go.mod
4
go.mod
|
@ -34,7 +34,7 @@ require (
|
||||||
k8s.io/apimachinery v0.0.0-20230215101505-6ecd1c896902
|
k8s.io/apimachinery v0.0.0-20230215101505-6ecd1c896902
|
||||||
k8s.io/cli-runtime v0.0.0-20230215135918-242143802030
|
k8s.io/cli-runtime v0.0.0-20230215135918-242143802030
|
||||||
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336
|
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336
|
||||||
k8s.io/component-base v0.0.0-20230215115332-4fe47bde14b4
|
k8s.io/component-base v0.0.0-20230215215219-ae9be4dda9da
|
||||||
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0
|
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0
|
||||||
k8s.io/klog/v2 v2.80.1
|
k8s.io/klog/v2 v2.80.1
|
||||||
k8s.io/kube-openapi v0.0.0-20230123231816-1cb3ae25d79a
|
k8s.io/kube-openapi v0.0.0-20230123231816-1cb3ae25d79a
|
||||||
|
@ -96,7 +96,7 @@ replace (
|
||||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230215135918-242143802030
|
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230215135918-242143802030
|
||||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336
|
k8s.io/client-go => k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336
|
||||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230215100914-00ad370df34c
|
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230215100914-00ad370df34c
|
||||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20230215115332-4fe47bde14b4
|
k8s.io/component-base => k8s.io/component-base v0.0.0-20230215215219-ae9be4dda9da
|
||||||
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0
|
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0
|
||||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20230215135002-90fa97165491
|
k8s.io/metrics => k8s.io/metrics v0.0.0-20230215135002-90fa97165491
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -539,8 +539,8 @@ k8s.io/cli-runtime v0.0.0-20230215135918-242143802030 h1:uDL2wYsM4y/HSk/+5O2a5ll
|
||||||
k8s.io/cli-runtime v0.0.0-20230215135918-242143802030/go.mod h1:PNtFfT/bQo0WkCbO/7fm8s1YAHOy6fNHDE5ml5KLhgo=
|
k8s.io/cli-runtime v0.0.0-20230215135918-242143802030/go.mod h1:PNtFfT/bQo0WkCbO/7fm8s1YAHOy6fNHDE5ml5KLhgo=
|
||||||
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336 h1:nAZj89psja9QerxrE67b//wZ0rJxRRDucR9DdMkcAz0=
|
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336 h1:nAZj89psja9QerxrE67b//wZ0rJxRRDucR9DdMkcAz0=
|
||||||
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336/go.mod h1:PFH3ejtNZ3FtEUkfV47aYwAghy3M0a43SIngByKfW04=
|
k8s.io/client-go v0.0.0-20230215104727-60f5c9b46336/go.mod h1:PFH3ejtNZ3FtEUkfV47aYwAghy3M0a43SIngByKfW04=
|
||||||
k8s.io/component-base v0.0.0-20230215115332-4fe47bde14b4 h1:HRpqqsHqGgSfAeSwsmozKo6mhWJ0p2JI36vao2er+fE=
|
k8s.io/component-base v0.0.0-20230215215219-ae9be4dda9da h1:tgXu2y67YqUXP4txgy1vMTvOOqpT/kzIxmrtE0SvR8M=
|
||||||
k8s.io/component-base v0.0.0-20230215115332-4fe47bde14b4/go.mod h1:GgrhCRrPLmN9lWoh+m52a6iHKG3zx8KJyclgqPgbFDQ=
|
k8s.io/component-base v0.0.0-20230215215219-ae9be4dda9da/go.mod h1:GgrhCRrPLmN9lWoh+m52a6iHKG3zx8KJyclgqPgbFDQ=
|
||||||
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0 h1:2lV0FlxzZ3gJgDcpiJz54cZgDxHjRn8u9xwoSxkYtD4=
|
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0 h1:2lV0FlxzZ3gJgDcpiJz54cZgDxHjRn8u9xwoSxkYtD4=
|
||||||
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0/go.mod h1:iADql9qALcuDPa5sGm2MRHnCQA5DGvV+LIPanigEbyo=
|
k8s.io/component-helpers v0.0.0-20230215120307-78f7b9c080f0/go.mod h1:iADql9qALcuDPa5sGm2MRHnCQA5DGvV+LIPanigEbyo=
|
||||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||||
|
|
|
@ -190,7 +190,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().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().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(`Debugging profile. Options are "legacy", "general", "baseline", or "restricted".`))
|
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Debugging profile. Options are "legacy", "general", "baseline", "netadmin", or "restricted".`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete finishes run-time initialization of debug.DebugOptions.
|
// Complete finishes run-time initialization of debug.DebugOptions.
|
||||||
|
|
|
@ -294,6 +294,27 @@ func TestGenerateDebugContainer(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "netadmin profile",
|
||||||
|
opts: &DebugOptions{
|
||||||
|
Image: "busybox",
|
||||||
|
PullPolicy: corev1.PullIfNotPresent,
|
||||||
|
Profile: ProfileNetadmin,
|
||||||
|
},
|
||||||
|
expected: &corev1.EphemeralContainer{
|
||||||
|
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||||
|
Name: "debugger-1",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||||
|
SecurityContext: &corev1.SecurityContext{
|
||||||
|
Capabilities: &corev1.Capabilities{
|
||||||
|
Add: []corev1.Capability{"NET_ADMIN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard()
|
tc.opts.IOStreams = genericclioptions.NewTestIOStreamsDiscard()
|
||||||
|
|
|
@ -52,6 +52,8 @@ const (
|
||||||
// ProfileRestricted is identical to "baseline" but adds configuration that's required
|
// ProfileRestricted is identical to "baseline" but adds configuration that's required
|
||||||
// under the restricted security profile, such as requiring a non-root user and dropping all capabilities.
|
// under the restricted security profile, such as requiring a non-root user and dropping all capabilities.
|
||||||
ProfileRestricted = "restricted"
|
ProfileRestricted = "restricted"
|
||||||
|
// ProfileNetadmin offers elevated privileges for network debugging.
|
||||||
|
ProfileNetadmin = "netadmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProfileApplier interface {
|
type ProfileApplier interface {
|
||||||
|
@ -70,6 +72,8 @@ func NewProfileApplier(profile string) (ProfileApplier, error) {
|
||||||
return &baselineProfile{}, nil
|
return &baselineProfile{}, nil
|
||||||
case ProfileRestricted:
|
case ProfileRestricted:
|
||||||
return &restrictedProfile{}, nil
|
return &restrictedProfile{}, nil
|
||||||
|
case ProfileNetadmin:
|
||||||
|
return &netadminProfile{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown profile: %s", profile)
|
return nil, fmt.Errorf("unknown profile: %s", profile)
|
||||||
|
@ -87,6 +91,9 @@ type baselineProfile struct {
|
||||||
type restrictedProfile struct {
|
type restrictedProfile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type netadminProfile struct {
|
||||||
|
}
|
||||||
|
|
||||||
func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||||
switch target.(type) {
|
switch target.(type) {
|
||||||
case *corev1.Pod:
|
case *corev1.Pod:
|
||||||
|
@ -183,6 +190,26 @@ func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||||
|
style, err := getDebugStyle(pod, target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("netadmin profile: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allowNetadminCapability(pod, containerName)
|
||||||
|
|
||||||
|
switch style {
|
||||||
|
case node:
|
||||||
|
useHostNamespaces(pod)
|
||||||
|
setPrivileged(pod, containerName)
|
||||||
|
|
||||||
|
case podCopy, ephemeral:
|
||||||
|
// no additional modifications needed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// removeLabelsAndProbes removes labels from the pod and remove probes
|
// removeLabelsAndProbes removes labels from the pod and remove probes
|
||||||
// from all containers of the pod.
|
// from all containers of the pod.
|
||||||
func removeLabelsAndProbes(p *corev1.Pod) {
|
func removeLabelsAndProbes(p *corev1.Pod) {
|
||||||
|
@ -239,6 +266,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.BoolPtr(true)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// disallowRoot configures the container to run as a non-root user.
|
// disallowRoot configures the container to run as a non-root user.
|
||||||
func disallowRoot(p *corev1.Pod, containerName string) {
|
func disallowRoot(p *corev1.Pod, containerName string) {
|
||||||
podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
|
podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
|
||||||
|
@ -274,13 +315,28 @@ func allowProcessTracing(p *corev1.Pod, containerName string) {
|
||||||
if c.Name != containerName {
|
if c.Name != containerName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if c.SecurityContext == nil {
|
addCapability(c, "SYS_PTRACE")
|
||||||
c.SecurityContext = &corev1.SecurityContext{}
|
|
||||||
}
|
|
||||||
if c.SecurityContext.Capabilities == nil {
|
|
||||||
c.SecurityContext.Capabilities = &corev1.Capabilities{}
|
|
||||||
}
|
|
||||||
c.SecurityContext.Capabilities.Add = append(c.SecurityContext.Capabilities.Add, "SYS_PTRACE")
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowNetadminCapability grants NET_ADMIN capability to the container.
|
||||||
|
func allowNetadminCapability(p *corev1.Pod, containerName string) {
|
||||||
|
podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
|
||||||
|
if c.Name != containerName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
addCapability(c, "NET_ADMIN")
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCapability(c *corev1.Container, capability corev1.Capability) {
|
||||||
|
if c.SecurityContext == nil {
|
||||||
|
c.SecurityContext = &corev1.SecurityContext{}
|
||||||
|
}
|
||||||
|
if c.SecurityContext.Capabilities == nil {
|
||||||
|
c.SecurityContext.Capabilities = &corev1.Capabilities{}
|
||||||
|
}
|
||||||
|
c.SecurityContext.Capabilities.Add = append(c.SecurityContext.Capabilities.Add, capability)
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package debug
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
@ -430,3 +431,140 @@ func TestRestrictedProfile(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNetAdminProfile(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("netadmin 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{
|
||||||
|
Capabilities: &corev1.Capabilities{
|
||||||
|
Add: []corev1.Capability{"NET_ADMIN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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{
|
||||||
|
Capabilities: &corev1.Capabilities{
|
||||||
|
Add: []corev1.Capability{"NET_ADMIN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "dbg",
|
||||||
|
Image: "dbgimage",
|
||||||
|
SecurityContext: &corev1.SecurityContext{
|
||||||
|
Privileged: pointer.BoolPtr(true),
|
||||||
|
Capabilities: &corev1.Capabilities{
|
||||||
|
Add: []corev1.Capability{"NET_ADMIN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := (&netadminProfile{}).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