Ctrl K
ComunidadGuías…

pkg/k8score/ephemeral_test.go

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

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"context"
	"fmt"
	"reflect"
	"testing"

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes/fake"
	clienttesting "k8s.io/client-go/testing"
)

func TestCreateEphemeralContainerDefaultsToGeneralSecurityContext(t *testing.T) {
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
	})

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "app",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}
	if ec.SecurityContext != nil {
		t.Fatalf("returned security context = %#v, want nil", ec.SecurityContext)
	}

	updated := updatedPodFromEphemeralAction(t, client.Actions())
	if len(updated.Spec.EphemeralContainers) != 1 {
		t.Fatalf("updated pod has %d ephemeral containers, want 1", len(updated.Spec.EphemeralContainers))
	}
	got := updated.Spec.EphemeralContainers[0]
	if got.Name != "debugger" || got.TargetContainerName != "app" {
		t.Fatalf("updated ephemeral container = %#v", got)
	}
	if got.SecurityContext != nil {
		t.Fatalf("submitted security context = %#v, want nil", got.SecurityContext)
	}
}

func TestCreateEphemeralContainerRetriesWithRestrictedSecurityContextAfterPodSecurityRejection(t *testing.T) {
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
	})
	rejectFirstRestrictedPodSecurityUpdate(client)

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "app",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}

	want := &corev1.SecurityContext{
		AllowPrivilegeEscalation: boolPtr(false),
		Capabilities: &corev1.Capabilities{
			Drop: []corev1.Capability{"ALL"},
		},
		RunAsNonRoot: boolPtr(true),
		RunAsUser:    int64Ptr(defaultDebugRunAsUser),
		SeccompProfile: &corev1.SeccompProfile{
			Type: corev1.SeccompProfileTypeRuntimeDefault,
		},
	}
	if !reflect.DeepEqual(ec.SecurityContext, want) {
		t.Fatalf("returned security context = %#v, want %#v", ec.SecurityContext, want)
	}

	updates := updatedPodsFromEphemeralActions(t, client.Actions())
	if len(updates) != 2 {
		t.Fatalf("got %d ephemeral container updates, want 2", len(updates))
	}
	if got := updates[0].Spec.EphemeralContainers[0].SecurityContext; got != nil {
		t.Fatalf("first submitted security context = %#v, want nil", got)
	}
	if got := updates[1].Spec.EphemeralContainers[0].SecurityContext; !reflect.DeepEqual(got, want) {
		t.Fatalf("retry submitted security context = %#v, want %#v", got, want)
	}
}

func TestCreateEphemeralContainerDoesNotRetryNonRestrictedError(t *testing.T) {
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
	})
	client.PrependReactor("update", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) {
		if action.GetSubresource() != "ephemeralcontainers" {
			return false, nil, nil
		}
		return true, nil, apierrors.NewForbidden(
			schema.GroupResource{Resource: "pods"},
			"app-0",
			fmt.Errorf("ephemeral containers are disabled"),
		)
	})

	_, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "app",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err == nil {
		t.Fatal("CreateEphemeralContainer returned nil error, want failure")
	}

	updates := updatedPodsFromEphemeralActions(t, client.Actions())
	if len(updates) != 1 {
		t.Fatalf("got %d ephemeral container updates, want 1", len(updates))
	}
}

func TestCreateEphemeralContainerUsesTargetContainerUserAndGroupOnRestrictedRetry(t *testing.T) {
	targetUser := int64(1001)
	targetGroup := int64(1002)
	podUser := int64(2002)
	podGroup := int64(2003)
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
		Spec: corev1.PodSpec{
			SecurityContext: &corev1.PodSecurityContext{
				RunAsGroup: &podGroup,
				RunAsUser:  &podUser,
			},
			Containers: []corev1.Container{{
				Name: "app",
				SecurityContext: &corev1.SecurityContext{
					RunAsGroup: &targetGroup,
					RunAsUser:  &targetUser,
				},
			}},
		},
	})
	rejectFirstRestrictedPodSecurityUpdate(client)

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "app",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}
	if ec.SecurityContext == nil || ec.SecurityContext.RunAsUser == nil || *ec.SecurityContext.RunAsUser != targetUser {
		t.Fatalf("runAsUser = %#v, want %d", ec.SecurityContext, targetUser)
	}
	if ec.SecurityContext.RunAsGroup == nil || *ec.SecurityContext.RunAsGroup != targetGroup {
		t.Fatalf("runAsGroup = %#v, want %d", ec.SecurityContext, targetGroup)
	}
}

func TestCreateEphemeralContainerUsesPodUserAndGroupWhenTargetUnsetOnRestrictedRetry(t *testing.T) {
	podUser := int64(2002)
	podGroup := int64(2003)
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
		Spec: corev1.PodSpec{
			SecurityContext: &corev1.PodSecurityContext{
				RunAsGroup: &podGroup,
				RunAsUser:  &podUser,
			},
			Containers: []corev1.Container{{
				Name: "app",
			}},
		},
	})
	rejectFirstRestrictedPodSecurityUpdate(client)

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "app",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}
	if ec.SecurityContext == nil || ec.SecurityContext.RunAsUser == nil || *ec.SecurityContext.RunAsUser != podUser {
		t.Fatalf("runAsUser = %#v, want %d", ec.SecurityContext, podUser)
	}
	if ec.SecurityContext.RunAsGroup == nil || *ec.SecurityContext.RunAsGroup != podGroup {
		t.Fatalf("runAsGroup = %#v, want %d", ec.SecurityContext, podGroup)
	}
}

func TestCreateEphemeralContainerDoesNotRetryWindowsPodWhenRestrictedContextUnavailable(t *testing.T) {
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "win-0",
		},
		Spec: corev1.PodSpec{
			OS: &corev1.PodOS{Name: corev1.Windows},
		},
	})
	rejectFirstRestrictedPodSecurityUpdate(client)

	_, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:     "team-a",
		PodName:       "win-0",
		Image:         "mcr.microsoft.com/windows/nanoserver:ltsc2022",
		ContainerName: "debugger",
	})
	if err == nil {
		t.Fatal("CreateEphemeralContainer returned nil error, want failure")
	}

	updates := updatedPodsFromEphemeralActions(t, client.Actions())
	if len(updates) != 1 {
		t.Fatalf("got %d ephemeral container updates, want 1", len(updates))
	}
	if got := updates[0].Spec.EphemeralContainers[0].SecurityContext; got != nil {
		t.Fatalf("submitted security context = %#v, want nil", got)
	}
}

func TestCreateEphemeralContainerOmitsLinuxSecurityContextForWindowsPod(t *testing.T) {
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "win-0",
		},
		Spec: corev1.PodSpec{
			OS: &corev1.PodOS{Name: corev1.Windows},
		},
	})

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:     "team-a",
		PodName:       "win-0",
		Image:         "mcr.microsoft.com/windows/nanoserver:ltsc2022",
		ContainerName: "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}
	if ec.SecurityContext != nil {
		t.Fatalf("returned security context = %#v, want nil", ec.SecurityContext)
	}

	updated := updatedPodFromEphemeralAction(t, client.Actions())
	if len(updated.Spec.EphemeralContainers) != 1 {
		t.Fatalf("updated pod has %d ephemeral containers, want 1", len(updated.Spec.EphemeralContainers))
	}
	if got := updated.Spec.EphemeralContainers[0].SecurityContext; got != nil {
		t.Fatalf("submitted security context = %#v, want nil", got)
	}
}

func updatedPodFromEphemeralAction(t *testing.T, actions []clienttesting.Action) *corev1.Pod {
	t.Helper()
	pods := updatedPodsFromEphemeralActions(t, actions)
	if len(pods) == 0 {
		t.Fatalf("no pods/ephemeralcontainers update action found in %#v", actions)
	}
	return pods[0]
}

func updatedPodsFromEphemeralActions(t *testing.T, actions []clienttesting.Action) []*corev1.Pod {
	t.Helper()
	var pods []*corev1.Pod
	for _, action := range actions {
		if action.GetVerb() != "update" || action.GetResource().Resource != "pods" || action.GetSubresource() != "ephemeralcontainers" {
			continue
		}
		update, ok := action.(clienttesting.UpdateAction)
		if !ok {
			t.Fatalf("ephemeralcontainers action has type %T, want UpdateAction", action)
		}
		pod, ok := update.GetObject().(*corev1.Pod)
		if !ok {
			t.Fatalf("ephemeralcontainers update object has type %T, want *corev1.Pod", update.GetObject())
		}
		pods = append(pods, pod)
	}
	return pods
}

func rejectFirstRestrictedPodSecurityUpdate(client *fake.Clientset) {
	attempts := 0
	client.PrependReactor("update", "pods", func(action clienttesting.Action) (bool, runtime.Object, error) {
		if action.GetSubresource() != "ephemeralcontainers" {
			return false, nil, nil
		}
		attempts++
		if attempts == 1 {
			return true, nil, apierrors.NewForbidden(
				schema.GroupResource{Resource: "pods"},
				"app-0",
				fmt.Errorf("violates PodSecurity \"restricted:latest\": allowPrivilegeEscalation != false"),
			)
		}
		return false, nil, nil
	})
}

func int64Ptr(v int64) *int64 { return &v }

func TestCreateEphemeralContainerUsesNativeSidecarUserAndGroupOnRestrictedRetry(t *testing.T) {
	sidecarUser := int64(1001)
	sidecarGroup := int64(1002)
	podUser := int64(2002)
	always := corev1.ContainerRestartPolicyAlways
	client := fake.NewSimpleClientset(&corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "team-a",
			Name:      "app-0",
		},
		Spec: corev1.PodSpec{
			SecurityContext: &corev1.PodSecurityContext{RunAsUser: &podUser},
			InitContainers: []corev1.Container{{
				Name:          "log-shipper",
				RestartPolicy: &always,
				SecurityContext: &corev1.SecurityContext{
					RunAsGroup: &sidecarGroup,
					RunAsUser:  &sidecarUser,
				},
			}},
			Containers: []corev1.Container{{Name: "app"}},
		},
	})
	rejectFirstRestrictedPodSecurityUpdate(client)

	ec, err := CreateEphemeralContainer(context.Background(), client, EphemeralContainerOptions{
		Namespace:       "team-a",
		PodName:         "app-0",
		TargetContainer: "log-shipper",
		Image:           "busybox:latest",
		ContainerName:   "debugger",
	})
	if err != nil {
		t.Fatalf("CreateEphemeralContainer returned error: %v", err)
	}
	if ec.SecurityContext == nil || ec.SecurityContext.RunAsUser == nil || *ec.SecurityContext.RunAsUser != sidecarUser {
		t.Fatalf("runAsUser = %#v, want %d", ec.SecurityContext, sidecarUser)
	}
	if ec.SecurityContext.RunAsGroup == nil || *ec.SecurityContext.RunAsGroup != sidecarGroup {
		t.Fatalf("runAsGroup = %#v, want %d", ec.SecurityContext, sidecarGroup)
	}
}