2023-07-17 07:55:31 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
Package diskencryption uses libcryptsetup to format and map crypt devices.
|
|
|
|
|
|
|
|
This is used by the disk-mapper to set up a node's state disk.
|
|
|
|
|
|
|
|
All interaction with libcryptsetup should be done here.
|
|
|
|
*/
|
|
|
|
package diskencryption
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-02-08 09:20:01 -05:00
|
|
|
"log/slog"
|
2023-07-17 07:55:31 -04:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/cryptsetup"
|
|
|
|
)
|
|
|
|
|
|
|
|
// DiskEncryption handles actions for formatting and mapping crypt devices.
|
|
|
|
type DiskEncryption struct {
|
2023-07-18 10:20:03 -04:00
|
|
|
device cryptDevice
|
|
|
|
devicePath string
|
2024-02-08 09:20:01 -05:00
|
|
|
log *slog.Logger
|
2023-07-17 07:55:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new crypt device for the device at path.
|
2024-02-08 09:20:01 -05:00
|
|
|
func New(path string, log *slog.Logger) (*DiskEncryption, func(), error) {
|
2023-07-17 07:55:31 -04:00
|
|
|
device := cryptsetup.New()
|
2023-07-18 10:20:03 -04:00
|
|
|
_, err := device.Init(path)
|
2023-07-17 07:55:31 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("initializing crypt device for disk %q: %w", path, err)
|
|
|
|
}
|
2023-07-18 10:20:03 -04:00
|
|
|
d := &DiskEncryption{device: device, devicePath: path, log: log}
|
|
|
|
return d, d.free, nil
|
2023-07-17 07:55:31 -04:00
|
|
|
}
|
|
|
|
|
2023-07-18 10:20:03 -04:00
|
|
|
// IsInitialized returns true if the device is formatted as a LUKS device,
|
|
|
|
// and has been successfully initialized before (successfully joined a cluster).
|
|
|
|
func (d *DiskEncryption) IsInitialized() bool {
|
|
|
|
if err := d.device.LoadLUKS2(); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.device.ConstellationStateDiskTokenIsInitialized()
|
2023-07-17 07:55:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// DiskUUID gets the device's UUID.
|
|
|
|
func (d *DiskEncryption) DiskUUID() (string, error) {
|
|
|
|
return d.device.GetUUID()
|
|
|
|
}
|
|
|
|
|
|
|
|
// FormatDisk formats the disk and adds passphrase in keyslot 0.
|
|
|
|
func (d *DiskEncryption) FormatDisk(passphrase string) error {
|
2023-07-18 10:20:03 -04:00
|
|
|
// Successfully calling LoadLUKS2() before FormatDisk() will cause format to fail.
|
|
|
|
// To make sure format is idempotent, we need to run it on a freshly initialized device.
|
|
|
|
// Therefore we free the device and reinitialize it.
|
|
|
|
d.free()
|
|
|
|
if _, err := d.device.Init(d.devicePath); err != nil {
|
|
|
|
return fmt.Errorf("re-initializing crypt device for disk %q: %w", d.devicePath, err)
|
|
|
|
}
|
|
|
|
|
2023-07-17 07:55:31 -04:00
|
|
|
if err := d.device.Format(cryptsetup.FormatIntegrity); err != nil {
|
|
|
|
return fmt.Errorf("formatting disk: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := d.device.KeyslotAddByVolumeKey(0, "", passphrase); err != nil {
|
|
|
|
return fmt.Errorf("adding keyslot: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// wipe using 64MiB block size
|
|
|
|
if err := d.Wipe(67108864); err != nil {
|
|
|
|
return fmt.Errorf("wiping disk: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-07-18 10:20:03 -04:00
|
|
|
if err := d.device.SetConstellationStateDiskToken(cryptsetup.SetDiskNotInitialized); err != nil {
|
|
|
|
return fmt.Errorf("setting disk token: %w", err)
|
|
|
|
}
|
2023-07-17 07:55:31 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MapDisk maps a crypt device to /dev/mapper/target using the provided passphrase.
|
|
|
|
func (d *DiskEncryption) MapDisk(target, passphrase string) error {
|
|
|
|
if err := d.device.ActivateByPassphrase(target, 0, passphrase, cryptsetup.ReadWriteQueueBypass); err != nil {
|
|
|
|
return fmt.Errorf("mapping disk as %q: %w", target, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmapDisk removes the mapping of target.
|
|
|
|
func (d *DiskEncryption) UnmapDisk(target string) error {
|
|
|
|
return d.device.Deactivate(target)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wipe overwrites the device with zeros to initialize integrity checksums.
|
|
|
|
func (d *DiskEncryption) Wipe(blockWipeSize int) error {
|
|
|
|
logProgress := func(size, offset uint64) {
|
|
|
|
prog := (float64(offset) / float64(size)) * 100
|
2024-02-08 09:20:01 -05:00
|
|
|
d.log.With(slog.String("progress", fmt.Sprintf("%.2f%%", prog))).Info("Wiping disk")
|
2023-07-17 07:55:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
// wipe the device
|
|
|
|
if err := d.device.Wipe("integrity", blockWipeSize, 0, logProgress, 30*time.Second); err != nil {
|
|
|
|
return fmt.Errorf("wiping disk: %w", err)
|
|
|
|
}
|
2024-02-08 09:20:01 -05:00
|
|
|
d.log.With(slog.Duration("duration", time.Since(start))).Info("Wiping disk successful")
|
2023-07-17 07:55:31 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-18 10:20:03 -04:00
|
|
|
func (d *DiskEncryption) free() {
|
|
|
|
d.device.Free()
|
|
|
|
}
|
|
|
|
|
2023-07-17 07:55:31 -04:00
|
|
|
type cryptDevice interface {
|
|
|
|
ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error
|
|
|
|
ActivateByVolumeKey(deviceName string, volumeKey string, volumeKeySize int, flags int) error
|
|
|
|
Deactivate(deviceName string) error
|
|
|
|
Format(integrity bool) error
|
2023-07-18 10:20:03 -04:00
|
|
|
Free()
|
2023-07-17 07:55:31 -04:00
|
|
|
GetUUID() (string, error)
|
2023-07-18 10:20:03 -04:00
|
|
|
Init(path string) (func(), error)
|
2023-07-17 07:55:31 -04:00
|
|
|
LoadLUKS2() error
|
|
|
|
KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error
|
2023-07-18 10:20:03 -04:00
|
|
|
SetConstellationStateDiskToken(diskIsInitialized bool) error
|
|
|
|
ConstellationStateDiskTokenIsInitialized() bool
|
2023-07-17 07:55:31 -04:00
|
|
|
Wipe(name string, wipeBlockSize int, flags int, logCallback func(size, offset uint64), logFrequency time.Duration) error
|
|
|
|
}
|