diff --git a/e2e/nfs.go b/e2e/nfs.go index 054558e49..dca1b227c 100644 --- a/e2e/nfs.go +++ b/e2e/nfs.go @@ -459,6 +459,38 @@ 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: 1 %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 4a748f15d..48ee28c92 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -1743,3 +1743,104 @@ func getConfigFile(filename, preferred, fallback string) string { return configFile } + +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 nil, err + } + if stdErr != "" { + return nil, fmt.Errorf("error listing exports in clusterID %v", stdErr) + } + + err = json.Unmarshal([]byte(stdout), &exportList) + if err != nil { + return nil, err + } + + return &exportList, nil +} + +// Check the export for a listed ip address and confirm that the export has +// been setup correctly. +func checkExports(f *framework.Framework, clusterID, 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 i := 0; i < len(*exportList); i++ { + export := (*exportList)[i] + 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 7bc21af15..c524fa539 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 2249f8c61..271393d97 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: