// Copyright 2020-2023 Project Capsule Authors. // SPDX-License-Identifier: Apache-2.0 package e2e import ( "context" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant", "storage"), func() { tnt1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "pv-one", }, Spec: capsulev1beta2.TenantSpec{ Owners: api.OwnerListSpec{ { CoreOwnerSpec: api.CoreOwnerSpec{ UserSpec: api.UserSpec{ Name: "jessica", Kind: "User", }, }, }, }, }, } tnt2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "pv-two", }, Spec: capsulev1beta2.TenantSpec{ Owners: api.OwnerListSpec{ { CoreOwnerSpec: api.CoreOwnerSpec{ UserSpec: api.UserSpec{ Name: "leto", Kind: "User", }, }, }, }, }, } JustBeforeEach(func() { for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { EventuallyCreation(func() error { tnt.ResourceVersion = "" return k8sClient.Create(context.TODO(), tnt) }).Should(Succeed()) } }) JustAfterEach(func() { for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) } }) It("should add labels to PersistentVolume and prevent cross-Tenant mount", func() { ns := NewNamespace("") NamespaceCreation(ns, tnt1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns.Name)) pvc := corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "arrakis", Namespace: ns.Name, }, Spec: corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ corev1.ReadWriteOnce, }, Resources: corev1.VolumeResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceStorage: resource.MustParse("1Gi"), }, }, StorageClassName: ptr.To("standard"), }, } EventuallyCreation(func() error { return k8sClient.Create(context.Background(), &pvc) }).Should(Succeed()) pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "arrakis-pod", Namespace: ns.Name, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "container", Image: "gcr.io/google_containers/pause-amd64:3.0", ImagePullPolicy: corev1.PullAlways, VolumeMounts: []corev1.VolumeMount{ { Name: "data", MountPath: "/tmp", }, }, }, }, Volumes: []corev1.Volume{ { Name: "data", VolumeSource: corev1.VolumeSource{ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ ClaimName: pvc.Name, }, }, }, }, }, } EventuallyCreation(func() error { return k8sClient.Create(context.Background(), &pod) }).Should(Succeed()) Eventually(func() int { nsName := types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace} if err := k8sClient.Get(context.Background(), nsName, &pvc); err != nil { return 0 } return len(pvc.Spec.VolumeName) }, defaultTimeoutInterval, defaultPollInterval).Should(BeNumerically(">", 0)) pv := corev1.PersistentVolume{} defer func() { _ = k8sClient.Delete(context.Background(), &pv) }() Eventually(func() string { if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil { return "not-found" } if pv.GetLabels() == nil { return "no-labels" } return pv.GetLabels()["capsule.clastix.io/tenant"] }, defaultTimeoutInterval, defaultPollInterval).Should(Equal(tnt1.Name)) Eventually(func() error { nsName := types.NamespacedName{Name: pv.Name} if err := k8sClient.Get(context.Background(), nsName, &pv); err != nil { return err } pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRecycle return k8sClient.Update(context.Background(), &pv) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) Expect(k8sClient.Delete(context.Background(), &pod, &client.DeleteOptions{GracePeriodSeconds: ptr.To(int64(0))})).ToNot(HaveOccurred()) ns2 := NewNamespace("") NamespaceCreation(ns2, tnt2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.Name)) Consistently(func() error { pvc := corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "caladan", Namespace: ns2.Name, }, Spec: corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ corev1.ReadWriteOnce, }, Resources: corev1.VolumeResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceStorage: resource.MustParse("1Gi"), }, }, StorageClassName: ptr.To("standard"), VolumeName: pv.Name, }, } return k8sClient.Create(context.Background(), &pvc) }, defaultTimeoutInterval, defaultPollInterval).Should(HaveOccurred()) }) })