mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-03 20:44:14 -04:00
AB#2260 Refactor disk-mapper recovery (#82)
* Refactor disk-mapper recovery * Adapt constellation recover command to use new disk-mapper recovery API * Fix Cilium connectivity on rebooting nodes (#89) * Lower CoreDNS reschedule timeout to 10 seconds (#93) Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
a7b20b2a11
commit
8cb155d5c5
40 changed files with 1600 additions and 1130 deletions
44
disk-mapper/internal/mapper/cryptdevice.go
Normal file
44
disk-mapper/internal/mapper/cryptdevice.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package mapper
|
||||
|
||||
import cryptsetup "github.com/martinjungblut/go-cryptsetup"
|
||||
|
||||
type cryptDevice interface {
|
||||
// ActivateByPassphrase activates a device by using a passphrase from a specific keyslot.
|
||||
// Returns nil on success, or an error otherwise.
|
||||
// C equivalent: crypt_activate_by_passphrase
|
||||
ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error
|
||||
// ActivateByVolumeKey activates a device by using a volume key.
|
||||
// Returns nil on success, or an error otherwise.
|
||||
ActivateByVolumeKey(deviceName string, volumeKey string, volumeKeySize int, flags int) error
|
||||
// Deactivate deactivates a device.
|
||||
// Returns nil on success, or an error otherwise.
|
||||
// C equivalent: crypt_deactivate
|
||||
Deactivate(deviceName string) error
|
||||
// Format formats a Device, using a specific device type, and type-independent parameters.
|
||||
// Returns nil on success, or an error otherwise.
|
||||
// C equivalent: crypt_format
|
||||
Format(deviceType cryptsetup.DeviceType, genericParams cryptsetup.GenericParams) error
|
||||
// Free releases crypt device context and used memory.
|
||||
// C equivalent: crypt_free
|
||||
Free() bool
|
||||
// GetUUID gets the device's UUID.
|
||||
// C equivalent: crypt_get_uuid
|
||||
GetUUID() string
|
||||
// Load loads crypt device parameters from the on-disk header.
|
||||
// Returns nil on success, or an error otherwise.
|
||||
// C equivalent: crypt_load
|
||||
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.
|
||||
// C equivalent: crypt_keyslot_add_by_volume_key
|
||||
KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error
|
||||
// Wipe removes existing data and clears the device for use with dm-integrity.
|
||||
// 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
|
||||
}
|
155
disk-mapper/internal/mapper/mapper.go
Normal file
155
disk-mapper/internal/mapper/mapper.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/logger"
|
||||
cryptsetup "github.com/martinjungblut/go-cryptsetup"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// packageLock is needed to block concurrent use of package functions, since libcryptsetup is not thread safe.
|
||||
// See: https://gitlab.com/cryptsetup/cryptsetup/-/issues/710
|
||||
//
|
||||
// https://stackoverflow.com/questions/30553386/cryptsetup-backend-safe-with-multithreading
|
||||
var packageLock = sync.Mutex{}
|
||||
|
||||
// Mapper handles actions for formating and mapping crypt devices.
|
||||
type Mapper struct {
|
||||
device cryptDevice
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
// New creates a new crypt device for the device at path.
|
||||
func New(path string, log *logger.Logger) (*Mapper, error) {
|
||||
packageLock.Lock()
|
||||
device, err := cryptsetup.Init(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing crypt device for disk %q: %w", path, err)
|
||||
}
|
||||
return &Mapper{device: device, log: log}, nil
|
||||
}
|
||||
|
||||
// Close closes and frees memory allocated for the crypt device.
|
||||
func (m *Mapper) Close() error {
|
||||
defer packageLock.Unlock()
|
||||
if m.device.Free() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("unable to close crypt device")
|
||||
}
|
||||
|
||||
// IsLUKSDevice returns true if the device is formatted as a LUKS device.
|
||||
func (m *Mapper) IsLUKSDevice() bool {
|
||||
return m.device.Load(cryptsetup.LUKS2{}) == nil
|
||||
}
|
||||
|
||||
// DiskUUID gets the device's UUID.
|
||||
func (m *Mapper) DiskUUID() string {
|
||||
return strings.ToLower(m.device.GetUUID())
|
||||
}
|
||||
|
||||
// FormatDisk formats the disk and adds passphrase in keyslot 0.
|
||||
func (m *Mapper) FormatDisk(passphrase string) error {
|
||||
luksParams := cryptsetup.LUKS2{
|
||||
SectorSize: 4096,
|
||||
Integrity: "hmac(sha256)",
|
||||
PBKDFType: &cryptsetup.PbkdfType{
|
||||
// Use low memory recommendation from https://datatracker.ietf.org/doc/html/rfc9106#section-7
|
||||
Type: "argon2id",
|
||||
TimeMs: 2000,
|
||||
Iterations: 3,
|
||||
ParallelThreads: 4,
|
||||
MaxMemoryKb: 65536, // ~64MiB
|
||||
},
|
||||
}
|
||||
|
||||
genericParams := cryptsetup.GenericParams{
|
||||
Cipher: "aes",
|
||||
CipherMode: "xts-plain64",
|
||||
VolumeKeySize: 96, // 32*2 bytes for aes-xts-plain64 encryption, 32 bytes for hmac(sha256) integrity
|
||||
}
|
||||
|
||||
if err := m.device.Format(luksParams, genericParams); err != nil {
|
||||
return fmt.Errorf("formatting disk: %w", err)
|
||||
}
|
||||
|
||||
if err := m.device.KeyslotAddByVolumeKey(0, "", passphrase); err != nil {
|
||||
return fmt.Errorf("adding keyslot: %w", err)
|
||||
}
|
||||
|
||||
// wipe using 64MiB block size
|
||||
if err := m.Wipe(67108864); err != nil {
|
||||
return fmt.Errorf("wiping disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapDisk maps a crypt device to /dev/mapper/target using the provided passphrase.
|
||||
func (m *Mapper) MapDisk(target, passphrase string) error {
|
||||
if err := m.device.ActivateByPassphrase(target, 0, passphrase, 0); err != nil {
|
||||
return fmt.Errorf("mapping disk as %q: %w", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmapDisk removes the mapping of target.
|
||||
func (m *Mapper) UnmapDisk(target string) error {
|
||||
return m.device.Deactivate(target)
|
||||
}
|
||||
|
||||
// Wipe overwrites the device with zeros to initialize integrity checksums.
|
||||
func (m *Mapper) Wipe(blockWipeSize int) error {
|
||||
// Activate as temporary device using the internal volume key
|
||||
tmpDevice := "tmp-cryptsetup-integrity"
|
||||
if err := m.device.ActivateByVolumeKey(tmpDevice, "", 0, (cryptsetup.CRYPT_ACTIVATE_PRIVATE | cryptsetup.CRYPT_ACTIVATE_NO_JOURNAL)); err != nil {
|
||||
return fmt.Errorf("activating as temporary device: %w", err)
|
||||
}
|
||||
|
||||
// set progress logging callback once every 30 seconds
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
firstReq := make(chan struct{}, 1)
|
||||
firstReq <- struct{}{}
|
||||
defer ticker.Stop()
|
||||
logProgress := func(size, offset uint64) {
|
||||
prog := (float64(offset) / float64(size)) * 100
|
||||
m.log.With(zap.String("progress", fmt.Sprintf("%.2f%%", prog))).Infof("Wiping disk")
|
||||
}
|
||||
|
||||
progressCallback := func(size, offset uint64) int {
|
||||
select {
|
||||
case <-firstReq:
|
||||
logProgress(size, offset)
|
||||
case <-ticker.C:
|
||||
logProgress(size, offset)
|
||||
default:
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
// wipe the device
|
||||
if err := m.device.Wipe("/dev/mapper/"+tmpDevice, cryptsetup.CRYPT_WIPE_ZERO, 0, 0, blockWipeSize, 0, progressCallback); err != nil {
|
||||
return fmt.Errorf("wiping disk: %w", err)
|
||||
}
|
||||
m.log.With(zap.Duration("duration", time.Since(start))).Infof("Wiping disk successful")
|
||||
|
||||
// Deactivate the temporary device
|
||||
if err := m.device.Deactivate(tmpDevice); err != nil {
|
||||
return fmt.Errorf("deactivating temporary device: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue