Skip to content

Commit

Permalink
rbd: Add timeout for cryptsetup commands
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
black-dragon74 committed Oct 28, 2024
1 parent 8ddb615 commit 911d52f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 34 deletions.
15 changes: 9 additions & 6 deletions internal/rbd/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"fmt"
"strconv"
"strings"
"time"

kmsapi "github.com/ceph/ceph-csi/internal/kms"
"github.com/ceph/ceph-csi/internal/util"
Expand Down Expand Up @@ -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
Expand All @@ -500,8 +498,13 @@ 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
timeoutCtx, cancel := context.WithTimeout(ctx, util.CryptSetupExecutionTimeout)
luks := util.NewLuksWrapperWithContext(timeoutCtx)
defer 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)
}
Expand All @@ -513,7 +516,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)
}
Expand All @@ -526,7 +529,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)
}
Expand Down
12 changes: 7 additions & 5 deletions internal/util/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var (
// DEKStore interface.
ErrDEKStoreNeeded = errors.New("DEKStore required, use " +
"VolumeEncryption.SetDEKStore()")

luks = NewLuksWrapperWithContext(context.Background())
)

type VolumeEncryption struct {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)

Expand Down
76 changes: 53 additions & 23 deletions internal/util/cryptsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,41 @@ 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
}

// NewLuksWrapperWithContext creates a new LuksWrapper instance with the provided context.
// The context is used to control the lifetime of the cryptsetup commands executed by the LuksWrapper.
func NewLuksWrapperWithContext(ctx context.Context) *LuksWrapper {
return &LuksWrapper{ctx: ctx}
}

// 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",
Expand All @@ -49,29 +68,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
Expand All @@ -84,7 +110,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(),
Expand All @@ -107,7 +133,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
}
Expand All @@ -120,13 +146,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
}
Expand All @@ -140,14 +166,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(),
Expand All @@ -166,15 +192,15 @@ 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 {
return false, err
}
defer os.Remove(keyFile.Name())

_, stderr, err := execCryptsetupCommand(
_, stderr, err := l.execCryptsetupCommand(
nil,
"--verbose",
"--key-file="+keyFile.Name(),
Expand All @@ -199,10 +225,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
Expand All @@ -217,6 +243,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)
Expand Down

0 comments on commit 911d52f

Please sign in to comment.