Skip to content

Commit

Permalink
Func tests for snapshot clone auth
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Kalenyuk <[email protected]>
  • Loading branch information
akalenyu committed Jul 26, 2023
1 parent f081b2b commit 4abf984
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 20 deletions.
2 changes: 2 additions & 0 deletions tests/libstorage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"datavolume.go",
"pvc.go",
"storageclass.go",
"volumesnapshot.go",
],
importpath = "kubevirt.io/kubevirt/tests/libstorage",
visibility = ["//visibility:public"],
Expand All @@ -21,6 +22,7 @@ go_library(
"//tests/framework/matcher:go_default_library",
"//tests/libnode:go_default_library",
"//tests/util:go_default_library",
"//vendor/github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1:go_default_library",
"//vendor/github.com/onsi/ginkgo/v2:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/github.com/onsi/gomega/types:go_default_library",
Expand Down
46 changes: 46 additions & 0 deletions tests/libstorage/storageclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,52 @@ func GetSnapshotStorageClass(client kubecli.KubevirtClient) (string, error) {
return storageClass, nil
}

func GetSnapshotClass(scName string, client kubecli.KubevirtClient) (string, error) {
crd, err := client.
ExtensionsClient().
ApiextensionsV1().
CustomResourceDefinitions().
Get(context.Background(), "volumesnapshotclasses.snapshot.storage.k8s.io", metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return "", nil
}

return "", err
}

hasV1 := false
for _, v := range crd.Spec.Versions {
if v.Name == "v1" && v.Served {
hasV1 = true
}
}

if !hasV1 {
return "", nil
}

volumeSnapshotClasses, err := client.KubernetesSnapshotClient().SnapshotV1().VolumeSnapshotClasses().List(context.Background(), metav1.ListOptions{})
if err != nil {
return "", err
}
if len(volumeSnapshotClasses.Items) == 0 {
return "", nil
}
sc, err := client.StorageV1().StorageClasses().Get(context.Background(), scName, metav1.GetOptions{})
if err != nil {
return "", err
}
for _, snapClass := range volumeSnapshotClasses.Items {
// Validate association between snapshot class and storage class
if snapClass.Driver == sc.Provisioner {
return snapClass.Name, nil
}
}

return "", nil
}

func GetRWXFileSystemStorageClass() (string, bool) {
storageRWXFileSystem := Config.StorageRWXFileSystem
return storageRWXFileSystem, storageRWXFileSystem != ""
Expand Down
42 changes: 42 additions & 0 deletions tests/libstorage/volumesnapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2023 Red Hat, Inc.
*
*/

package libstorage

import (
vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// NewVolumeSnapshot initializes a VolumeSnapshot struct
func NewVolumeSnapshot(name, namespace, sourcePvcName string, snapshotClassName *string) *vsv1.VolumeSnapshot {
return &vsv1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: vsv1.VolumeSnapshotSpec{
Source: vsv1.VolumeSnapshotSource{
PersistentVolumeClaimName: &sourcePvcName,
},
VolumeSnapshotClassName: snapshotClassName,
},
}
}
145 changes: 125 additions & 20 deletions tests/storage/datavolume.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,10 +757,10 @@ var _ = SIGDescribe("DataVolume Integration", func() {
var vm *v1.VirtualMachine

BeforeEach(func() {
var exists bool
storageClass, exists = libstorage.GetRWOFileSystemStorageClass()
if !exists {
Skip("Skip test when RWOFileSystem storage class is not present")
storageClass, err = libstorage.GetSnapshotStorageClass(virtClient)
Expect(err).ToNot(HaveOccurred())
if storageClass == "" {
Skip("Skiping test, no VolumeSnapshot support")
}

dataVolume = libdv.NewDataVolume(
Expand All @@ -784,7 +784,7 @@ var _ = SIGDescribe("DataVolume Integration", func() {
},
},
}
vm.Spec.DataVolumeTemplates[0].Spec.PVC.StorageClassName = pointer.StringPtr(storageClass)
vm.Spec.DataVolumeTemplates[0].Spec.PVC.StorageClassName = pointer.String(storageClass)
vm.Spec.Template.Spec.Volumes = append(vm.Spec.Template.Spec.Volumes, saVol)
vm.Spec.Template.Spec.Domain.Devices.Disks = append(vm.Spec.Template.Spec.Domain.Devices.Disks, v1.Disk{Name: volumeName})
})
Expand All @@ -803,7 +803,7 @@ var _ = SIGDescribe("DataVolume Integration", func() {
}
})

createVmSuccess := func() {
createVMSuccess := func() {
// sometimes it takes a bit for permission to actually be applied so eventually
Eventually(func() bool {
_, err = virtClient.VirtualMachine(vm.Namespace).Create(context.Background(), vm)
Expand All @@ -820,10 +820,9 @@ var _ = SIGDescribe("DataVolume Integration", func() {
libstorage.EventuallyDVWith(vm.Namespace, targetDVName, 90, HaveSucceeded())
}

It("should resolve DataVolume sourceRef", func() {
// convert DV to use datasource
createPVCDataSource := func() *cdiv1.DataSource {
dvt := &vm.Spec.DataVolumeTemplates[0]
ds := &cdiv1.DataSource{
return &cdiv1.DataSource{
ObjectMeta: metav1.ObjectMeta{
Name: "ds-" + rand.String(12),
},
Expand All @@ -833,6 +832,65 @@ var _ = SIGDescribe("DataVolume Integration", func() {
},
},
}
}

snapshotCloneMutateFunc := func() {
snapshotClassName, err := libstorage.GetSnapshotClass(storageClass, virtClient)
Expect(err).ToNot(HaveOccurred())
if snapshotClassName == "" {
Fail("The clone permission suite uses a snapshot-capable storage class, must have associated snapshot class")
}

dvt := &vm.Spec.DataVolumeTemplates[0]
snap := libstorage.NewVolumeSnapshot(dvt.Spec.Source.PVC.Name, dvt.Spec.Source.PVC.Namespace, dvt.Spec.Source.PVC.Name, &snapshotClassName)
snap, err = virtClient.KubernetesSnapshotClient().
SnapshotV1().
VolumeSnapshots(snap.Namespace).
Create(context.Background(), snap, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())

dvt.Spec.Source = &cdiv1.DataVolumeSource{
Snapshot: &cdiv1.DataVolumeSourceSnapshot{
Namespace: snap.Namespace,
Name: snap.Name,
},
}
}

createSnapshotDataSource := func() *cdiv1.DataSource {
snapshotClassName, err := libstorage.GetSnapshotClass(storageClass, virtClient)
Expect(err).ToNot(HaveOccurred())
if snapshotClassName == "" {
Fail("The clone permission suite uses a snapshot-capable storage class, must have associated snapshot class")
}

dvt := &vm.Spec.DataVolumeTemplates[0]
snap := libstorage.NewVolumeSnapshot(dvt.Spec.Source.PVC.Name, dvt.Spec.Source.PVC.Namespace, dvt.Spec.Source.PVC.Name, &snapshotClassName)
snap, err = virtClient.KubernetesSnapshotClient().
SnapshotV1().
VolumeSnapshots(snap.Namespace).
Create(context.Background(), snap, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())

return &cdiv1.DataSource{
ObjectMeta: metav1.ObjectMeta{
Name: "ds-" + rand.String(12),
},
Spec: cdiv1.DataSourceSpec{
Source: cdiv1.DataSourceSource{
Snapshot: &cdiv1.DataVolumeSourceSnapshot{
Namespace: snap.Namespace,
Name: snap.Name,
},
},
},
}
}

DescribeTable("should resolve DataVolume sourceRef", func(createDataSourceFunc func() *cdiv1.DataSource) {
// convert DV to use datasource
dvt := &vm.Spec.DataVolumeTemplates[0]
ds := createDataSourceFunc()
ds, err := virtClient.CdiClient().CdiV1beta1().DataSources(vm.Namespace).Create(context.TODO(), ds, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())

Expand All @@ -855,7 +913,7 @@ var _ = SIGDescribe("DataVolume Integration", func() {
testsuite.NamespaceTestAlternative,
)

createVmSuccess()
createVMSuccess()

dv, err := virtClient.CdiClient().CdiV1beta1().DataVolumes(vm.Namespace).Get(context.TODO(), dvt.Name, metav1.GetOptions{})
if libstorage.IsDataVolumeGC(virtClient) {
Expand All @@ -864,9 +922,20 @@ var _ = SIGDescribe("DataVolume Integration", func() {
}
Expect(err).ToNot(HaveOccurred())
Expect(dv.Spec.SourceRef).To(BeNil())
Expect(dv.Spec.Source.PVC.Namespace).To(Equal(ds.Spec.Source.PVC.Namespace))
Expect(dv.Spec.Source.PVC.Name).To(Equal(ds.Spec.Source.PVC.Name))
})
switch {
case dv.Spec.Source.PVC != nil:
Expect(dv.Spec.Source.PVC.Namespace).To(Equal(ds.Spec.Source.PVC.Namespace))
Expect(dv.Spec.Source.PVC.Name).To(Equal(ds.Spec.Source.PVC.Name))
case dv.Spec.Source.Snapshot != nil:
Expect(dv.Spec.Source.Snapshot.Namespace).To(Equal(ds.Spec.Source.Snapshot.Namespace))
Expect(dv.Spec.Source.Snapshot.Name).To(Equal(ds.Spec.Source.Snapshot.Name))
default:
Fail(fmt.Sprintf("wrong dv source %+v", dv))
}
},
Entry("with PVC source", createPVCDataSource),
Entry("with Snapshot source", createSnapshotDataSource),
)

It("should report DataVolume without source PVC", func() {
cloneRole, cloneRoleBinding = addClonePermission(
Expand Down Expand Up @@ -912,10 +981,13 @@ var _ = SIGDescribe("DataVolume Integration", func() {
}, 30*time.Second, 5*time.Second).Should(BeTrue())
})

DescribeTable("[storage-req] deny then allow clone request", decorators.StorageReq, func(role *rbacv1.Role, allServiceAccounts, allServiceAccountsInNamespace bool) {
DescribeTable("[storage-req] deny then allow clone request", decorators.StorageReq, func(role *rbacv1.Role, allServiceAccounts, allServiceAccountsInNamespace bool, cloneMutateFunc func(), fail bool) {
if cloneMutateFunc != nil {
cloneMutateFunc()
}
_, err := virtClient.VirtualMachine(vm.Namespace).Create(context.Background(), vm)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Authorization failed, message is:"))
Expect(err.Error()).To(ContainSubstring("insufficient permissions in clone source namespace"))

saName := testsuite.AdminServiceAccountName
saNamespace := testsuite.GetTestNamespace(nil)
Expand All @@ -929,16 +1001,29 @@ var _ = SIGDescribe("DataVolume Integration", func() {

// add permission
cloneRole, cloneRoleBinding = addClonePermission(virtClient, role, saName, saNamespace, testsuite.NamespaceTestAlternative)
if fail {
Consistently(func() error {
_, err = virtClient.VirtualMachine(vm.Namespace).Create(context.Background(), vm)
return err
}, 5*time.Second, 1*time.Second).Should(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("insufficient permissions in clone source namespace"))

createVmSuccess()
return
}
createVMSuccess()

// stop vm
vm = tests.StopVirtualMachine(vm)
},
Entry("[test_id:3193]with explicit role", explicitCloneRole, false, false),
Entry("[test_id:3194]with implicit role", implicitCloneRole, false, false),
Entry("[test_id:5253]with explicit role (all namespaces)", explicitCloneRole, true, false),
Entry("[test_id:5254]with explicit role (one namespace)", explicitCloneRole, false, true),
Entry("[test_id:3193]with explicit role", explicitCloneRole, false, false, nil, false),
Entry("[test_id:3194]with implicit role", implicitCloneRole, false, false, nil, false),
Entry("[test_id:5253]with explicit role (all namespaces)", explicitCloneRole, true, false, nil, false),
Entry("[test_id:5254]with explicit role (one namespace)", explicitCloneRole, false, true, nil, false),
Entry("with explicit role snapshot clone", explicitCloneRole, false, false, snapshotCloneMutateFunc, false),
Entry("with implicit insufficient role snapshot clone", implicitCloneRole, false, false, snapshotCloneMutateFunc, true),
Entry("with implicit sufficient role snapshot clone", implicitSnapshotCloneRole, false, false, snapshotCloneMutateFunc, false),
Entry("with explicit role (all namespaces) snapshot clone", explicitCloneRole, true, false, snapshotCloneMutateFunc, false),
Entry("with explicit role (one namespace) snapshot clone", explicitCloneRole, false, true, snapshotCloneMutateFunc, false),
)
})
})
Expand Down Expand Up @@ -1247,6 +1332,26 @@ var implicitCloneRole = &rbacv1.Role{
},
}

var implicitSnapshotCloneRole = &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "implicit-clone-role",
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{
"",
},
Resources: []string{
"pods",
"pvcs",
},
Verbs: []string{
"create",
},
},
},
}

func addClonePermission(client kubecli.KubevirtClient, role *rbacv1.Role, sa, saNamespace, targetNamesace string) (*rbacv1.Role, *rbacv1.RoleBinding) {
role, err := client.RbacV1().Roles(targetNamesace).Create(context.Background(), role, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
Expand Down

0 comments on commit 4abf984

Please sign in to comment.