diff --git a/driver/diskutils.go b/driver/diskutils.go index f34455a..d1e6dc8 100644 --- a/driver/diskutils.go +++ b/driver/diskutils.go @@ -59,8 +59,11 @@ type DiskUtils interface { // GetStatfs return the statfs struct for the given path GetStatfs(path string) (*unix.Statfs_t, error) - // Resize resizes the given volumes - Resize(targetPath string, devicePath string) error + // Resize resizes the given volumes, it will try to resize the LUKS device first if the passphrase is provided + Resize(targetPath string, devicePath, passphrase string) error + + // IsEncrypted returns true if the device with the given path is encrypted with LUKS + IsEncrypted(devicePath string) (bool, error) // EncryptAndOpenDevice encrypts the volume with the given ID with the given passphrase and open it // If the device is already encrypted (LUKS header present), it will only open the device @@ -162,7 +165,9 @@ func (d *diskUtils) GetMappedDevicePath(volumeID string) (string, error) { // first line should look like // /dev/mapper/ is active. - if !strings.HasSuffix(statusLines[0], "is active.") { + // or + // /dev/mapper/ is active and is in use. + if !strings.HasSuffix(statusLines[0], "is active.") && !strings.HasSuffix(statusLines[0], "is active and is in use.") { // when a device is not active, an error exit code is thrown // something went wrong if we reach here return "", fmt.Errorf("luksStatus returned ok, but device %s is not active", diskLuksMapperPrefix+volumeID) @@ -431,12 +436,25 @@ func (d *diskUtils) GetStatfs(path string) (*unix.Statfs_t, error) { return fs, err } -func (d *diskUtils) Resize(targetPath string, devicePath string) error { +func (d *diskUtils) IsEncrypted(devicePath string) (bool, error) { + return luksIsLuks(devicePath) +} + +func (d *diskUtils) Resize(targetPath string, devicePath, passphrase string) error { mountInfo, err := d.GetMountInfo(targetPath) if err != nil { return err } + if passphrase != "" { + klog.V(4).Infof("resizing LUKS device %s", devicePath) + if err := luksResize(devicePath, passphrase); err != nil { + return err + } + } + + klog.V(4).Infof("resizing filesystem %s on %s", mountInfo.fsType, devicePath) + switch mountInfo.fsType { case "ext3", "ext4": resize2fsPath, err := exec.LookPath("resize2fs") diff --git a/driver/luks_utils.go b/driver/luks_utils.go index f5664f6..df14983 100644 --- a/driver/luks_utils.go +++ b/driver/luks_utils.go @@ -3,6 +3,7 @@ package driver import ( "bytes" "errors" + "fmt" "os/exec" "strings" ) @@ -56,6 +57,27 @@ func luksClose(mapperFile string) error { return luksCloseCmd.Run() } +func luksResize(mapperFile, passphrase string) error { + args := []string{ + "resize", // resize + mapperFile, // mapper file to resize + "--key-file", "/dev/stdin", // read the passphrase from stdin + } + + luksResizeCmd := exec.Command(cryptsetupCmd, args...) + + luksResizeCmd.Stdin = strings.NewReader(passphrase) + o := &bytes.Buffer{} + e := &bytes.Buffer{} + luksResizeCmd.Stdout = o + luksResizeCmd.Stderr = e + + if err := luksResizeCmd.Run(); err != nil { + return fmt.Errorf("luks resize failed: %v, stdout: %s, stderr: %s", err, o.String(), e.String()) + } + return nil +} + func luksStatus(mapperFile string) ([]byte, error) { args := []string{ "status", // status diff --git a/driver/node.go b/driver/node.go index a31de8b..9764ffe 100644 --- a/driver/node.go +++ b/driver/node.go @@ -7,12 +7,13 @@ import ( "strings" "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/scaleway/scaleway-csi/scaleway" "github.com/scaleway/scaleway-sdk-go/scw" "golang.org/x/sys/unix" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog/v2" + + "github.com/scaleway/scaleway-csi/scaleway" ) const ( @@ -573,8 +574,25 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV return &csi.NodeExpandVolumeResponse{}, nil } - err = d.diskUtils.Resize(volumePath, devicePath) + klog.V(4).Infof("resizing volume %s mounted on %s", volumeID, volumePath) + encrypted, err := d.diskUtils.IsEncrypted(devicePath) if err != nil { + return nil, status.Errorf(codes.Internal, "error checking if volume %s is encrypted: %s", volumeID, err.Error()) + } + + passphrase := req.GetSecrets()[encryptionPassphraseKey] + if encrypted { + devicePath, err = d.diskUtils.GetMappedDevicePath(volumeID) + if err != nil { + return nil, status.Errorf(codes.Internal, "error retrieving mapped device path for volume with ID %s: %s", volumeID, err.Error()) + } + klog.V(4).Infof("mapped device path for volume %s is %s", volumeID, devicePath) + if passphrase == "" { + return nil, status.Errorf(codes.InvalidArgument, "device %s is LUKS encrypted, but no passphrase was provided", devicePath) + } + } + + if err = d.diskUtils.Resize(volumePath, devicePath, passphrase); err != nil { return nil, status.Errorf(codes.Internal, "failed to resize volume %s mounted on %s: %v", volumeID, volumePath, err) } diff --git a/driver/sanity_test.go b/driver/sanity_test.go index fe3b8a1..eb6c427 100644 --- a/driver/sanity_test.go +++ b/driver/sanity_test.go @@ -12,13 +12,14 @@ import ( "github.com/google/uuid" "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" - "github.com/scaleway/scaleway-csi/scaleway" "github.com/scaleway/scaleway-sdk-go/api/instance/v1" "github.com/scaleway/scaleway-sdk-go/scw" "golang.org/x/sys/unix" kmount "k8s.io/mount-utils" kexec "k8s.io/utils/exec" utilsio "k8s.io/utils/io" + + "github.com/scaleway/scaleway-csi/scaleway" ) type fakeHelper struct { @@ -526,10 +527,14 @@ func (s *fakeHelper) GetStatfs(path string) (*unix.Statfs_t, error) { }, nil } -func (s *fakeHelper) Resize(targetPath string, devicePath string) error { +func (s *fakeHelper) Resize(targetPath string, devicePath, passphrase string) error { return nil } +func (s *fakeHelper) IsEncrypted(devicePath string) (bool, error) { + return false, nil +} + func (s *fakeHelper) EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) { return "", nil } diff --git a/examples/kubernetes/README.md b/examples/kubernetes/README.md index 306bb12..9e2ea75 100644 --- a/examples/kubernetes/README.md +++ b/examples/kubernetes/README.md @@ -245,7 +245,8 @@ data: and the following StorageClass: ```yaml -allowVolumeExpansion: false # not yet supported +# Volume expansion is supported with CSINodeExpandSecret feature gate since v1.25.0 or by default since v1.27.0 +allowVolumeExpansion: true apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: @@ -257,6 +258,9 @@ parameters: encrypted: "true" csi.storage.k8s.io/node-stage-secret-name: "enc-secret" csi.storage.k8s.io/node-stage-secret-namespace: "default" + # Required for volume expansion + csi.storage.k8s.io/node-expand-secret-name: "enc-secret" + csi.storage.k8s.io/node-expand-secret-namespace: "default" ``` all the PVC created with the StorageClass `scw-bssd-enc` will be encrypted at rest with the passphrase `myawesomepassphrase`.