From 943698e33b224dcab32a66e79c2e5dc325d56748 Mon Sep 17 00:00:00 2001 From: Niraj Yadav Date: Thu, 17 Oct 2024 17:25:08 +0530 Subject: [PATCH] rbd: Add timeout for cryptsetup commands This PR modifies the execCryptSetupCommand so that the process is killed in an event of lock timeout. Useful in cases where the volume lock is released but the command is still running. Signed-off-by: Niraj Yadav --- internal/rbd/encryption.go | 14 +++--- internal/util/crypto.go | 12 +++-- internal/util/cryptsetup.go | 94 ++++++++++++++++++++++++++++--------- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/internal/rbd/encryption.go b/internal/rbd/encryption.go index 4c27bb6e2dfd..01cce671c104 100644 --- a/internal/rbd/encryption.go +++ b/internal/rbd/encryption.go @@ -22,7 +22,6 @@ import ( "fmt" "strconv" "strings" - "time" kmsapi "github.com/ceph/ceph-csi/internal/kms" "github.com/ceph/ceph-csi/internal/util" @@ -475,11 +474,10 @@ func (rv *rbdVolume) RotateEncryptionKey(ctx context.Context) error { // Lock params lockName := rv.VolID + "-mutexlock" lockDesc := "Key rotation mutex lock for " + rv.VolID - lockDuration := 3 * time.Minute lockCookie := rv.VolID + "-enc-key-rotate" // Acquire the exclusive lock based on vol id - lck := lock.NewLock(rv.ioctx, rv.VolID, lockName, lockCookie, lockDesc, lockDuration) + lck := lock.NewLock(rv.ioctx, rv.VolID, lockName, lockCookie, lockDesc, util.CryptSetupExecutionTimeout) err = lck.LockExclusive(ctx) if err != nil { return err @@ -500,8 +498,12 @@ func (rv *rbdVolume) RotateEncryptionKey(ctx context.Context) error { return fmt.Errorf("failed to fetch the current passphrase for %q: %w", rv, err) } + // Create a new luks wrapper + luks := util.NewLuksWrapperWithTimeout(util.CryptSetupExecutionTimeout) + defer luks.Cancel() + // Step 2: Add current key to slot 1 - err = util.LuksAddKey(devicePath, oldPassphrase, oldPassphrase, luksSlot1) + err = luks.LuksAddKey(devicePath, oldPassphrase, oldPassphrase, luksSlot1) if err != nil { return fmt.Errorf("failed to add curr key to luksSlot1: %w", err) } @@ -513,7 +515,7 @@ func (rv *rbdVolume) RotateEncryptionKey(ctx context.Context) error { return fmt.Errorf("failed to generate a new passphrase: %w", err) } - err = util.LuksAddKey(devicePath, oldPassphrase, newPassphrase, luksSlot0) + err = luks.LuksAddKey(devicePath, oldPassphrase, newPassphrase, luksSlot0) if err != nil { return fmt.Errorf("failed to add the new key to luksSlot0: %w", err) } @@ -526,7 +528,7 @@ func (rv *rbdVolume) RotateEncryptionKey(ctx context.Context) error { // Step 5: Remove the old key from slot 1 // We use the newPassphrase to authenticate LUKS here - err = util.LuksRemoveKey(devicePath, newPassphrase, luksSlot1) + err = luks.LuksRemoveKey(devicePath, newPassphrase, luksSlot1) if err != nil { return fmt.Errorf("failed to remove the backup key from luksSlot1: %w", err) } diff --git a/internal/util/crypto.go b/internal/util/crypto.go index 995c8282f6d1..6a981ad211e1 100644 --- a/internal/util/crypto.go +++ b/internal/util/crypto.go @@ -49,6 +49,8 @@ var ( // DEKStore interface. ErrDEKStoreNeeded = errors.New("DEKStore required, use " + "VolumeEncryption.SetDEKStore()") + + luks = NewLuksWrapper() ) type VolumeEncryption struct { @@ -264,7 +266,7 @@ func VolumeMapper(volumeID string) (string, string) { // EncryptVolume encrypts provided device with LUKS. func EncryptVolume(ctx context.Context, devicePath, passphrase string) error { log.DebugLog(ctx, "Encrypting device %q with LUKS", devicePath) - _, stdErr, err := LuksFormat(devicePath, passphrase) + _, stdErr, err := luks.LuksFormat(devicePath, passphrase) if err != nil || stdErr != "" { log.ErrorLog(ctx, "failed to encrypt device %q with LUKS (%v): %s", devicePath, err, stdErr) } @@ -275,7 +277,7 @@ func EncryptVolume(ctx context.Context, devicePath, passphrase string) error { // OpenEncryptedVolume opens volume so that it can be used by the client. func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase string) error { log.DebugLog(ctx, "Opening device %q with LUKS on %q", devicePath, mapperFile) - _, stdErr, err := LuksOpen(devicePath, mapperFile, passphrase) + _, stdErr, err := luks.LuksOpen(devicePath, mapperFile, passphrase) if err != nil || stdErr != "" { log.ErrorLog(ctx, "failed to open device %q (%v): %s", devicePath, err, stdErr) } @@ -286,7 +288,7 @@ func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase // ResizeEncryptedVolume resizes encrypted volume so that it can be used by the client. func ResizeEncryptedVolume(ctx context.Context, mapperFile string) error { log.DebugLog(ctx, "Resizing LUKS device %q", mapperFile) - _, stdErr, err := LuksResize(mapperFile) + _, stdErr, err := luks.LuksResize(mapperFile) if err != nil || stdErr != "" { log.ErrorLog(ctx, "failed to resize LUKS device %q (%v): %s", mapperFile, err, stdErr) } @@ -297,7 +299,7 @@ func ResizeEncryptedVolume(ctx context.Context, mapperFile string) error { // CloseEncryptedVolume closes encrypted volume so it can be detached. func CloseEncryptedVolume(ctx context.Context, mapperFile string) error { log.DebugLog(ctx, "Closing LUKS device %q", mapperFile) - _, stdErr, err := LuksClose(mapperFile) + _, stdErr, err := luks.LuksClose(mapperFile) if err != nil || stdErr != "" { log.ErrorLog(ctx, "failed to close LUKS device %q (%v): %s", mapperFile, err, stdErr) } @@ -320,7 +322,7 @@ func DeviceEncryptionStatus(ctx context.Context, devicePath string) (string, str return devicePath, "", nil } mapPath := strings.TrimPrefix(devicePath, mapperFilePathPrefix+"/") - stdout, stdErr, err := LuksStatus(mapPath) + stdout, stdErr, err := luks.LuksStatus(mapPath) if err != nil || stdErr != "" { log.DebugLog(ctx, "%q is not an active LUKS device (%v): %s", devicePath, err, stdErr) diff --git a/internal/util/cryptsetup.go b/internal/util/cryptsetup.go index 06e2028f3f63..39f4dc4af76e 100644 --- a/internal/util/cryptsetup.go +++ b/internal/util/cryptsetup.go @@ -18,22 +18,59 @@ package util import ( "bytes" + "context" + "errors" "fmt" "os" "os/exec" "strconv" "strings" + "time" "github.com/ceph/ceph-csi/internal/util/file" "github.com/ceph/ceph-csi/internal/util/log" ) -// Limit memory used by Argon2i PBKDF to 32 MiB. -const cryptsetupPBKDFMemoryLimit = 32 << 10 // 32768 KiB +const ( + // Maximum time to wait for cryptsetup commands to complete. + CryptSetupExecutionTimeout = 2*time.Minute + 30*time.Second + + // Limit memory used by Argon2i PBKDF to 32 MiB. + cryptsetupPBKDFMemoryLimit = 32 << 10 // 32768 KiB +) + +// LuksWrapper is a struct that provides a context-aware wrapper around cryptsetup commands. +type LuksWrapper struct { + ctx context.Context + cancelFunc context.CancelFunc +} + +// NewLuksWrapperWithTimeout returns a new LuksWrapper instance with a context that +// has a timeout set. This ensures that any cryptsetup commands executed +// through the LuksWrapper will be terminated if they take longer than the specified timeout. +func NewLuksWrapperWithTimeout(timeout time.Duration) *LuksWrapper { + ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) + + return &LuksWrapper{ctx: ctx, cancelFunc: cancelFunc} +} + +// NewLuksWrapper returns a new LuksWrapper instance with a context that +// has a cancel function. +func NewLuksWrapper() *LuksWrapper { + ctx, cancelFunc := context.WithCancel(context.Background()) + + return &LuksWrapper{ctx: ctx, cancelFunc: cancelFunc} +} + +// Cancel cancels the context associated with the LuksWrapper instance, which will +// cause any cryptsetup commands executed through this instance to be terminated. +func (l *LuksWrapper) Cancel() { + l.cancelFunc() +} // LuksFormat sets up volume as an encrypted LUKS partition. -func LuksFormat(devicePath, passphrase string) (string, string, error) { - return execCryptsetupCommand( +func (l *LuksWrapper) LuksFormat(devicePath, passphrase string) (string, string, error) { + return l.execCryptsetupCommand( &passphrase, "-q", "luksFormat", @@ -49,29 +86,36 @@ func LuksFormat(devicePath, passphrase string) (string, string, error) { } // LuksOpen opens LUKS encrypted partition and sets up a mapping. -func LuksOpen(devicePath, mapperFile, passphrase string) (string, string, error) { +func (l *LuksWrapper) LuksOpen(devicePath, mapperFile, passphrase string) (string, string, error) { // cryptsetup option --disable-keyring (introduced with cryptsetup v2.0.0) // will be ignored with luks1 - return execCryptsetupCommand(&passphrase, "luksOpen", devicePath, mapperFile, "--disable-keyring", "-d", "/dev/stdin") + return l.execCryptsetupCommand( + &passphrase, + "luksOpen", + devicePath, + mapperFile, + "--disable-keyring", + "-d", + "/dev/stdin") } // LuksResize resizes LUKS encrypted partition. -func LuksResize(mapperFile string) (string, string, error) { - return execCryptsetupCommand(nil, "resize", mapperFile) +func (l *LuksWrapper) LuksResize(mapperFile string) (string, string, error) { + return l.execCryptsetupCommand(nil, "resize", mapperFile) } // LuksClose removes existing mapping. -func LuksClose(mapperFile string) (string, string, error) { - return execCryptsetupCommand(nil, "luksClose", mapperFile) +func (l *LuksWrapper) LuksClose(mapperFile string) (string, string, error) { + return l.execCryptsetupCommand(nil, "luksClose", mapperFile) } // LuksStatus returns encryption status of a provided device. -func LuksStatus(mapperFile string) (string, string, error) { - return execCryptsetupCommand(nil, "status", mapperFile) +func (l *LuksWrapper) LuksStatus(mapperFile string) (string, string, error) { + return l.execCryptsetupCommand(nil, "status", mapperFile) } // LuksAddKey adds a new key to the specified slot. -func LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { +func (l *LuksWrapper) LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { passFile, err := file.CreateTempFile("luks-", passphrase) if err != nil { return err @@ -84,7 +128,7 @@ func LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { } defer os.Remove(newPassFile.Name()) - _, stderr, err := execCryptsetupCommand( + _, stderr, err := l.execCryptsetupCommand( nil, "--verbose", "--key-file="+passFile.Name(), @@ -107,7 +151,7 @@ func LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { if strings.Contains(stderr, fmt.Sprintf("Key slot %s is full", slot)) { // The given slot already has a key // Check if it is the one that we want to update with - exists, fErr := LuksVerifyKey(devicePath, newPassphrase, slot) + exists, fErr := l.LuksVerifyKey(devicePath, newPassphrase, slot) if fErr != nil { return fErr } @@ -120,13 +164,13 @@ func LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { // Else, we remove the key from the given slot and add the new one // Note: we use existing passphrase here as we are not yet sure if // the newPassphrase is present in the headers - fErr = LuksRemoveKey(devicePath, passphrase, slot) + fErr = l.LuksRemoveKey(devicePath, passphrase, slot) if fErr != nil { return fErr } // Now the slot is free, add the new key to it - fErr = LuksAddKey(devicePath, passphrase, newPassphrase, slot) + fErr = l.LuksAddKey(devicePath, passphrase, newPassphrase, slot) if fErr != nil { return fErr } @@ -140,14 +184,14 @@ func LuksAddKey(devicePath, passphrase, newPassphrase, slot string) error { } // LuksRemoveKey removes the key by killing the specified slot. -func LuksRemoveKey(devicePath, passphrase, slot string) error { +func (l *LuksWrapper) LuksRemoveKey(devicePath, passphrase, slot string) error { keyFile, err := file.CreateTempFile("luks-", passphrase) if err != nil { return err } defer os.Remove(keyFile.Name()) - _, stderr, err := execCryptsetupCommand( + _, stderr, err := l.execCryptsetupCommand( nil, "--verbose", "--key-file="+keyFile.Name(), @@ -166,7 +210,7 @@ func LuksRemoveKey(devicePath, passphrase, slot string) error { } // LuksVerifyKey verifies that a key exists in a given slot. -func LuksVerifyKey(devicePath, passphrase, slot string) (bool, error) { +func (l *LuksWrapper) LuksVerifyKey(devicePath, passphrase, slot string) (bool, error) { // Create a temp file that we will use to open the device keyFile, err := file.CreateTempFile("luks-", passphrase) if err != nil { @@ -174,7 +218,7 @@ func LuksVerifyKey(devicePath, passphrase, slot string) (bool, error) { } defer os.Remove(keyFile.Name()) - _, stderr, err := execCryptsetupCommand( + _, stderr, err := l.execCryptsetupCommand( nil, "--verbose", "--key-file="+keyFile.Name(), @@ -199,10 +243,10 @@ func LuksVerifyKey(devicePath, passphrase, slot string) (bool, error) { return true, nil } -func execCryptsetupCommand(stdin *string, args ...string) (string, string, error) { +func (l *LuksWrapper) execCryptsetupCommand(stdin *string, args ...string) (string, string, error) { var ( program = "cryptsetup" - cmd = exec.Command(program, args...) // #nosec:G204, commands executing not vulnerable. + cmd = exec.CommandContext(l.ctx, program, args...) // #nosec:G204, commands executing not vulnerable. sanitizedArgs = StripSecretInArgs(args) stdoutBuf bytes.Buffer stderrBuf bytes.Buffer @@ -217,6 +261,10 @@ func execCryptsetupCommand(stdin *string, args ...string) (string, string, error stdout := stdoutBuf.String() stderr := stderrBuf.String() + if errors.Is(l.ctx.Err(), context.DeadlineExceeded) { + return stdout, stderr, fmt.Errorf("timeout occurred while running %s args: %v", program, sanitizedArgs) + } + if err != nil { return stdout, stderr, fmt.Errorf("an error (%v)"+ " occurred while running %s args: %v", err, program, sanitizedArgs)