From 6b3d45dd09584c628195576e420a2676561b8724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= Date: Tue, 10 May 2022 10:43:48 +0200 Subject: [PATCH] Add resize functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Weiße --- go.mod | 2 + go.sum | 4 +- mount/cryptmapper/cryptmapper.go | 117 ++++++++--- mount/cryptmapper/cryptmapper_test.go | 290 +++++++++++++++++--------- mount/test/mount_integration_test.go | 22 +- 5 files changed, 298 insertions(+), 137 deletions(-) diff --git a/go.mod b/go.mod index 046b76b43..57e108e47 100644 --- a/go.mod +++ b/go.mod @@ -220,3 +220,5 @@ require ( sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect ) + +replace github.com/martinjungblut/go-cryptsetup => github.com/daniel-weisse/go-cryptsetup v0.0.0-20220510090142-d35a60c619db diff --git a/go.sum b/go.sum index 3972aa0d0..9171ce9fc 100644 --- a/go.sum +++ b/go.sum @@ -512,6 +512,8 @@ github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1S github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/daniel-weisse/go-cryptsetup v0.0.0-20220510090142-d35a60c619db h1:MgKZLtrp/goZpLnshCnx7j5YwjfibjCt60Sl/pPJHtg= +github.com/daniel-weisse/go-cryptsetup v0.0.0-20220510090142-d35a60c619db/go.mod h1:gZoZ0+POlM1ge/VUxWpMmZVNPzzMJ7l436CgkQ5+qzU= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -1016,8 +1018,6 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/martinjungblut/go-cryptsetup v0.0.0-20220421194528-92e17766b2e7 h1:/KmytOTrWo56d+J/h4VKakoCu0PxQ796+NCTfOQFCYc= -github.com/martinjungblut/go-cryptsetup v0.0.0-20220421194528-92e17766b2e7/go.mod h1:gZoZ0+POlM1ge/VUxWpMmZVNPzzMJ7l436CgkQ5+qzU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= diff --git a/mount/cryptmapper/cryptmapper.go b/mount/cryptmapper/cryptmapper.go index dddb92e5e..14c24df5f 100644 --- a/mount/cryptmapper/cryptmapper.go +++ b/mount/cryptmapper/cryptmapper.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/edgelesssys/constellation/internal/constants" cryptsetup "github.com/martinjungblut/go-cryptsetup" "k8s.io/klog/v2" mount "k8s.io/mount-utils" @@ -30,6 +31,11 @@ const ( // https://stackoverflow.com/questions/30553386/cryptsetup-backend-safe-with-multithreading var packageLock = sync.Mutex{} +func init() { + cryptsetup.SetDebugLevel(cryptsetup.CRYPT_LOG_NORMAL) + cryptsetup.SetLogCallback(func(level int, message string) { klog.V(4).Infof("libcryptsetup: %s", message) }) +} + // KeyCreator is an interface to create data encryption keys. type KeyCreator interface { GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error) @@ -40,9 +46,15 @@ type DeviceMapper interface { // Init initializes a crypt device backed by 'devicePath'. // Sets the deviceMapper to the newly allocated Device or returns any error encountered. Init(devicePath string) error + // InitByName initializes a crypt device from provided active device 'name'. + // Sets the deviceMapper to the newly allocated Device or returns any error encountered. + InitByName(name string) error + // ActivateByPassphrase activates a device by using a passphrase from a specific keyslot. + // Returns nil on success, or an error otherwise. + ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error // ActivateByVolumeKey activates a device by using a volume key. // Returns nil on success, or an error otherwise. - ActivateByVolumeKey(deviceName, volumeKey string, volumeKeySize, flags int) error + ActivateByVolumeKey(deviceName string, volumeKey string, volumeKeySize int, flags int) error // Deactivate deactivates a device. // Returns nil on success, or an error otherwise. Deactivate(deviceName string) error @@ -54,6 +66,9 @@ type DeviceMapper interface { // Load loads crypt device parameters from the on-disk header. // Returns nil on success, or an error otherwise. Load(cryptsetup.DeviceType) error + // KeyslotAddByVolumeKey adds a key slot using a volume key to perform the required security check. + // Returns nil on success, or an error otherwise. + KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error // Wipe removes existing data and clears the device for use with dm-integrity. // Returns nil on success, or an error otherwise. Wipe(devicePath string, pattern int, offset, length uint64, wipeBlockSize int, flags int, progress func(size, offset uint64) int) error @@ -78,6 +93,17 @@ func (c *CryptDevice) Init(devicePath string) error { return nil } +// InitByName initializes a crypt device from provided active device 'name'. +// Sets the deviceMapper to the newly allocated Device or returns any error encountered. +func (c *CryptDevice) InitByName(name string) error { + device, err := cryptsetup.InitByName(name) + if err != nil { + return err + } + c.Device = device + return nil +} + // Free releases crypt device context and used memory. func (c *CryptDevice) Free() bool { res := c.Device.Free() @@ -142,17 +168,31 @@ func (c *CryptMapper) CloseCryptDevice(volumeID string) error { func (c *CryptMapper) OpenCryptDevice(ctx context.Context, source, volumeID string, integrity bool) (string, error) { klog.V(4).Infof("Fetching data encryption key for volume %q", volumeID) - keySize := keySizeCrypt - if integrity { - keySize = keySizeIntegrity - } - dek, err := c.kms.GetDEK(ctx, volumeID, keySize) + passphrase, err := c.kms.GetDEK(ctx, volumeID, constants.StateDiskKeyLength) if err != nil { return "", err } + if len(passphrase) != constants.StateDiskKeyLength { + return "", fmt.Errorf("expected key length to be [%d] but got [%d]", constants.StateDiskKeyLength, len(passphrase)) + } m := &mount.SafeFormatAndMount{Exec: utilexec.New()} - return openCryptDevice(c.mapper, source, volumeID, string(dek), integrity, m.GetDiskFormat) + return openCryptDevice(c.mapper, source, volumeID, string(passphrase), integrity, m.GetDiskFormat) +} + +// ResizeCryptDevice resizes the underlying crypt device and returns the mapped device path. +func (c *CryptMapper) ResizeCryptDevice(ctx context.Context, volumeID string) (string, error) { + dek, err := c.kms.GetDEK(ctx, volumeID, constants.StateDiskKeyLength) + if err != nil { + return "", err + } + klog.V(4).Infof("Resizing LUKS2 partition %q", cryptPrefix+volumeID) + + if err := resizeCryptDevice(c.mapper, volumeID, string(dek)); err != nil { + return "", err + } + + return cryptPrefix + volumeID, nil } // closeCryptDevice closes the crypt device mapped for volumeID. @@ -161,9 +201,8 @@ func closeCryptDevice(device DeviceMapper, source, volumeID, deviceType string) defer packageLock.Unlock() klog.V(4).Infof("Unmapping dm-%s volume %q for device %q", deviceType, cryptPrefix+volumeID, source) - cryptsetup.SetLogCallback(func(level int, message string) { klog.V(4).Infof("libcryptsetup: %s", message) }) - if err := device.Init(source); err != nil { + if err := device.InitByName(volumeID); err != nil { klog.Errorf("Could not initialize dm-%s to unmap device %q: %s", deviceType, source, err) return fmt.Errorf("could not initialize dm-%s to unmap device %q: %w", deviceType, source, err) } @@ -179,26 +218,18 @@ func closeCryptDevice(device DeviceMapper, source, volumeID, deviceType string) } // openCryptDevice maps the volume at source to the crypt device identified by volumeID. -func openCryptDevice(device DeviceMapper, source, volumeID, dek string, integrity bool, diskInfo func(disk string) (string, error)) (string, error) { +func openCryptDevice(device DeviceMapper, source, volumeID, passphrase string, integrity bool, diskInfo func(disk string) (string, error)) (string, error) { packageLock.Lock() defer packageLock.Unlock() var integrityType string - keySize := len(dek) - + keySize := keySizeCrypt if integrity { - if len(dek) != keySizeIntegrity { - return "", fmt.Errorf("invalid key size for crypt with integrity: expected [%d], got [%d]", keySizeIntegrity, len(dek)) - } integrityType = "hmac(sha256)" - } - - if !integrity && (len(dek) != keySizeCrypt) { - return "", fmt.Errorf("invalid key length for plain crypt: expected [%d], got [%d]", keySizeCrypt, len(dek)) + keySize = keySizeIntegrity } klog.V(4).Infof("Mapping device %q to dm-crypt volume %q", source, cryptPrefix+volumeID) - cryptsetup.SetLogCallback(func(level int, message string) { klog.V(4).Infof("libcryptsetup: %s", message) }) // Initialize the block device if err := device.Init(source); err != nil { @@ -240,24 +271,28 @@ func openCryptDevice(device DeviceMapper, source, volumeID, dek string, integrit cryptsetup.GenericParams{ Cipher: "aes", CipherMode: "xts-plain64", - VolumeKey: dek, VolumeKeySize: keySize, }); err != nil { klog.Errorf("Formatting device %q failed: %s", source, err) return "", fmt.Errorf("formatting device %q failed: %w", source, err) } + + // Add a new keyslot using the internal volume key + if err := device.KeyslotAddByVolumeKey(0, "", passphrase); err != nil { + return "", fmt.Errorf("adding keyslot: %w", err) + } needWipe = true } if integrity && needWipe { - if err := performWipe(device, volumeID, dek); err != nil { + if err := performWipe(device, volumeID); err != nil { return "", fmt.Errorf("wiping device: %w", err) } } klog.V(4).Infof("Activating LUKS2 device %q", cryptPrefix+volumeID) - if err := device.ActivateByVolumeKey(volumeID, dek, keySize, 0); err != nil { + if err := device.ActivateByPassphrase(volumeID, 0, passphrase, 0); err != nil { klog.Errorf("Trying to activate dm-crypt volume: %s", err) return "", fmt.Errorf("trying to activate dm-crypt volume: %w", err) } @@ -268,12 +303,12 @@ func openCryptDevice(device DeviceMapper, source, volumeID, dek string, integrit } // performWipe handles setting up parameters and clearing the device for dm-integrity. -func performWipe(device DeviceMapper, volumeID, dek string) error { +func performWipe(device DeviceMapper, volumeID string) error { klog.V(4).Infof("Preparing device for dm-integrity. This may take while...") tmpDevice := "temporary-cryptsetup-" + volumeID // Active as temporary device - if err := device.ActivateByVolumeKey(tmpDevice, dek, len(dek), (cryptsetup.CRYPT_ACTIVATE_PRIVATE | cryptsetup.CRYPT_ACTIVATE_NO_JOURNAL)); err != nil { + if err := device.ActivateByVolumeKey(tmpDevice, "", 0, (cryptsetup.CRYPT_ACTIVATE_PRIVATE | cryptsetup.CRYPT_ACTIVATE_NO_JOURNAL)); err != nil { klog.Errorf("Trying to activate temporary dm-crypt volume: %s", err) return fmt.Errorf("trying to activate temporary dm-crypt volume: %w", err) } @@ -284,7 +319,7 @@ func performWipe(device DeviceMapper, volumeID, dek string) error { // If we are printing to a terminal we can show continues updates progressCallback = func(size, offset uint64) int { prog := (float64(offset) / float64(size)) * 100 - fmt.Printf("\033[2K\rWipe in progress: %.2f%%", prog) + fmt.Printf("\033[1A\033[2K\rWipe in progress: %.2f%%\n", prog) return 0 } } else { @@ -312,11 +347,10 @@ func performWipe(device DeviceMapper, volumeID, dek string) error { } } - // Wipe the device using the same options as used in cryptsetup: https://gitlab.com/cryptsetup/cryptsetup/-/blob/master/src/cryptsetup.c#L1178 + // Wipe the device using the same options as used in cryptsetup: https://gitlab.com/cryptsetup/cryptsetup/-/blob/v2.4.3/src/cryptsetup.c#L1345 if err := device.Wipe(cryptPrefix+tmpDevice, cryptsetup.CRYPT_WIPE_ZERO, 0, 0, 1024*1024, 0, progressCallback); err != nil { return err } - fmt.Println() // Deactivate the temporary device if err := device.Deactivate(tmpDevice); err != nil { @@ -328,6 +362,33 @@ func performWipe(device DeviceMapper, volumeID, dek string) error { return nil } +func resizeCryptDevice(device DeviceMapper, name, passphrase string) error { + packageLock.Lock() + defer packageLock.Unlock() + + if err := device.InitByName(name); err != nil { + return fmt.Errorf("initializing device: %w", err) + } + defer device.Free() + + if err := device.Load(cryptsetup.LUKS2{}); err != nil { + return fmt.Errorf("loading device: %w", err) + } + + if err := device.ActivateByPassphrase("", 0, passphrase, cryptsetup.CRYPT_ACTIVATE_KEYRING_KEY); err != nil { + klog.Errorf("Unable to activate keyring for crypt device %q with passphrase: %s", name, err) + return fmt.Errorf("activating keyrung for crypt device %q with passphrase: %w", name, err) + } + + if err := device.Resize(name, 0); err != nil { + klog.Errorf("Unable to resize crypt device: %s", err) + return fmt.Errorf("resizing device: %w", err) + } + klog.V(4).Infof("Successfully resized LUKS2 partition for %q", cryptPrefix+name) + + return nil +} + // IsIntegrityFS checks if the fstype string contains an integrity suffix. // If yes, returns the trimmed fstype and true, fstype and false otherwise. func IsIntegrityFS(fstype string) (string, bool) { diff --git a/mount/cryptmapper/cryptmapper_test.go b/mount/cryptmapper/cryptmapper_test.go index 05b5583a0..1aad773d4 100644 --- a/mount/cryptmapper/cryptmapper_test.go +++ b/mount/cryptmapper/cryptmapper_test.go @@ -22,27 +22,41 @@ var testDEK = []byte{ } type stubCryptDevice struct { - initErr error - activateErr error - deactivateErr error - formatErr error - loadErr error - wipeErr error + deviceName string + initErr error + initByNameErr error + activateErr error + activatePassErr error + deactivateErr error + formatErr error + loadErr error + keySlotAddCalled bool + keySlotAddErr error + wipeErr error + resizeErr error } -func (c *stubCryptDevice) Init(devicePath string) error { +func (c *stubCryptDevice) Init(string) error { return c.initErr } -func (c *stubCryptDevice) ActivateByVolumeKey(deviceName, volumeKey string, volumeKeySize, flags int) error { +func (c *stubCryptDevice) InitByName(string) error { + return c.initByNameErr +} + +func (c *stubCryptDevice) ActivateByVolumeKey(string, string, int, int) error { return c.activateErr } -func (c *stubCryptDevice) Deactivate(deviceName string) error { +func (c *stubCryptDevice) ActivateByPassphrase(string, int, string, int) error { + return c.activatePassErr +} + +func (c *stubCryptDevice) Deactivate(string) error { return c.deactivateErr } -func (c *stubCryptDevice) Format(deviceType cryptsetup.DeviceType, genericParams cryptsetup.GenericParams) error { +func (c *stubCryptDevice) Format(cryptsetup.DeviceType, cryptsetup.GenericParams) error { return c.formatErr } @@ -54,10 +68,19 @@ func (c *stubCryptDevice) Load(cryptsetup.DeviceType) error { return c.loadErr } -func (c *stubCryptDevice) Wipe(devicePath string, pattern int, offset, length uint64, wipeBlockSize int, flags int, progress func(size, offset uint64) int) error { +func (c *stubCryptDevice) KeyslotAddByVolumeKey(int, string, string) error { + c.keySlotAddCalled = true + return c.keySlotAddErr +} + +func (c *stubCryptDevice) Wipe(string, int, uint64, uint64, int, int, func(size, offset uint64) int) error { return c.wipeErr } +func (c *stubCryptDevice) Resize(string, uint64) error { + return c.resizeErr +} + func TestCloseCryptDevice(t *testing.T) { testCases := map[string]struct { mapper *stubCryptDevice @@ -67,16 +90,12 @@ func TestCloseCryptDevice(t *testing.T) { mapper: &stubCryptDevice{}, wantErr: false, }, - "error on Init": { - mapper: &stubCryptDevice{ - initErr: errors.New("error"), - }, + "error on InitByName": { + mapper: &stubCryptDevice{initByNameErr: errors.New("error")}, wantErr: true, }, "error on Deactivate": { - mapper: &stubCryptDevice{ - deactivateErr: errors.New("error"), - }, + mapper: &stubCryptDevice{deactivateErr: errors.New("error")}, wantErr: true, }, } @@ -103,113 +122,116 @@ func TestOpenCryptDevice(t *testing.T) { someErr := errors.New("error") testCases := map[string]struct { - source string - volumeID string - dek string - integrity bool - mapper *stubCryptDevice - diskInfo func(disk string) (string, error) - wantErr bool + source string + volumeID string + passphrase string + integrity bool + mapper *stubCryptDevice + diskInfo func(disk string) (string, error) + wantErr bool }{ "success with Load": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: false, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: false, }, "success with error on Load": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{loadErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: false, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{loadErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: false, }, "success with integrity": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(append(testDEK, testDEK[:32]...)), - integrity: true, - mapper: &stubCryptDevice{loadErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: false, - }, - "incorrect key size": { - source: "/dev/some-device", - volumeID: "volume0", - dek: "short", - mapper: &stubCryptDevice{}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(append(testDEK, testDEK[:32]...)), + integrity: true, + mapper: &stubCryptDevice{loadErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: false, }, "error on Init": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{initErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{initErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, "error on Format": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{loadErr: someErr, formatErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{loadErr: someErr, formatErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, "error on Activate": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{activateErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{activatePassErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, "error on diskInfo": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{loadErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", someErr }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{loadErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", someErr }, + wantErr: true, }, "disk is already formatted": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(testDEK), - mapper: &stubCryptDevice{loadErr: someErr}, - diskInfo: func(disk string) (string, error) { return "ext4", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{loadErr: someErr}, + diskInfo: func(disk string) (string, error) { return "ext4", nil }, + wantErr: true, }, "error with integrity on wipe": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(append(testDEK, testDEK[:32]...)), - integrity: true, - mapper: &stubCryptDevice{loadErr: someErr, wipeErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(append(testDEK, testDEK[:32]...)), + integrity: true, + mapper: &stubCryptDevice{loadErr: someErr, wipeErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, "error with integrity on activate": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(append(testDEK, testDEK[:32]...)), - integrity: true, - mapper: &stubCryptDevice{loadErr: someErr, activateErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(append(testDEK, testDEK[:32]...)), + integrity: true, + mapper: &stubCryptDevice{loadErr: someErr, activateErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, "error with integrity on deactivate": { - source: "/dev/some-device", - volumeID: "volume0", - dek: string(append(testDEK, testDEK[:32]...)), - integrity: true, - mapper: &stubCryptDevice{loadErr: someErr, deactivateErr: someErr}, - diskInfo: func(disk string) (string, error) { return "", nil }, - wantErr: true, + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(append(testDEK, testDEK[:32]...)), + integrity: true, + mapper: &stubCryptDevice{loadErr: someErr, deactivateErr: someErr}, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, + }, + "error on adding keyslot": { + source: "/dev/some-device", + volumeID: "volume0", + passphrase: string(testDEK), + mapper: &stubCryptDevice{ + loadErr: someErr, + keySlotAddErr: someErr, + }, + diskInfo: func(disk string) (string, error) { return "", nil }, + wantErr: true, }, } @@ -217,12 +239,18 @@ func TestOpenCryptDevice(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - out, err := openCryptDevice(tc.mapper, tc.source, tc.volumeID, tc.dek, tc.integrity, tc.diskInfo) + out, err := openCryptDevice(tc.mapper, tc.source, tc.volumeID, tc.passphrase, tc.integrity, tc.diskInfo) if tc.wantErr { assert.Error(err) } else { assert.NoError(err) assert.Equal(cryptPrefix+tc.volumeID, out) + + if tc.mapper.loadErr == nil { + assert.False(tc.mapper.keySlotAddCalled) + } else { + assert.True(tc.mapper.keySlotAddCalled) + } } }) } @@ -232,6 +260,60 @@ func TestOpenCryptDevice(t *testing.T) { assert.NoError(t, err) } +func TestResizeCryptDevice(t *testing.T) { + volumeID := "pvc-123" + someErr := errors.New("error") + testCases := map[string]struct { + volumeID string + device *stubCryptDevice + wantErr bool + }{ + "success": { + volumeID: volumeID, + device: &stubCryptDevice{}, + }, + "InitByName fails": { + volumeID: volumeID, + device: &stubCryptDevice{initByNameErr: someErr}, + wantErr: true, + }, + "Load fails": { + volumeID: volumeID, + device: &stubCryptDevice{loadErr: someErr}, + wantErr: true, + }, + "Resize fails": { + volumeID: volumeID, + device: &stubCryptDevice{resizeErr: someErr}, + wantErr: true, + }, + "ActivateByPassphrase fails": { + volumeID: volumeID, + device: &stubCryptDevice{activatePassErr: someErr}, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + mapper := &CryptMapper{ + kms: kms.NewStaticKMS(), + mapper: tc.device, + } + + res, err := mapper.ResizeCryptDevice(context.Background(), tc.volumeID) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(cryptPrefix+tc.volumeID, res) + } + }) + } +} + func TestIsIntegrityFS(t *testing.T) { testCases := map[string]struct { wantIntegrity bool diff --git a/mount/test/mount_integration_test.go b/mount/test/mount_integration_test.go index 262c12832..756661c9c 100644 --- a/mount/test/mount_integration_test.go +++ b/mount/test/mount_integration_test.go @@ -29,9 +29,13 @@ func teardown() { exec.Command("/bin/rm", "-f", DevicePath).Run() } +func resize() { + exec.Command("/bin/dd", "if=/dev/zero", fmt.Sprintf("of=%s", DevicePath), "bs=32M", "count=1", "oflag=append", "conv=notrunc").Run() +} + func TestMain(m *testing.M) { if os.Getuid() != 0 { - fmt.Printf("This test suite requires root privileges, as libcrypsetup uses the kernel's device mapper.\n") + fmt.Printf("This test suite requires root privileges, as libcryptsetup uses the kernel's device mapper.\n") os.Exit(1) } @@ -53,7 +57,7 @@ func TestOpenAndClose(t *testing.T) { newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, false) require.NoError(err) - assert.Equal(newPath, "/dev/mapper/"+DeviceName) + assert.Equal("/dev/mapper/"+DeviceName, newPath) // assert crypt device got created _, err = os.Stat(newPath) @@ -62,6 +66,13 @@ func TestOpenAndClose(t *testing.T) { _, err = os.Stat(newPath + "_dif") assert.True(os.IsNotExist(err)) + // Resize the device + resize() + + resizedPath, err := mapper.ResizeCryptDevice(context.Background(), DeviceName) + require.NoError(err) + assert.Equal("/dev/mapper/"+DeviceName, resizedPath) + assert.NoError(mapper.CloseCryptDevice(DeviceName)) // assert crypt device got removed @@ -80,7 +91,7 @@ func TestOpenAndCloseIntegrity(t *testing.T) { newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, true) require.NoError(err) - assert.Equal(newPath, "/dev/mapper/"+DeviceName) + assert.Equal("/dev/mapper/"+DeviceName, newPath) // assert crypt device got created _, err = os.Stat(newPath) @@ -89,6 +100,11 @@ func TestOpenAndCloseIntegrity(t *testing.T) { _, err = os.Stat(newPath + "_dif") assert.NoError(err) + // integrity devices do not support resizing + resize() + _, err = mapper.ResizeCryptDevice(context.Background(), DeviceName) + assert.Error(err) + assert.NoError(mapper.CloseCryptDevice(DeviceName)) // assert crypt device got removed