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: support v2 backing image cloning and encryption #316

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
Loading