mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-12-10 05:31:11 -05:00
cryptsetup: unify code (#2043)
* Add common backend for interacting with cryptsetup * Use common cryptsetup backend in bootstrapper * Use common cryptsetup backend in disk-mapper * Use common cryptsetup backend in csi lib --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
f52c6752e2
commit
ac1128d07f
34 changed files with 1061 additions and 1307 deletions
|
|
@ -3,27 +3,16 @@ load("//bazel/go:go_test.bzl", "go_ld_test", "go_test")
|
|||
|
||||
go_library(
|
||||
name = "diskencryption",
|
||||
srcs = [
|
||||
"diskencryption.go",
|
||||
"diskencryption_cgo.go",
|
||||
"diskencryption_cross.go",
|
||||
],
|
||||
srcs = ["diskencryption.go"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/bootstrapper/internal/diskencryption",
|
||||
target_compatible_with = [
|
||||
"@platforms//os:linux",
|
||||
],
|
||||
visibility = ["//bootstrapper:__subpackages__"],
|
||||
deps = select({
|
||||
"@io_bazel_rules_go//go/platform:android": [
|
||||
"@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:linux": [
|
||||
"@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
deps = [
|
||||
"//internal/cryptsetup",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,68 @@ Copyright (c) Edgeless Systems GmbH
|
|||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package diskencryption handles interaction with a node's state disk.
|
||||
|
||||
This package is not thread safe, since libcryptsetup is not thread safe.
|
||||
There should only be one instance using this package per process.
|
||||
*/
|
||||
// Package diskencryption handles interaction with a node's state disk.
|
||||
package diskencryption
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cryptsetup"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
const (
|
||||
stateMapperDevice = "state"
|
||||
initialKeyPath = "/run/cryptsetup-keys.d/state.key"
|
||||
keyslot = 0
|
||||
)
|
||||
|
||||
// DiskEncryption manages the encrypted state mapper device.
|
||||
type DiskEncryption struct {
|
||||
fs afero.Fs
|
||||
device cryptdevice
|
||||
}
|
||||
|
||||
// New creates a new Cryptsetup.
|
||||
func New() *DiskEncryption {
|
||||
return &DiskEncryption{
|
||||
fs: afero.NewOsFs(),
|
||||
device: cryptsetup.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens the cryptdevice.
|
||||
func (c *DiskEncryption) Open() (free func(), err error) {
|
||||
return c.device.InitByName(stateMapperDevice)
|
||||
}
|
||||
|
||||
// UUID gets the device's UUID.
|
||||
// Only works after calling Open().
|
||||
func (c *DiskEncryption) UUID() (string, error) {
|
||||
return c.device.GetUUID()
|
||||
}
|
||||
|
||||
// UpdatePassphrase switches the initial random passphrase of the mapped crypt device to a permanent passphrase.
|
||||
// Only works after calling Open().
|
||||
func (c *DiskEncryption) UpdatePassphrase(passphrase string) error {
|
||||
initialPassphrase, err := c.getInitialPassphrase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.device.KeyslotChangeByPassphrase(keyslot, keyslot, initialPassphrase, passphrase)
|
||||
}
|
||||
|
||||
// getInitialPassphrase retrieves the initial passphrase used on first boot.
|
||||
func (c *DiskEncryption) getInitialPassphrase() (string, error) {
|
||||
passphrase, err := afero.ReadFile(c.fs, initialKeyPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading first boot encryption passphrase from disk: %w", err)
|
||||
}
|
||||
return string(passphrase), nil
|
||||
}
|
||||
|
||||
type cryptdevice interface {
|
||||
InitByName(name string) (func(), error)
|
||||
GetUUID() (string, error)
|
||||
KeyslotChangeByPassphrase(currentKeyslot int, newKeyslot int, currentPassphrase string, newPassphrase string) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
//go:build linux && cgo
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package diskencryption
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/martinjungblut/go-cryptsetup"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
const (
|
||||
stateMapperDevice = "state"
|
||||
initialKeyPath = "/run/cryptsetup-keys.d/state.key"
|
||||
keyslot = 0
|
||||
)
|
||||
|
||||
var (
|
||||
// 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
|
||||
packageLock = sync.Mutex{}
|
||||
errDeviceNotOpen = errors.New("cryptdevice not open")
|
||||
errDeviceAlreadyOpen = errors.New("cryptdevice already open")
|
||||
)
|
||||
|
||||
// Cryptsetup manages the encrypted state mapper device.
|
||||
type Cryptsetup struct {
|
||||
fs afero.Fs
|
||||
device cryptdevice
|
||||
initByName initByName
|
||||
}
|
||||
|
||||
// New creates a new Cryptsetup.
|
||||
func New() *Cryptsetup {
|
||||
return &Cryptsetup{
|
||||
fs: afero.NewOsFs(),
|
||||
initByName: func(name string) (cryptdevice, error) {
|
||||
return cryptsetup.InitByName(name)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens the cryptdevice.
|
||||
func (c *Cryptsetup) Open() error {
|
||||
packageLock.Lock()
|
||||
defer packageLock.Unlock()
|
||||
if c.device != nil {
|
||||
return errDeviceAlreadyOpen
|
||||
}
|
||||
var err error
|
||||
c.device, err = c.initByName(stateMapperDevice)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing crypt device for mapped device %q: %w", stateMapperDevice, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the cryptdevice.
|
||||
func (c *Cryptsetup) Close() error {
|
||||
packageLock.Lock()
|
||||
defer packageLock.Unlock()
|
||||
if c.device == nil {
|
||||
return errDeviceNotOpen
|
||||
}
|
||||
c.device.Free()
|
||||
c.device = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// UUID gets the device's UUID.
|
||||
// Only works after calling Open().
|
||||
func (c *Cryptsetup) UUID() (string, error) {
|
||||
packageLock.Lock()
|
||||
defer packageLock.Unlock()
|
||||
if c.device == nil {
|
||||
return "", errDeviceNotOpen
|
||||
}
|
||||
uuid := c.device.GetUUID()
|
||||
if uuid == "" {
|
||||
return "", fmt.Errorf("unable to get UUID for mapped device %q", stateMapperDevice)
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// UpdatePassphrase switches the initial random passphrase of the mapped crypt device to a permanent passphrase.
|
||||
// Only works after calling Open().
|
||||
func (c *Cryptsetup) UpdatePassphrase(passphrase string) error {
|
||||
packageLock.Lock()
|
||||
defer packageLock.Unlock()
|
||||
if c.device == nil {
|
||||
return errDeviceNotOpen
|
||||
}
|
||||
initialPassphrase, err := c.getInitialPassphrase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.device.KeyslotChangeByPassphrase(keyslot, keyslot, initialPassphrase, passphrase); err != nil {
|
||||
return fmt.Errorf("changing passphrase for mapped device %q: %w", stateMapperDevice, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getInitialPassphrase retrieves the initial passphrase used on first boot.
|
||||
func (c *Cryptsetup) getInitialPassphrase() (string, error) {
|
||||
passphrase, err := afero.ReadFile(c.fs, initialKeyPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading first boot encryption passphrase from disk: %w", err)
|
||||
}
|
||||
return string(passphrase), nil
|
||||
}
|
||||
|
||||
type cryptdevice interface {
|
||||
GetUUID() string
|
||||
KeyslotChangeByPassphrase(currentKeyslot int, newKeyslot int, currentPassphrase string, newPassphrase string) error
|
||||
Free() bool
|
||||
}
|
||||
|
||||
type initByName func(name string) (cryptdevice, error)
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
//go:build !linux || !cgo
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package diskencryption handles interaction with a node's state disk.
|
||||
|
||||
This package is not thread safe, since libcryptsetup is not thread safe.
|
||||
There should only be one instance using this package per process.
|
||||
*/
|
||||
package diskencryption
|
||||
|
||||
import "errors"
|
||||
|
||||
// Cryptsetup manages the encrypted state mapper device.
|
||||
type Cryptsetup struct{}
|
||||
|
||||
// New creates a new Cryptsetup.
|
||||
// This function panics if CGO is disabled.
|
||||
func New() *Cryptsetup {
|
||||
return &Cryptsetup{}
|
||||
}
|
||||
|
||||
// Open opens the cryptdevice.
|
||||
// This function does nothing if CGO is disabled.
|
||||
func (c *Cryptsetup) Open() error {
|
||||
return errors.New("using cryptsetup requires building with CGO")
|
||||
}
|
||||
|
||||
// Close closes the cryptdevice.
|
||||
// This function errors if CGO is disabled.
|
||||
func (c *Cryptsetup) Close() error {
|
||||
return errors.New("using cryptsetup requires building with CGO")
|
||||
}
|
||||
|
||||
// UUID gets the device's UUID.
|
||||
// This function errors if CGO is disabled.
|
||||
func (c *Cryptsetup) UUID() (string, error) {
|
||||
return "", errors.New("using cryptsetup requires building with CGO")
|
||||
}
|
||||
|
||||
// UpdatePassphrase switches the initial random passphrase of the mapped crypt device to a permanent passphrase.
|
||||
// This function errors if CGO is disabled.
|
||||
func (c *Cryptsetup) UpdatePassphrase(_ string) error {
|
||||
return errors.New("using cryptsetup requires building with CGO")
|
||||
}
|
||||
|
|
@ -23,98 +23,6 @@ func TestMain(m *testing.M) {
|
|||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestOpenClose(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
initByNameErr error
|
||||
operations []string
|
||||
wantErr bool
|
||||
}{
|
||||
"open and close work": {
|
||||
operations: []string{"open", "close"},
|
||||
},
|
||||
"opening twice fails": {
|
||||
operations: []string{"open", "open"},
|
||||
wantErr: true,
|
||||
},
|
||||
"closing first fails": {
|
||||
operations: []string{"close"},
|
||||
wantErr: true,
|
||||
},
|
||||
"initByName failure detected": {
|
||||
initByNameErr: errors.New("initByNameErr"),
|
||||
operations: []string{"open"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
crypt := Cryptsetup{
|
||||
fs: afero.NewMemMapFs(),
|
||||
initByName: func(name string) (cryptdevice, error) {
|
||||
return &stubCryptdevice{}, tc.initByNameErr
|
||||
},
|
||||
}
|
||||
|
||||
err := executeOperations(&crypt, tc.operations)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
open bool
|
||||
wantUUID string
|
||||
wantErr bool
|
||||
}{
|
||||
"getting uuid works": {
|
||||
open: true,
|
||||
wantUUID: "uuid",
|
||||
},
|
||||
"getting uuid on closed device fails": {
|
||||
wantErr: true,
|
||||
},
|
||||
"empty uuid is detected": {
|
||||
open: true,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
crypt := Cryptsetup{
|
||||
fs: afero.NewMemMapFs(),
|
||||
initByName: func(name string) (cryptdevice, error) {
|
||||
return &stubCryptdevice{uuid: tc.wantUUID}, nil
|
||||
},
|
||||
}
|
||||
|
||||
if tc.open {
|
||||
require.NoError(crypt.Open())
|
||||
defer crypt.Close()
|
||||
}
|
||||
uuid, err := crypt.UUID()
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantUUID, uuid)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePassphrase(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
writePassphrase bool
|
||||
|
|
@ -126,9 +34,6 @@ func TestUpdatePassphrase(t *testing.T) {
|
|||
writePassphrase: true,
|
||||
open: true,
|
||||
},
|
||||
"updating passphrase on closed device fails": {
|
||||
wantErr: true,
|
||||
},
|
||||
"reading initial passphrase can fail": {
|
||||
open: true,
|
||||
wantErr: true,
|
||||
|
|
@ -152,17 +57,11 @@ func TestUpdatePassphrase(t *testing.T) {
|
|||
require.NoError(afero.WriteFile(fs, initialKeyPath, []byte("key"), 0o777))
|
||||
}
|
||||
|
||||
crypt := Cryptsetup{
|
||||
fs: fs,
|
||||
initByName: func(name string) (cryptdevice, error) {
|
||||
return &stubCryptdevice{keyslotChangeErr: tc.keyslotChangeByPassphraseErr}, nil
|
||||
},
|
||||
crypt := DiskEncryption{
|
||||
fs: fs,
|
||||
device: &stubCryptdevice{keyslotChangeErr: tc.keyslotChangeByPassphraseErr},
|
||||
}
|
||||
|
||||
if tc.open {
|
||||
require.NoError(crypt.Open())
|
||||
defer crypt.Close()
|
||||
}
|
||||
err := crypt.UpdatePassphrase("new-key")
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
|
@ -173,37 +72,24 @@ func TestUpdatePassphrase(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func executeOperations(crypt *Cryptsetup, operations []string) error {
|
||||
for _, operation := range operations {
|
||||
var err error
|
||||
switch operation {
|
||||
case "open":
|
||||
err = crypt.Open()
|
||||
case "close":
|
||||
err = crypt.Close()
|
||||
default:
|
||||
panic("unknown operation")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubCryptdevice struct {
|
||||
uuid string
|
||||
uuidErr error
|
||||
keyslotChangeErr error
|
||||
}
|
||||
|
||||
func (s *stubCryptdevice) GetUUID() string {
|
||||
return s.uuid
|
||||
func (s *stubCryptdevice) InitByName(_ string) (func(), error) {
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (s *stubCryptdevice) GetUUID() (string, error) {
|
||||
return s.uuid, s.uuidErr
|
||||
}
|
||||
|
||||
func (s *stubCryptdevice) KeyslotChangeByPassphrase(_, _ int, _, _ string) error {
|
||||
return s.keyslotChangeErr
|
||||
}
|
||||
|
||||
func (s *stubCryptdevice) Free() bool {
|
||||
return false
|
||||
func (s *stubCryptdevice) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue