diff --git a/e2e/nfs.go b/e2e/nfs.go index d06e06e4e82d..a7ac99ecc936 100644 --- a/e2e/nfs.go +++ b/e2e/nfs.go @@ -440,6 +440,39 @@ var _ = Describe("nfs", func() { } }) + By("create a storageclass with a restricted set of clients allowed to mount it", func() { + clientExample := "192.168.49.29" + err := createNFSStorageClass(f.ClientSet, f, false, map[string]string{ + "clients": clientExample, + }) + if err != nil { + framework.Failf("failed to create NFS storageclass: %v", err) + } + pvc, err := loadPVC(pvcPath) + if err != nil { + framework.Failf("Could not create PVC: %v", err) + } + pvc.Namespace = f.UniqueName + err = createPVCAndvalidatePV(f.ClientSet, pvc, deployTimeout) + if err != nil { + framework.Failf("failed to create PVC: %v", err) + + } + + if !checkExports(f, "my-nfs", clientExample) { + framework.Failf("failed in testing exports") + } + + err = deletePVCAndValidatePV(f.ClientSet, pvc, deployTimeout) + if err != nil { + framework.Failf("failed to delete PVC: %v", err) + } + err = deleteResource(nfsExamplePath + "storageclass.yaml") + if err != nil { + framework.Failf("failed to delete NFS storageclass: %v", err) + } + }) + By("create a PVC and bind it to an app", func() { err := createNFSStorageClass(f.ClientSet, f, false, nil) if err != nil { diff --git a/e2e/utils.go b/e2e/utils.go index 4a748f15d377..6a7277b56fb3 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -129,6 +129,56 @@ func listCephFSFileSystems(f *framework.Framework) ([]cephfsFilesystem, error) { return fsList, nil } +type nfsExportsFSAL struct { + Name string `json:"name"` + UserID string `json:"user_id"` + FSName string `json:"fs_name"` +} + +type nfsExportsClients struct { + Addresses []string `json:"addresses"` + AccessType string `json:"access_type"` + Squash string `json:"squash"` +} + +type cephNFSExport struct { + ExportID int `json:"export_id"` + Path string `json:"path"` + ClusterID string `json:"cluster_id"` + Pseudo string `json:"pseudo"` + AccessType string `json:"access_type"` + Squash string `json:"squash"` + SecurityLabel bool `json:"security_label"` + Protocols []int `json:"protocols"` + Transports []string `json:"transports"` + FSAL nfsExportsFSAL `json:"fsal"` + Clients []nfsExportsClients `json:"clients"` + SecTypes []string `json:"secTypes"` +} + +// Get list of exports for a cluster_id +func listExports(f *framework.Framework, clusterID string) ([]cephNFSExport, error) { + var exportList []cephNFSExport + + stdout, stdErr, err := execCommandInToolBoxPod( + f, + "ceph nfs export ls "+clusterID+" --detailed", + rookNamespace) + if err != nil { + return exportList, err + } + if stdErr != "" { + return exportList, fmt.Errorf("error listing exports in clusterID %v", stdErr) + } + + err = json.Unmarshal([]byte(stdout), &exportList) + if err != nil { + return exportList, err + } + + return exportList, nil +} + // getCephFSMetadataPoolName get CephFS pool name from filesystem name. func getCephFSMetadataPoolName(f *framework.Framework, filesystem string) (string, error) { fsList, err := listCephFSFileSystems(f) @@ -1743,3 +1793,49 @@ func getConfigFile(filename, preferred, fallback string) string { return configFile } + +// Check the export for a listed ip address and confirm that the export has +// been setup correctly. +func checkExports(f *framework.Framework, clusterID string, clientString string) bool { + exportList, err := listExports(f, clusterID) + if err != nil { + framework.Logf("failed to fetch list of exports: %v", err) + return false + } + + found := false + for _, export := range exportList { + for _, client := range export.Clients { + for _, address := range client.Addresses { + if address == clientString { + found = true + + break + } + } + if found { + if client.AccessType != "rw" { + framework.Logf("Unexpected value for client AccessType: %s", client.AccessType) + return false + } + + break + } + } + if found { + if export.AccessType != "none" { + framework.Logf("Unexpected value for default AccessType: %s", export.AccessType) + return false + } + + break + } + } + + if !found { + framework.Logf("Could not find the configured clients in the list of exports") + return false + } + + return true +} diff --git a/examples/nfs/storageclass.yaml b/examples/nfs/storageclass.yaml index 7bc21af15c64..c524fa5392c1 100644 --- a/examples/nfs/storageclass.yaml +++ b/examples/nfs/storageclass.yaml @@ -51,5 +51,11 @@ parameters: # This option is available with Ceph v17.2.6 and newer. # secTypes: + # (optional) The clients parameter in the storage class is used to limit + # access to the export to the set of hostnames, networks or ip addresses + # specified. The is a comma delimited string, + # for example: "192.168.0.10,192.168.1.0/8" + # clients: + reclaimPolicy: Delete allowVolumeExpansion: true diff --git a/internal/nfs/controller/volume.go b/internal/nfs/controller/volume.go index 2249f8c61706..271393d97706 100644 --- a/internal/nfs/controller/volume.go +++ b/internal/nfs/controller/volume.go @@ -132,6 +132,7 @@ func (nv *NFSVolume) CreateExport(backend *csi.Volume) error { nfsCluster := backend.VolumeContext["nfsCluster"] path := backend.VolumeContext["subvolumePath"] secTypes := backend.VolumeContext["secTypes"] + clients := backend.VolumeContext["clients"] err := nv.setNFSCluster(nfsCluster) if err != nil { @@ -157,6 +158,10 @@ func (nv *NFSVolume) CreateExport(backend *csi.Volume) error { } } + if clients != "" { + export.ClientAddr = strings.Split(clients, ",") + } + _, err = nfsa.CreateCephFSExport(export) switch { case err == nil: