Add resize functions

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2022-05-10 10:43:48 +02:00 committed by Daniel Weiße
parent 2b80341d99
commit 6b3d45dd09
5 changed files with 298 additions and 137 deletions

2
go.mod
View File

@ -220,3 +220,5 @@ require (
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // 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

4
go.sum
View File

@ -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/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/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/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 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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/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.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.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=

View File

@ -11,6 +11,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/edgelesssys/constellation/internal/constants"
cryptsetup "github.com/martinjungblut/go-cryptsetup" cryptsetup "github.com/martinjungblut/go-cryptsetup"
"k8s.io/klog/v2" "k8s.io/klog/v2"
mount "k8s.io/mount-utils" mount "k8s.io/mount-utils"
@ -30,6 +31,11 @@ const (
// https://stackoverflow.com/questions/30553386/cryptsetup-backend-safe-with-multithreading // https://stackoverflow.com/questions/30553386/cryptsetup-backend-safe-with-multithreading
var packageLock = sync.Mutex{} 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. // KeyCreator is an interface to create data encryption keys.
type KeyCreator interface { type KeyCreator interface {
GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error) 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'. // Init initializes a crypt device backed by 'devicePath'.
// Sets the deviceMapper to the newly allocated Device or returns any error encountered. // Sets the deviceMapper to the newly allocated Device or returns any error encountered.
Init(devicePath string) error 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. // ActivateByVolumeKey activates a device by using a volume key.
// Returns nil on success, or an error otherwise. // 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. // Deactivate deactivates a device.
// Returns nil on success, or an error otherwise. // Returns nil on success, or an error otherwise.
Deactivate(deviceName string) error Deactivate(deviceName string) error
@ -54,6 +66,9 @@ type DeviceMapper interface {
// Load loads crypt device parameters from the on-disk header. // Load loads crypt device parameters from the on-disk header.
// Returns nil on success, or an error otherwise. // Returns nil on success, or an error otherwise.
Load(cryptsetup.DeviceType) error 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. // Wipe removes existing data and clears the device for use with dm-integrity.
// Returns nil on success, or an error otherwise. // 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 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 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. // Free releases crypt device context and used memory.
func (c *CryptDevice) Free() bool { func (c *CryptDevice) Free() bool {
res := c.Device.Free() 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) { 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) klog.V(4).Infof("Fetching data encryption key for volume %q", volumeID)
keySize := keySizeCrypt passphrase, err := c.kms.GetDEK(ctx, volumeID, constants.StateDiskKeyLength)
if integrity {
keySize = keySizeIntegrity
}
dek, err := c.kms.GetDEK(ctx, volumeID, keySize)
if err != nil { if err != nil {
return "", err 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()} 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. // closeCryptDevice closes the crypt device mapped for volumeID.
@ -161,9 +201,8 @@ func closeCryptDevice(device DeviceMapper, source, volumeID, deviceType string)
defer packageLock.Unlock() defer packageLock.Unlock()
klog.V(4).Infof("Unmapping dm-%s volume %q for device %q", deviceType, cryptPrefix+volumeID, source) 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) 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) 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. // 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() packageLock.Lock()
defer packageLock.Unlock() defer packageLock.Unlock()
var integrityType string var integrityType string
keySize := len(dek) keySize := keySizeCrypt
if integrity { 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)" integrityType = "hmac(sha256)"
} keySize = keySizeIntegrity
if !integrity && (len(dek) != keySizeCrypt) {
return "", fmt.Errorf("invalid key length for plain crypt: expected [%d], got [%d]", keySizeCrypt, len(dek))
} }
klog.V(4).Infof("Mapping device %q to dm-crypt volume %q", source, cryptPrefix+volumeID) 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 // Initialize the block device
if err := device.Init(source); err != nil { if err := device.Init(source); err != nil {
@ -240,24 +271,28 @@ func openCryptDevice(device DeviceMapper, source, volumeID, dek string, integrit
cryptsetup.GenericParams{ cryptsetup.GenericParams{
Cipher: "aes", Cipher: "aes",
CipherMode: "xts-plain64", CipherMode: "xts-plain64",
VolumeKey: dek,
VolumeKeySize: keySize, VolumeKeySize: keySize,
}); err != nil { }); err != nil {
klog.Errorf("Formatting device %q failed: %s", source, err) klog.Errorf("Formatting device %q failed: %s", source, err)
return "", fmt.Errorf("formatting device %q failed: %w", 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 needWipe = true
} }
if integrity && needWipe { 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) return "", fmt.Errorf("wiping device: %w", err)
} }
} }
klog.V(4).Infof("Activating LUKS2 device %q", cryptPrefix+volumeID) 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) klog.Errorf("Trying to activate dm-crypt volume: %s", err)
return "", fmt.Errorf("trying to activate dm-crypt volume: %w", 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. // 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...") klog.V(4).Infof("Preparing device for dm-integrity. This may take while...")
tmpDevice := "temporary-cryptsetup-" + volumeID tmpDevice := "temporary-cryptsetup-" + volumeID
// Active as temporary device // 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) klog.Errorf("Trying to activate temporary dm-crypt volume: %s", err)
return fmt.Errorf("trying to activate temporary dm-crypt volume: %w", 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 // If we are printing to a terminal we can show continues updates
progressCallback = func(size, offset uint64) int { progressCallback = func(size, offset uint64) int {
prog := (float64(offset) / float64(size)) * 100 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 return 0
} }
} else { } 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 { if err := device.Wipe(cryptPrefix+tmpDevice, cryptsetup.CRYPT_WIPE_ZERO, 0, 0, 1024*1024, 0, progressCallback); err != nil {
return err return err
} }
fmt.Println()
// Deactivate the temporary device // Deactivate the temporary device
if err := device.Deactivate(tmpDevice); err != nil { if err := device.Deactivate(tmpDevice); err != nil {
@ -328,6 +362,33 @@ func performWipe(device DeviceMapper, volumeID, dek string) error {
return nil 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. // IsIntegrityFS checks if the fstype string contains an integrity suffix.
// If yes, returns the trimmed fstype and true, fstype and false otherwise. // If yes, returns the trimmed fstype and true, fstype and false otherwise.
func IsIntegrityFS(fstype string) (string, bool) { func IsIntegrityFS(fstype string) (string, bool) {

View File

@ -22,27 +22,41 @@ var testDEK = []byte{
} }
type stubCryptDevice struct { type stubCryptDevice struct {
initErr error deviceName string
activateErr error initErr error
deactivateErr error initByNameErr error
formatErr error activateErr error
loadErr error activatePassErr error
wipeErr 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 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 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 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 return c.formatErr
} }
@ -54,10 +68,19 @@ func (c *stubCryptDevice) Load(cryptsetup.DeviceType) error {
return c.loadErr 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 return c.wipeErr
} }
func (c *stubCryptDevice) Resize(string, uint64) error {
return c.resizeErr
}
func TestCloseCryptDevice(t *testing.T) { func TestCloseCryptDevice(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
mapper *stubCryptDevice mapper *stubCryptDevice
@ -67,16 +90,12 @@ func TestCloseCryptDevice(t *testing.T) {
mapper: &stubCryptDevice{}, mapper: &stubCryptDevice{},
wantErr: false, wantErr: false,
}, },
"error on Init": { "error on InitByName": {
mapper: &stubCryptDevice{ mapper: &stubCryptDevice{initByNameErr: errors.New("error")},
initErr: errors.New("error"),
},
wantErr: true, wantErr: true,
}, },
"error on Deactivate": { "error on Deactivate": {
mapper: &stubCryptDevice{ mapper: &stubCryptDevice{deactivateErr: errors.New("error")},
deactivateErr: errors.New("error"),
},
wantErr: true, wantErr: true,
}, },
} }
@ -103,113 +122,116 @@ func TestOpenCryptDevice(t *testing.T) {
someErr := errors.New("error") someErr := errors.New("error")
testCases := map[string]struct { testCases := map[string]struct {
source string source string
volumeID string volumeID string
dek string passphrase string
integrity bool integrity bool
mapper *stubCryptDevice mapper *stubCryptDevice
diskInfo func(disk string) (string, error) diskInfo func(disk string) (string, error)
wantErr bool wantErr bool
}{ }{
"success with Load": { "success with Load": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{}, mapper: &stubCryptDevice{},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: false, wantErr: false,
}, },
"success with error on Load": { "success with error on Load": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{loadErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: false, wantErr: false,
}, },
"success with integrity": { "success with integrity": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(append(testDEK, testDEK[:32]...)), passphrase: string(append(testDEK, testDEK[:32]...)),
integrity: true, integrity: true,
mapper: &stubCryptDevice{loadErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: false, 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,
}, },
"error on Init": { "error on Init": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{initErr: someErr}, mapper: &stubCryptDevice{initErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, wantErr: true,
}, },
"error on Format": { "error on Format": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{loadErr: someErr, formatErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr, formatErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, wantErr: true,
}, },
"error on Activate": { "error on Activate": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{activateErr: someErr}, mapper: &stubCryptDevice{activatePassErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, wantErr: true,
}, },
"error on diskInfo": { "error on diskInfo": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{loadErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr},
diskInfo: func(disk string) (string, error) { return "", someErr }, diskInfo: func(disk string) (string, error) { return "", someErr },
wantErr: true, wantErr: true,
}, },
"disk is already formatted": { "disk is already formatted": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(testDEK), passphrase: string(testDEK),
mapper: &stubCryptDevice{loadErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr},
diskInfo: func(disk string) (string, error) { return "ext4", nil }, diskInfo: func(disk string) (string, error) { return "ext4", nil },
wantErr: true, wantErr: true,
}, },
"error with integrity on wipe": { "error with integrity on wipe": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(append(testDEK, testDEK[:32]...)), passphrase: string(append(testDEK, testDEK[:32]...)),
integrity: true, integrity: true,
mapper: &stubCryptDevice{loadErr: someErr, wipeErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr, wipeErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, wantErr: true,
}, },
"error with integrity on activate": { "error with integrity on activate": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(append(testDEK, testDEK[:32]...)), passphrase: string(append(testDEK, testDEK[:32]...)),
integrity: true, integrity: true,
mapper: &stubCryptDevice{loadErr: someErr, activateErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr, activateErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, wantErr: true,
}, },
"error with integrity on deactivate": { "error with integrity on deactivate": {
source: "/dev/some-device", source: "/dev/some-device",
volumeID: "volume0", volumeID: "volume0",
dek: string(append(testDEK, testDEK[:32]...)), passphrase: string(append(testDEK, testDEK[:32]...)),
integrity: true, integrity: true,
mapper: &stubCryptDevice{loadErr: someErr, deactivateErr: someErr}, mapper: &stubCryptDevice{loadErr: someErr, deactivateErr: someErr},
diskInfo: func(disk string) (string, error) { return "", nil }, diskInfo: func(disk string) (string, error) { return "", nil },
wantErr: true, 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) { t.Run(name, func(t *testing.T) {
assert := assert.New(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 { if tc.wantErr {
assert.Error(err) assert.Error(err)
} else { } else {
assert.NoError(err) assert.NoError(err)
assert.Equal(cryptPrefix+tc.volumeID, out) 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) 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) { func TestIsIntegrityFS(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
wantIntegrity bool wantIntegrity bool

View File

@ -29,9 +29,13 @@ func teardown() {
exec.Command("/bin/rm", "-f", DevicePath).Run() 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) { func TestMain(m *testing.M) {
if os.Getuid() != 0 { 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) os.Exit(1)
} }
@ -53,7 +57,7 @@ func TestOpenAndClose(t *testing.T) {
newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, false) newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, false)
require.NoError(err) require.NoError(err)
assert.Equal(newPath, "/dev/mapper/"+DeviceName) assert.Equal("/dev/mapper/"+DeviceName, newPath)
// assert crypt device got created // assert crypt device got created
_, err = os.Stat(newPath) _, err = os.Stat(newPath)
@ -62,6 +66,13 @@ func TestOpenAndClose(t *testing.T) {
_, err = os.Stat(newPath + "_dif") _, err = os.Stat(newPath + "_dif")
assert.True(os.IsNotExist(err)) 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.NoError(mapper.CloseCryptDevice(DeviceName))
// assert crypt device got removed // assert crypt device got removed
@ -80,7 +91,7 @@ func TestOpenAndCloseIntegrity(t *testing.T) {
newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, true) newPath, err := mapper.OpenCryptDevice(context.Background(), DevicePath, DeviceName, true)
require.NoError(err) require.NoError(err)
assert.Equal(newPath, "/dev/mapper/"+DeviceName) assert.Equal("/dev/mapper/"+DeviceName, newPath)
// assert crypt device got created // assert crypt device got created
_, err = os.Stat(newPath) _, err = os.Stat(newPath)
@ -89,6 +100,11 @@ func TestOpenAndCloseIntegrity(t *testing.T) {
_, err = os.Stat(newPath + "_dif") _, err = os.Stat(newPath + "_dif")
assert.NoError(err) 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.NoError(mapper.CloseCryptDevice(DeviceName))
// assert crypt device got removed // assert crypt device got removed