...

Source file src/k8s.io/kubectl/pkg/cmd/debug/profiles.go

Documentation: k8s.io/kubectl/pkg/cmd/debug

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package debug
    18  
    19  import (
    20  	"fmt"
    21  
    22  	corev1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/kubectl/pkg/util/podutils"
    25  	"k8s.io/utils/pointer"
    26  )
    27  
    28  type debugStyle int
    29  
    30  const (
    31  	// debug by ephemeral container
    32  	ephemeral debugStyle = iota
    33  	// debug by pod copy
    34  	podCopy
    35  	// debug node
    36  	node
    37  	// unsupported debug methodology
    38  	unsupported
    39  )
    40  
    41  const (
    42  	// NOTE: when you add a new profile string, remember to add it to the
    43  	// --profile flag's help text
    44  
    45  	// ProfileLegacy represents the legacy debugging profile which is backwards-compatible with 1.23 behavior.
    46  	ProfileLegacy = "legacy"
    47  	// ProfileGeneral contains a reasonable set of defaults tailored for each debugging journey.
    48  	ProfileGeneral = "general"
    49  	// ProfileBaseline is identical to "general" but eliminates privileges that are disallowed under
    50  	// the baseline security profile, such as host namespaces, host volume, mounts and SYS_PTRACE.
    51  	ProfileBaseline = "baseline"
    52  	// ProfileRestricted is identical to "baseline" but adds configuration that's required
    53  	// under the restricted security profile, such as requiring a non-root user and dropping all capabilities.
    54  	ProfileRestricted = "restricted"
    55  	// ProfileNetadmin offers elevated privileges for network debugging.
    56  	ProfileNetadmin = "netadmin"
    57  	// ProfileSysadmin offers elevated privileges for debugging.
    58  	ProfileSysadmin = "sysadmin"
    59  )
    60  
    61  type ProfileApplier interface {
    62  	// Apply applies the profile to the given container in the pod.
    63  	Apply(pod *corev1.Pod, containerName string, target runtime.Object) error
    64  }
    65  
    66  // NewProfileApplier returns a new Options for the given profile name.
    67  func NewProfileApplier(profile string) (ProfileApplier, error) {
    68  	switch profile {
    69  	case ProfileLegacy:
    70  		return &legacyProfile{}, nil
    71  	case ProfileGeneral:
    72  		return &generalProfile{}, nil
    73  	case ProfileBaseline:
    74  		return &baselineProfile{}, nil
    75  	case ProfileRestricted:
    76  		return &restrictedProfile{}, nil
    77  	case ProfileNetadmin:
    78  		return &netadminProfile{}, nil
    79  	case ProfileSysadmin:
    80  		return &sysadminProfile{}, nil
    81  	}
    82  
    83  	return nil, fmt.Errorf("unknown profile: %s", profile)
    84  }
    85  
    86  type legacyProfile struct {
    87  }
    88  
    89  type generalProfile struct {
    90  }
    91  
    92  type baselineProfile struct {
    93  }
    94  
    95  type restrictedProfile struct {
    96  }
    97  
    98  type netadminProfile struct {
    99  }
   100  
   101  type sysadminProfile struct {
   102  }
   103  
   104  func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   105  	switch target.(type) {
   106  	case *corev1.Pod:
   107  		// do nothing to the copied pod
   108  		return nil
   109  	case *corev1.Node:
   110  		mountRootPartition(pod, containerName)
   111  		useHostNamespaces(pod)
   112  		return nil
   113  	default:
   114  		return fmt.Errorf("the %s profile doesn't support objects of type %T", ProfileLegacy, target)
   115  	}
   116  }
   117  
   118  func getDebugStyle(pod *corev1.Pod, target runtime.Object) (debugStyle, error) {
   119  	switch target.(type) {
   120  	case *corev1.Pod:
   121  		if asserted, ok := target.(*corev1.Pod); ok {
   122  			if pod != asserted { // comparing addresses
   123  				return podCopy, nil
   124  			}
   125  		}
   126  		return ephemeral, nil
   127  	case *corev1.Node:
   128  		return node, nil
   129  	}
   130  	return unsupported, fmt.Errorf("objects of type %T are not supported", target)
   131  }
   132  
   133  func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   134  	style, err := getDebugStyle(pod, target)
   135  	if err != nil {
   136  		return fmt.Errorf("general profile: %s", err)
   137  	}
   138  
   139  	switch style {
   140  	case node:
   141  		mountRootPartition(pod, containerName)
   142  		clearSecurityContext(pod, containerName)
   143  		useHostNamespaces(pod)
   144  
   145  	case podCopy:
   146  		removeLabelsAndProbes(pod)
   147  		allowProcessTracing(pod, containerName)
   148  		shareProcessNamespace(pod)
   149  
   150  	case ephemeral:
   151  		allowProcessTracing(pod, containerName)
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  func (p *baselineProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   158  	style, err := getDebugStyle(pod, target)
   159  	if err != nil {
   160  		return fmt.Errorf("baseline profile: %s", err)
   161  	}
   162  
   163  	clearSecurityContext(pod, containerName)
   164  
   165  	switch style {
   166  	case podCopy:
   167  		removeLabelsAndProbes(pod)
   168  		shareProcessNamespace(pod)
   169  
   170  	case ephemeral, node:
   171  		// no additional modifications needed
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   178  	style, err := getDebugStyle(pod, target)
   179  	if err != nil {
   180  		return fmt.Errorf("restricted profile: %s", err)
   181  	}
   182  
   183  	clearSecurityContext(pod, containerName)
   184  	disallowRoot(pod, containerName)
   185  	dropCapabilities(pod, containerName)
   186  	disallowPrivilegeEscalation(pod, containerName)
   187  	setSeccompProfile(pod, containerName)
   188  
   189  	switch style {
   190  	case podCopy:
   191  		shareProcessNamespace(pod)
   192  
   193  	case ephemeral, node:
   194  		// no additional modifications needed
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   201  	style, err := getDebugStyle(pod, target)
   202  	if err != nil {
   203  		return fmt.Errorf("netadmin profile: %s", err)
   204  	}
   205  
   206  	allowNetadminCapability(pod, containerName)
   207  
   208  	switch style {
   209  	case node:
   210  		useHostNamespaces(pod)
   211  
   212  	case podCopy:
   213  		shareProcessNamespace(pod)
   214  
   215  	case ephemeral:
   216  		// no additional modifications needed
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
   223  	style, err := getDebugStyle(pod, target)
   224  	if err != nil {
   225  		return fmt.Errorf("sysadmin profile: %s", err)
   226  	}
   227  
   228  	setPrivileged(pod, containerName)
   229  
   230  	switch style {
   231  	case node:
   232  		useHostNamespaces(pod)
   233  		mountRootPartition(pod, containerName)
   234  
   235  	case podCopy:
   236  		// to mimic general, default and baseline
   237  		shareProcessNamespace(pod)
   238  	case ephemeral:
   239  		// no additional modifications needed
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // removeLabelsAndProbes removes labels from the pod and remove probes
   246  // from all containers of the pod.
   247  func removeLabelsAndProbes(p *corev1.Pod) {
   248  	p.Labels = nil
   249  	for i := range p.Spec.Containers {
   250  		p.Spec.Containers[i].LivenessProbe = nil
   251  		p.Spec.Containers[i].ReadinessProbe = nil
   252  		p.Spec.Containers[i].StartupProbe = nil
   253  	}
   254  }
   255  
   256  // mountRootPartition mounts the host's root path at "/host" in the container.
   257  func mountRootPartition(p *corev1.Pod, containerName string) {
   258  	const volumeName = "host-root"
   259  	p.Spec.Volumes = append(p.Spec.Volumes, corev1.Volume{
   260  		Name: volumeName,
   261  		VolumeSource: corev1.VolumeSource{
   262  			HostPath: &corev1.HostPathVolumeSource{Path: "/"},
   263  		},
   264  	})
   265  	podutils.VisitContainers(&p.Spec, podutils.Containers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   266  		if c.Name != containerName {
   267  			return true
   268  		}
   269  		c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
   270  			MountPath: "/host",
   271  			Name:      volumeName,
   272  		})
   273  		return false
   274  	})
   275  }
   276  
   277  // useHostNamespaces configures the pod to use the host's network, PID, and IPC
   278  // namespaces.
   279  func useHostNamespaces(p *corev1.Pod) {
   280  	p.Spec.HostNetwork = true
   281  	p.Spec.HostPID = true
   282  	p.Spec.HostIPC = true
   283  }
   284  
   285  // shareProcessNamespace configures all containers in the pod to share the
   286  // process namespace.
   287  func shareProcessNamespace(p *corev1.Pod) {
   288  	if p.Spec.ShareProcessNamespace == nil {
   289  		p.Spec.ShareProcessNamespace = pointer.Bool(true)
   290  	}
   291  }
   292  
   293  // clearSecurityContext clears the security context for the container.
   294  func clearSecurityContext(p *corev1.Pod, containerName string) {
   295  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   296  		if c.Name != containerName {
   297  			return true
   298  		}
   299  		c.SecurityContext = nil
   300  		return false
   301  	})
   302  }
   303  
   304  // setPrivileged configures the containers as privileged.
   305  func setPrivileged(p *corev1.Pod, containerName string) {
   306  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   307  		if c.Name != containerName {
   308  			return true
   309  		}
   310  		if c.SecurityContext == nil {
   311  			c.SecurityContext = &corev1.SecurityContext{}
   312  		}
   313  		c.SecurityContext.Privileged = pointer.Bool(true)
   314  		return false
   315  	})
   316  }
   317  
   318  // disallowRoot configures the container to run as a non-root user.
   319  func disallowRoot(p *corev1.Pod, containerName string) {
   320  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   321  		if c.Name != containerName {
   322  			return true
   323  		}
   324  		if c.SecurityContext == nil {
   325  			c.SecurityContext = &corev1.SecurityContext{}
   326  		}
   327  		c.SecurityContext.RunAsNonRoot = pointer.Bool(true)
   328  		return false
   329  	})
   330  }
   331  
   332  // dropCapabilities drops all Capabilities for the container
   333  func dropCapabilities(p *corev1.Pod, containerName string) {
   334  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   335  		if c.Name != containerName {
   336  			return true
   337  		}
   338  		if c.SecurityContext == nil {
   339  			c.SecurityContext = &corev1.SecurityContext{}
   340  		}
   341  		if c.SecurityContext.Capabilities == nil {
   342  			c.SecurityContext.Capabilities = &corev1.Capabilities{}
   343  		}
   344  		c.SecurityContext.Capabilities.Drop = []corev1.Capability{"ALL"}
   345  		c.SecurityContext.Capabilities.Add = nil
   346  		return false
   347  	})
   348  }
   349  
   350  // allowProcessTracing grants the SYS_PTRACE capability to the container.
   351  func allowProcessTracing(p *corev1.Pod, containerName string) {
   352  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   353  		if c.Name != containerName {
   354  			return true
   355  		}
   356  		addCapability(c, "SYS_PTRACE")
   357  		return false
   358  	})
   359  }
   360  
   361  // allowNetadminCapability grants NET_ADMIN and NET_RAW capability to the container.
   362  func allowNetadminCapability(p *corev1.Pod, containerName string) {
   363  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   364  		if c.Name != containerName {
   365  			return true
   366  		}
   367  		addCapability(c, "NET_ADMIN")
   368  		addCapability(c, "NET_RAW")
   369  		return false
   370  	})
   371  }
   372  
   373  func addCapability(c *corev1.Container, capability corev1.Capability) {
   374  	if c.SecurityContext == nil {
   375  		c.SecurityContext = &corev1.SecurityContext{}
   376  	}
   377  	if c.SecurityContext.Capabilities == nil {
   378  		c.SecurityContext.Capabilities = &corev1.Capabilities{}
   379  	}
   380  	c.SecurityContext.Capabilities.Add = append(c.SecurityContext.Capabilities.Add, capability)
   381  }
   382  
   383  // disallowPrivilegeEscalation configures the containers not allowed PrivilegeEscalation
   384  func disallowPrivilegeEscalation(p *corev1.Pod, containerName string) {
   385  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   386  		if c.Name != containerName {
   387  			return true
   388  		}
   389  		if c.SecurityContext == nil {
   390  			c.SecurityContext = &corev1.SecurityContext{}
   391  		}
   392  		c.SecurityContext.AllowPrivilegeEscalation = pointer.Bool(false)
   393  		return false
   394  	})
   395  }
   396  
   397  // setSeccompProfile apply SeccompProfile to the containers
   398  func setSeccompProfile(p *corev1.Pod, containerName string) {
   399  	podutils.VisitContainers(&p.Spec, podutils.AllContainers, func(c *corev1.Container, _ podutils.ContainerType) bool {
   400  		if c.Name != containerName {
   401  			return true
   402  		}
   403  		if c.SecurityContext == nil {
   404  			c.SecurityContext = &corev1.SecurityContext{}
   405  		}
   406  		c.SecurityContext.SeccompProfile = &corev1.SeccompProfile{Type: "RuntimeDefault"}
   407  		return false
   408  	})
   409  }
   410  

View as plain text