Ctrl K
ComunidadGuías…

pkg/k8score/ephemeral.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"context"
	"fmt"
	"strings"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

// DefaultDebugImage is the default image for ephemeral debug containers.
const DefaultDebugImage = "busybox:latest"

const defaultDebugRunAsUser int64 = 65532

// EphemeralContainerOptions configures debug container creation.
type EphemeralContainerOptions struct {
	Namespace       string
	PodName         string
	TargetContainer string // Container to share process namespace with
	Image           string // Debug image (default: busybox:latest)
	ContainerName   string // Name for ephemeral container (auto-generated if empty)
}

// CreateEphemeralContainer adds an ephemeral debug container to a pod.
func CreateEphemeralContainer(ctx context.Context, client kubernetes.Interface, opts EphemeralContainerOptions) (*corev1.EphemeralContainer, error) {
	if client == nil {
		return nil, fmt.Errorf("kubernetes client not initialized")
	}
	if opts.Image == "" {
		opts.Image = DefaultDebugImage
	}
	if opts.ContainerName == "" {
		opts.ContainerName = fmt.Sprintf("debug-%d", time.Now().Unix())
	}

	pod, err := client.CoreV1().Pods(opts.Namespace).Get(ctx, opts.PodName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to get pod: %w", err)
	}

	ec := newEphemeralContainer(opts, nil)
	updateErr := updateEphemeralContainer(ctx, client, pod, ec)
	if updateErr != nil && isRestrictedPodSecurityError(updateErr) {
		pod, err = client.CoreV1().Pods(opts.Namespace).Get(ctx, opts.PodName, metav1.GetOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to get pod: %w", err)
		}
		securityContext := debugContainerSecurityContext(pod, opts.TargetContainer)
		if securityContext != nil {
			ec = newEphemeralContainer(opts, securityContext)
			updateErr = updateEphemeralContainer(ctx, client, pod, ec)
		}
	}
	if updateErr != nil {
		return nil, fmt.Errorf("failed to create ephemeral container: %w", updateErr)
	}

	return &ec, nil
}

func newEphemeralContainer(opts EphemeralContainerOptions, securityContext *corev1.SecurityContext) corev1.EphemeralContainer {
	return corev1.EphemeralContainer{
		EphemeralContainerCommon: corev1.EphemeralContainerCommon{
			Name:                     opts.ContainerName,
			Image:                    opts.Image,
			ImagePullPolicy:          corev1.PullIfNotPresent,
			Stdin:                    true,
			TTY:                      true,
			TerminationMessagePolicy: corev1.TerminationMessageReadFile,
			SecurityContext:          securityContext,
		},
		TargetContainerName: opts.TargetContainer,
	}
}

func updateEphemeralContainer(ctx context.Context, client kubernetes.Interface, pod *corev1.Pod, ec corev1.EphemeralContainer) error {
	updated := pod.DeepCopy()
	updated.Spec.EphemeralContainers = append(updated.Spec.EphemeralContainers, ec)
	_, err := client.CoreV1().Pods(updated.Namespace).UpdateEphemeralContainers(
		ctx,
		updated.Name,
		updated,
		metav1.UpdateOptions{},
	)
	return err
}

func isRestrictedPodSecurityError(err error) bool {
	if err == nil {
		return false
	}
	msg := err.Error()
	return strings.Contains(msg, "violates PodSecurity") && strings.Contains(msg, "restricted")
}

func debugContainerSecurityContext(pod *corev1.Pod, targetContainer string) *corev1.SecurityContext {
	if pod != nil && pod.Spec.OS != nil && pod.Spec.OS.Name == corev1.Windows {
		return nil
	}

	runAsUser := debugRunAsUser(pod, targetContainer)
	runAsGroup := debugRunAsGroup(pod, targetContainer)
	return &corev1.SecurityContext{
		AllowPrivilegeEscalation: boolPtr(false),
		Capabilities: &corev1.Capabilities{
			Drop: []corev1.Capability{"ALL"},
		},
		RunAsNonRoot: boolPtr(true),
		RunAsGroup:   runAsGroup,
		RunAsUser:    runAsUser,
		SeccompProfile: &corev1.SeccompProfile{
			Type: corev1.SeccompProfileTypeRuntimeDefault,
		},
	}
}

// targetSecurityContext finds the target among regular containers and init
// containers, since a native sidecar (an init container with restartPolicy
// Always) can be a debug target too.
func targetSecurityContext(pod *corev1.Pod, targetContainer string) *corev1.SecurityContext {
	for _, list := range [][]corev1.Container{pod.Spec.Containers, pod.Spec.InitContainers} {
		for i := range list {
			if list[i].Name == targetContainer {
				return list[i].SecurityContext
			}
		}
	}
	return nil
}

func debugRunAsUser(pod *corev1.Pod, targetContainer string) *int64 {
	if pod != nil {
		if sc := targetSecurityContext(pod, targetContainer); sc != nil && sc.RunAsUser != nil && *sc.RunAsUser != 0 {
			runAsUser := *sc.RunAsUser
			return &runAsUser
		}
		if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.RunAsUser != nil && *pod.Spec.SecurityContext.RunAsUser != 0 {
			runAsUser := *pod.Spec.SecurityContext.RunAsUser
			return &runAsUser
		}
	}
	runAsUser := defaultDebugRunAsUser
	return &runAsUser
}

func debugRunAsGroup(pod *corev1.Pod, targetContainer string) *int64 {
	if pod != nil {
		if sc := targetSecurityContext(pod, targetContainer); sc != nil && sc.RunAsGroup != nil && *sc.RunAsGroup != 0 {
			runAsGroup := *sc.RunAsGroup
			return &runAsGroup
		}
		if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.RunAsGroup != nil && *pod.Spec.SecurityContext.RunAsGroup != 0 {
			runAsGroup := *pod.Spec.SecurityContext.RunAsGroup
			return &runAsGroup
		}
	}
	return nil
}

// WaitForEphemeralContainer polls until an ephemeral container reaches Running state or timeout.
func WaitForEphemeralContainer(ctx context.Context, client kubernetes.Interface, namespace, podName, containerName string, timeout time.Duration) error {
	if client == nil {
		return fmt.Errorf("kubernetes client not initialized")
	}
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		pod, err := client.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("failed to get pod: %w", err)
		}

		for _, status := range pod.Status.EphemeralContainerStatuses {
			if status.Name == containerName {
				if status.State.Running != nil {
					return nil
				}
				if status.State.Terminated != nil {
					return fmt.Errorf("container terminated: %s", status.State.Terminated.Reason)
				}
			}
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(500 * time.Millisecond):
			continue
		}
	}

	return fmt.Errorf("timeout waiting for ephemeral container to start")
}