diff --git a/internal/rbd/encryption.go b/internal/rbd/encryption.go index 4c27bb6e2dfd..d4cdfcb281a3 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() + 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..aa4b6bc6ffb8 100644 --- a/internal/util/cryptsetup.go +++ b/internal/util/cryptsetup.go @@ -18,22 +18,58 @@ 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 to CryptSetupExecutionTimeout. This ensures that any cryptsetup +// commands executed through the LuksWrapper will be terminated if they take longer +// than the specified timeout. +func NewLuksWrapperWithTimeout() *LuksWrapper { + ctx, cancelFunc := context.WithTimeout(context.Background(), CryptSetupExecutionTimeout) + 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 +85,29 @@ 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 +120,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 +143,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 +156,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 +176,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 +202,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 +210,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 +235,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 +253,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)