Skip to content

Commit

Permalink
feat: support v2 backing image cloning and encryption
Browse files Browse the repository at this point in the history
ref: longhorn/longhorn 9996

Signed-off-by: Jack Lin <[email protected]>
  • Loading branch information
ChanYiLin committed Feb 10, 2025
1 parent 44753b5 commit ae89412
Show file tree
Hide file tree
Showing 10 changed files with 923 additions and 564 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.23.0

toolchain go1.23.4

replace github.com/longhorn/types v0.0.0-20241225162202-00d3a5fd7502 => github.com/chanyilin/types v0.0.0-20250122064930-c1b9017c2968

require (
github.com/0xPolygon/polygon-edge v1.3.3
github.com/google/uuid v1.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 h1:SjZ2GvvOononHOpK
github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chanyilin/types v0.0.0-20250122064930-c1b9017c2968 h1:RSdTuXuVvvX7AMEUczdNhFYBtu6sHZ3jGd6I/CqAjJI=
github.com/chanyilin/types v0.0.0-20250122064930-c1b9017c2968/go.mod h1:3jHuVDtpkXQzpnp4prguDBskVRric2kmF8aSPkRJ4jw=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -57,8 +59,6 @@ github.com/longhorn/go-common-libs v0.0.0-20250107022351-ec79818ce8db h1:aXAcEW8
github.com/longhorn/go-common-libs v0.0.0-20250107022351-ec79818ce8db/go.mod h1:Fm4sGHDO1JXsAI/DxdEJ/QFWYv+jJMJMfHaCyd+SnGA=
github.com/longhorn/go-spdk-helper v0.0.0-20250116051812-eabe34a4219e h1:rBySu2+Dfrp0+xBwo989TskR9hBCe31H/F2m+PIOXxI=
github.com/longhorn/go-spdk-helper v0.0.0-20250116051812-eabe34a4219e/go.mod h1:2NT1Xz4rBNecYAQmj/UqHz5D3UEDZZkiv2hcmVB7kqM=
github.com/longhorn/types v0.0.0-20241225162202-00d3a5fd7502 h1:jgw7nosooLe1NQEdCGzM/nEOFzPcurNO+0PDsicc5+A=
github.com/longhorn/types v0.0.0-20241225162202-00d3a5fd7502/go.mod h1:3jHuVDtpkXQzpnp4prguDBskVRric2kmF8aSPkRJ4jw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
Expand Down
183 changes: 183 additions & 0 deletions pkg/backing_image_crypto/backing_image_crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package backingimagecrypto

import (
"fmt"
"path"
"strings"
"time"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"

lhns "github.com/longhorn/go-common-libs/ns"
lhtypes "github.com/longhorn/go-common-libs/types"
)

const (
MapperFilePathPrefix = "/dev/mapper"

CryptoKeyDefaultCipher = "aes-xts-plain64"
CryptoKeyDefaultHash = "sha256"
CryptoKeyDefaultSize = "256"
CryptoDefaultPBKDF = "argon2i"

CommandExecutionTimeout = 10 * time.Second

EncryptionMetaSize = 16 * 1024 * 1024 // 16MB
)

// EncryptParams keeps the customized cipher options from the secret CR
type EncryptParams struct {
KeyProvider string
KeyCipher string
KeyHash string
KeySize string
PBKDF string
}

func NewEncryptParams(keyProvider, keyCipher, keyHash, keySize, pbkdf string) *EncryptParams {
return &EncryptParams{KeyProvider: keyProvider, KeyCipher: keyCipher, KeyHash: keyHash, KeySize: keySize, PBKDF: pbkdf}
}

func (cp *EncryptParams) GetKeyCipher() string {
if cp.KeyCipher == "" {
return CryptoKeyDefaultCipher
}
return cp.KeyCipher
}

func (cp *EncryptParams) GetKeyHash() string {
if cp.KeyHash == "" {
return CryptoKeyDefaultHash
}
return cp.KeyHash
}

func (cp *EncryptParams) GetKeySize() string {
if cp.KeySize == "" {
return CryptoKeyDefaultSize
}
return cp.KeySize
}

func (cp *EncryptParams) GetPBKDF() string {
if cp.PBKDF == "" {
return CryptoDefaultPBKDF
}
return cp.PBKDF
}

// EncryptBackingImage encrypts provided device with LUKS.
func EncryptBackingImage(devicePath, passphrase string, cryptoParams *EncryptParams) error {
namespaces := []lhtypes.Namespace{lhtypes.NamespaceMnt, lhtypes.NamespaceIpc}
nsexec, err := lhns.NewNamespaceExecutor(lhtypes.ProcessNone, lhtypes.HostProcDirectory, namespaces)
if err != nil {
return err
}

logrus.Infof("Encrypting device %s with LUKS", devicePath)
if _, err := nsexec.LuksFormat(
devicePath, passphrase,
cryptoParams.GetKeyCipher(),
cryptoParams.GetKeyHash(),
cryptoParams.GetKeySize(),
cryptoParams.GetPBKDF(),
lhtypes.LuksTimeout); err != nil {
return errors.Wrapf(err, "failed to encrypt device %s with LUKS", devicePath)
}
return nil
}

// OpenBackingImage opens backing image so that it can be used by the client.
func OpenBackingImage(devicePath, passphrase, name string) error {
if isOpen, _ := IsEncryptedDeviceOpened(BackingImageMapper(name)); isOpen {
logrus.Infof("Device %s is already opened at %s", devicePath, BackingImageMapper(name))
return nil
}

namespaces := []lhtypes.Namespace{lhtypes.NamespaceMnt, lhtypes.NamespaceIpc}
nsexec, err := lhns.NewNamespaceExecutor(lhtypes.ProcessNone, lhtypes.HostProcDirectory, namespaces)
if err != nil {
return err
}

logrus.Infof("Opening device %s with LUKS", devicePath)
_, err = nsexec.LuksOpen(GetLuksBackingImageName(name), devicePath, passphrase, lhtypes.LuksTimeout)
if err != nil {
logrus.WithError(err).Warnf("Failed to open LUKS device %s", devicePath)
}
return err
}

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

logrus.Infof("Closing LUKS device %s", GetLuksBackingImageName(name))
_, err = nsexec.LuksClose(GetLuksBackingImageName(name), lhtypes.LuksTimeout)
return err
}

// IsEncryptedDeviceOpened determines if encrypted device is already open.
func IsEncryptedDeviceOpened(device string) (bool, error) {
_, mappedFile, err := DeviceEncryptionStatus(device)
return mappedFile != "", err
}

// DeviceEncryptionStatus looks to identify if the passed device is a LUKS mapping
// and if so what the device is and the mapper name as used by LUKS.
// If not, just returns the original device and an empty string.
func DeviceEncryptionStatus(devicePath string) (mappedDevice, mapper string, err error) {
if !strings.HasPrefix(devicePath, MapperFilePathPrefix) {
return devicePath, "", nil
}

namespaces := []lhtypes.Namespace{lhtypes.NamespaceMnt, lhtypes.NamespaceIpc}
nsexec, err := lhns.NewNamespaceExecutor(lhtypes.ProcessNone, lhtypes.HostProcDirectory, namespaces)
if err != nil {
return devicePath, "", err
}

backingImage := strings.TrimPrefix(devicePath, MapperFilePathPrefix+"/")
stdout, err := nsexec.LuksStatus(backingImage, lhtypes.LuksTimeout)
if err != nil {
logrus.WithError(err).Warnf("Device %s is not an active LUKS device", devicePath)
return devicePath, "", nil
}

lines := strings.Split(string(stdout), "\n")
if len(lines) < 1 {
return "", "", fmt.Errorf("device encryption status returned no stdout for %s", devicePath)
}

if !strings.Contains(lines[0], " is active") {
// Implies this is not a LUKS device
return devicePath, "", nil
}

for i := 1; i < len(lines); i++ {
kv := strings.SplitN(strings.TrimSpace(lines[i]), ":", 2)
if len(kv) < 1 {
return "", "", fmt.Errorf("device encryption status output for %s is badly formatted: %s",
devicePath, lines[i])
}
if strings.Compare(kv[0], "device") == 0 {
return strings.TrimSpace(kv[1]), backingImage, nil
}
}
// Identified as LUKS, but failed to identify a mapped device
return "", "", fmt.Errorf("mapped device not found in path %s", devicePath)
}

func BackingImageMapper(uuid string) string {
return path.Join(MapperFilePathPrefix, GetLuksBackingImageName(uuid))
}

// TODO: Fix hard coded
func GetLuksBackingImageName(name string) string {
return fmt.Sprintf("%v-%v", "v2backing", name)
}
21 changes: 12 additions & 9 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,22 +825,25 @@ func (c *SPDKClient) ReplicaRestoreStatus(replicaName string) (*spdkrpc.ReplicaR
})
}

func (c *SPDKClient) BackingImageCreate(name, backingImageUUID, lvsUUID string, size uint64, checksum string, fromAddress string, srcLvsUUID string) (*api.BackingImage, error) {
if name == "" || backingImageUUID == "" || checksum == "" || lvsUUID == "" || size == 0 {
func (c *SPDKClient) BackingImageCreate(name, backingImageUUID, lvsUUID string, size uint64, checksum, fromAddress, srcLvsUUID, srcBackingImageName, encryption string, credential map[string]string) (*api.BackingImage, error) {
if name == "" || backingImageUUID == "" || lvsUUID == "" || srcBackingImageName == "" || size == 0 {
return nil, fmt.Errorf("failed to start SPDK backing image: missing required parameters")
}
client := c.getSPDKServiceClient()
ctx, cancel := context.WithTimeout(context.Background(), GRPCServiceTimeout)
defer cancel()

resp, err := client.BackingImageCreate(ctx, &spdkrpc.BackingImageCreateRequest{
Name: name,
BackingImageUuid: backingImageUUID,
LvsUuid: lvsUUID,
Size: size,
Checksum: checksum,
FromAddress: fromAddress,
SrcLvsUuid: srcLvsUUID,
Name: name,
BackingImageUuid: backingImageUUID,
LvsUuid: lvsUUID,
Size: size,
Checksum: checksum,
FromAddress: fromAddress,
SrcLvsUuid: srcLvsUUID,
SrcBackingImageName: srcBackingImageName,
Encryption: encryption,
Credential: credential,
})
if err != nil {
return nil, errors.Wrap(err, "failed to start SPDK backing image")
Expand Down
Loading

0 comments on commit ae89412

Please sign in to comment.