Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(crypto): v2 volume encryption support #424

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/cmd/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"fmt"
"net"
"os"
"os/signal"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/longhorn/longhorn-share-manager/pkg/rpc"
"github.com/longhorn/longhorn-share-manager/pkg/server"
"github.com/longhorn/longhorn-share-manager/pkg/types"
"github.com/longhorn/longhorn-share-manager/pkg/util"
"github.com/longhorn/longhorn-share-manager/pkg/volume"
)
Expand All @@ -33,6 +35,11 @@ func ServerCmd() cli.Command {
Usage: "The volume to export via the nfs server",
Required: true,
},
cli.StringFlag{
Name: "dataEngine",
Usage: "The volume data engine",
Required: true,
},
mantissahz marked this conversation as resolved.
Show resolved Hide resolved
cli.BoolFlag{
Name: "encrypted",
Usage: "signals that a volume is encrypted",
Expand Down Expand Up @@ -84,6 +91,7 @@ func ServerCmd() cli.Command {
Action: func(c *cli.Context) {
vol := volume.Volume{
Name: c.String("volume"),
DataEngine: c.String("dataEngine"),
Passphrase: c.String("passphrase"),
CryptoKeyCipher: c.String("crytpokeycipher"),
CryptoKeyHash: c.String("crytpokeyhash"),
Expand All @@ -106,6 +114,11 @@ func ServerCmd() cli.Command {

func start(vol volume.Volume) error {
logger := util.NewLogger()
if vol.DataEngine != types.DataEngineTypeV1 && vol.DataEngine != types.DataEngineTypeV2 {
logger.Errorf("Invalid data engine value: %s", vol.DataEngine)
return fmt.Errorf("invalid data engine value: %s", vol.DataEngine)
}

manager, err := server.NewShareManager(logger, vol)
if err != nil {
return err
Expand Down
28 changes: 16 additions & 12 deletions pkg/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ func EncryptVolume(devicePath, passphrase, keyCipher, keyHash, keySize, pbkdf st
}

// OpenVolume opens volume so that it can be used by the client.
func OpenVolume(volume, devicePath, passphrase string) error {
devPath := types.GetVolumeDevicePath(volume, true)
// devicePath is the path of the volume on the host that will be opened for instance '/dev/longhorn/volume1'
func OpenVolume(volume, dataEngine, devicePath, passphrase string) error {
devPath := types.GetVolumeDevicePath(volume, dataEngine, true)
if isOpen, _ := IsDeviceOpen(devPath); isOpen {
logrus.Debugf("Device %s is already opened at %s", devicePath, devPath)
return nil
Expand All @@ -42,33 +43,36 @@ func OpenVolume(volume, devicePath, passphrase string) error {
return err
}

logrus.Debugf("Opening device %s with LUKS on %s", devicePath, volume)
_, err = nsexec.LuksOpen(volume, devicePath, passphrase, lhtypes.LuksTimeout)
encryptedDevName := types.GetEncryptVolumeName(volume, dataEngine)
logrus.Debugf("Opening device %s with LUKS on %s", devicePath, encryptedDevName)
_, err = nsexec.LuksOpen(encryptedDevName, devicePath, passphrase, lhtypes.LuksTimeout)
if err != nil {
logrus.WithError(err).Warnf("Failed to open LUKS device %s", devicePath)
logrus.WithError(err).Warnf("Failed to open LUKS device %s to %s", devicePath, encryptedDevName)
}
return err
}

// CloseVolume closes encrypted volume so it can be detached.
func CloseVolume(volume string) error {
func CloseVolume(volume, dataEngine string) error {
namespaces := []lhtypes.Namespace{lhtypes.NamespaceMnt, lhtypes.NamespaceIpc}
nsexec, err := lhns.NewNamespaceExecutor(lhtypes.ProcessNone, lhtypes.HostProcDirectory, namespaces)
if err != nil {
return err
}

logrus.Debugf("Closing LUKS device %s", volume)
_, err = nsexec.LuksClose(volume, lhtypes.LuksTimeout)
deviceName := types.GetEncryptVolumeName(volume, dataEngine)
logrus.Debugf("Closing LUKS device %s", deviceName)
_, err = nsexec.LuksClose(deviceName, lhtypes.LuksTimeout)
return err
}

func ResizeEncryptoDevice(volume, passphrase string) error {
devPath := types.GetVolumeDevicePath(volume, true)
func ResizeEncryptoDevice(volume, dataEngine, passphrase string) error {
// devPath is the full path of the encrypted device on the host that will be resized
devPath := types.GetVolumeDevicePath(volume, dataEngine, true)
if isOpen, err := IsDeviceOpen(devPath); err != nil {
return err
} else if !isOpen {
return fmt.Errorf("volume %v encrypto device is closed for resizing", volume)
return fmt.Errorf("volume %v encrypto device %s is closed for resizing", volume, devPath)
}

namespaces := []lhtypes.Namespace{lhtypes.NamespaceMnt, lhtypes.NamespaceIpc}
Expand All @@ -77,7 +81,7 @@ func ResizeEncryptoDevice(volume, passphrase string) error {
return err
}

_, err = nsexec.LuksResize(volume, passphrase, lhtypes.LuksTimeout)
_, err = nsexec.LuksResize(types.GetEncryptVolumeName(volume, dataEngine), passphrase, lhtypes.LuksTimeout)
return err
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (s *ShareManagerServer) FilesystemTrim(ctx context.Context, req *smrpc.File
}
}()

devicePath := types.GetVolumeDevicePath(vol.Name, req.EncryptedDevice)
devicePath := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, req.EncryptedDevice)
if !volume.CheckDeviceValid(devicePath) {
return &emptypb.Empty{}, grpcstatus.Errorf(grpccodes.FailedPrecondition, "volume %v is not valid", vol.Name)
}
Expand Down Expand Up @@ -134,7 +134,7 @@ func (s *ShareManagerServer) FilesystemResize(ctx context.Context, req *emptypb.
}
}()

devicePath := types.GetVolumeDevicePath(vol.Name, vol.IsEncrypted())
devicePath := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, vol.IsEncrypted())
if !volume.CheckDeviceValid(devicePath) {
return &emptypb.Empty{}, grpcstatus.Errorf(grpccodes.FailedPrecondition, "volume %v is not valid", vol.Name)
}
Expand All @@ -161,7 +161,7 @@ func (s *ShareManagerServer) FilesystemResize(ctx context.Context, req *emptypb.
return &emptypb.Empty{}, grpcstatus.Errorf(grpccodes.InvalidArgument, "unsupported disk encryption format %v", diskFormat)
}

if err = crypto.ResizeEncryptoDevice(vol.Name, vol.Passphrase); err != nil {
if err = crypto.ResizeEncryptoDevice(vol.Name, vol.DataEngine, vol.Passphrase); err != nil {
return &emptypb.Empty{}, grpcstatus.Errorf(grpccodes.Internal, "failed to resize crypto device %v for volume %v node expansion: %v", devicePath, vol.Name, err)
}
}
Expand Down Expand Up @@ -309,7 +309,7 @@ func (s *ShareManagerServer) Mount(ctx context.Context, req *emptypb.Empty) (res

log.Info("Mounting and exporting volume")

devicePath := types.GetVolumeDevicePath(vol.Name, false)
devicePath := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, false)
mountPath := types.GetMountPath(vol.Name)

defer func() {
Expand Down
10 changes: 5 additions & 5 deletions pkg/server/share_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func NewShareManager(logger logrus.FieldLogger, volume volume.Volume) (*ShareMan
func (m *ShareManager) Run() error {
vol := m.volume
mountPath := types.GetMountPath(vol.Name)
devicePath := types.GetVolumeDevicePath(vol.Name, false)
devicePath := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, false)

defer func() {
// if the server is exiting, try to unmount & teardown device before we terminate the container
Expand Down Expand Up @@ -201,9 +201,9 @@ func (m *ShareManager) setupDevice(vol volume.Volume, devicePath string) (string
}
}

cryptoDevice := types.GetVolumeDevicePath(vol.Name, true)
cryptoDevice := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, true)
m.logger.Infof("Volume %s requires crypto device %s", vol.Name, cryptoDevice)
if err := crypto.OpenVolume(vol.Name, devicePath, vol.Passphrase); err != nil {
if err := crypto.OpenVolume(vol.Name, vol.DataEngine, devicePath, vol.Passphrase); err != nil {
m.logger.WithError(err).Error("Failed to open encrypted volume")
return "", err
}
Expand All @@ -217,12 +217,12 @@ func (m *ShareManager) setupDevice(vol volume.Volume, devicePath string) (string

func (m *ShareManager) tearDownDevice(vol volume.Volume) error {
// close any matching crypto device for this volume
cryptoDevice := types.GetVolumeDevicePath(vol.Name, true)
cryptoDevice := types.GetVolumeDevicePath(vol.Name, vol.DataEngine, true)
if isOpen, err := crypto.IsDeviceOpen(cryptoDevice); err != nil {
return err
} else if isOpen {
m.logger.Infof("Volume %s has active crypto device %s", vol.Name, cryptoDevice)
if err := crypto.CloseVolume(vol.Name); err != nil {
if err := crypto.CloseVolume(vol.Name, vol.DataEngine); err != nil {
return err
}
m.logger.Infof("Volume %s closed active crypto device %s", vol.Name, cryptoDevice)
Expand Down
20 changes: 19 additions & 1 deletion pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,29 @@ const (
DevPath = "/dev"
MapperDevPath = "/dev/mapper"

// The suffix for dm device name of the encrypted v2 volume.
MapperV2VolumeSuffix = "-encrypted"

ExportPath = "/export"

DataEngineTypeV1 = "v1"
DataEngineTypeV2 = "v2"
)

func GetVolumeDevicePath(volumeName string, EncryptedDevice bool) string {
func GetEncryptVolumeName(volume, dataEngine string) string {
if dataEngine == DataEngineTypeV2 {
return volume + MapperV2VolumeSuffix
}
return volume
}
mantissahz marked this conversation as resolved.
Show resolved Hide resolved

func GetVolumeDevicePath(volumeName, dataEngine string, EncryptedDevice bool) string {
if EncryptedDevice {
if dataEngine == DataEngineTypeV2 {
// v2 volume will use a dm device as default to control IO path when attaching. This dm device will be created with the same name as the volume name.
// The encrypted volume will be created with the volume name with "-encrypted" suffix to resolve the naming conflict.
return path.Join(MapperDevPath, GetEncryptVolumeName(volumeName, dataEngine))
}
return path.Join(MapperDevPath, volumeName)
}
return filepath.Join(DevPath, "longhorn", volumeName)
Expand Down
1 change: 1 addition & 0 deletions pkg/volume/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

type Volume struct {
Name string
DataEngine string
Passphrase string
CryptoKeyCipher string
CryptoKeyHash string
Expand Down
Loading