From f321a8bd8ffb89ee373dfaa5264ffb43f97f2ef7 Mon Sep 17 00:00:00 2001 From: mostlikelee Date: Tue, 19 Nov 2024 20:34:04 -0700 Subject: [PATCH] add salt and keyslot --- orbit/pkg/luks/luks.go | 17 +++++-- orbit/pkg/luks/luks_linux.go | 87 +++++++++++++++++++++++++--------- orbit/pkg/luks/luks_stub.go | 2 +- server/service/orbit_client.go | 10 ++-- 4 files changed, 86 insertions(+), 30 deletions(-) diff --git a/orbit/pkg/luks/luks.go b/orbit/pkg/luks/luks.go index 4ef459af1f06..6376b24eaa83 100644 --- a/orbit/pkg/luks/luks.go +++ b/orbit/pkg/luks/luks.go @@ -1,4 +1,4 @@ -package luks_runner +package luks import ( "github.com/fleetdm/fleet/v4/orbit/pkg/dialog" @@ -14,8 +14,19 @@ type LuksRunner struct { } type LuksResponse struct { - Key string `json:"key"` - Err string `json:"err"` + // Passphrase is a newly created passphrase generated by fleetd for securing the LUKS volume. + // This passphrase will be securely escrowed to the server. + Passphrase string + + // KeySlot specifies the LUKS key slot where this new passphrase was created. + // It is currently not used, but may be useful in the future for passphrase rotation. + KeySlot *uint + + // Salt is the salt used to generate the LUKS key. + Salt string + + // Err is the error message that occurred during the escrow process. + Err string } func New(escrower KeyEscrower, notifier dialog.Dialog) *LuksRunner { diff --git a/orbit/pkg/luks/luks_linux.go b/orbit/pkg/luks/luks_linux.go index f9bfbc0f076c..c18776108501 100644 --- a/orbit/pkg/luks/luks_linux.go +++ b/orbit/pkg/luks/luks_linux.go @@ -1,13 +1,15 @@ //go:build linux -package luks_runner +package luks import ( "context" "crypto/rand" + "encoding/json" "errors" "fmt" "math/big" + "os/exec" "regexp" "time" @@ -16,7 +18,7 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/rs/zerolog/log" "github.com/siderolabs/go-blockdevice/v2/encryption" - "github.com/siderolabs/go-blockdevice/v2/encryption/luks" + luksdevice "github.com/siderolabs/go-blockdevice/v2/encryption/luks" ) const ( @@ -40,13 +42,27 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error { return nil } + devicePath, err := lvm.FindRootDisk() + if err != nil { + return fmt.Errorf("Failed to find LUKS Root Partition: %w", err) + } + var response LuksResponse - key, err := lr.getEscrowKey(ctx) + key, keyslot, err := lr.getEscrowKey(ctx, devicePath) if err != nil { response.Err = err.Error() } - response.Key = string(key) + response.Passphrase = string(key) + response.KeySlot = keyslot + + if keyslot != nil { + salt, err := getSaltforKeySlot(ctx, devicePath, *keyslot) + if err != nil { + return fmt.Errorf("Failed to get salt for key slot: %w", err) + } + response.Salt = salt + } if err := lr.escrower.SendLinuxKeyEscrowResponse(response); err != nil { if err := lr.infoPrompt(ctx, infoFailedTitle, infoFailedText); err != nil { @@ -70,31 +86,26 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error { return nil } -func (lr *LuksRunner) getEscrowKey(ctx context.Context) ([]byte, error) { - devicePath, err := lvm.FindRootDisk() - if err != nil { - return nil, fmt.Errorf("Failed to find LUKS Root Partition: %w", err) - } - - device := luks.New(luks.AESXTSPlain64Cipher) +func (lr *LuksRunner) getEscrowKey(ctx context.Context, devicePath string) ([]byte, *uint, error) { + device := luksdevice.New(luksdevice.AESXTSPlain64Cipher) // Prompt user for existing LUKS passphrase passphrase, err := lr.entryPrompt(ctx, entryDialogTitle, entryDialogText) if err != nil { - return nil, fmt.Errorf("Failed to show passphrase entry prompt: %w", err) + return nil, nil, fmt.Errorf("Failed to show passphrase entry prompt: %w", err) } // Validate the passphrase for { valid, err := lr.passphraseIsValid(ctx, device, devicePath, passphrase) if err != nil { - return nil, fmt.Errorf("Failed validating passphrase: %w", err) + return nil, nil, fmt.Errorf("Failed validating passphrase: %w", err) } if !valid { passphrase, err = lr.entryPrompt(ctx, entryDialogTitle, retryEntryDialogText) if err != nil { - return nil, fmt.Errorf("Failed re-prompting for passphrase: %w", err) + return nil, nil, fmt.Errorf("Failed re-prompting for passphrase: %w", err) } continue } @@ -104,41 +115,41 @@ func (lr *LuksRunner) getEscrowKey(ctx context.Context) ([]byte, error) { if len(passphrase) == 0 { log.Debug().Msg("Passphrase is empty, no password supplied, dialog was canceled, or timed out") - return nil, nil + return nil, nil, nil } escrowPassphrase, err := generateRandomPassphrase() if err != nil { - return nil, fmt.Errorf("Failed to generate random passphrase: %w", err) + return nil, nil, fmt.Errorf("Failed to generate random passphrase: %w", err) } // Create a new key slot, error if all key slots are full // keySlot 0 is assumed to be the user's passphrase // so we start at 1 - keySlot := 1 + var keySlot uint = 1 for { if keySlot == maxKeySlots { - return nil, errors.New("all LUKS key slots are full") + return nil, nil, errors.New("all LUKS key slots are full") } userKey := encryption.NewKey(0, passphrase) - escrowKey := encryption.NewKey(keySlot, escrowPassphrase) + escrowKey := encryption.NewKey(int(keySlot), escrowPassphrase) if err := device.AddKey(ctx, devicePath, userKey, escrowKey); err != nil { if ErrKeySlotFull.MatchString(err.Error()) { keySlot++ continue } - return nil, fmt.Errorf("Failed to add key: %w", err) + return nil, nil, fmt.Errorf("Failed to add key: %w", err) } break } - return escrowPassphrase, nil + return escrowPassphrase, &keySlot, nil } -func (lr *LuksRunner) passphraseIsValid(ctx context.Context, device *luks.LUKS, devicePath string, passphrase []byte) (bool, error) { +func (lr *LuksRunner) passphraseIsValid(ctx context.Context, device *luksdevice.LUKS, devicePath string, passphrase []byte) (bool, error) { if len(passphrase) == 0 { return false, nil } @@ -219,3 +230,35 @@ func (lr *LuksRunner) infoPrompt(ctx context.Context, title, text string) error return nil } + +type LuksDump struct { + Keyslots map[string]Keyslot `json:"keyslots"` +} + +type Keyslot struct { + KDF KDF `json:"kdf"` +} + +type KDF struct { + Salt string `json:"salt"` +} + +func getSaltforKeySlot(ctx context.Context, devicePath string, keySlot uint) (string, error) { + cmd := exec.CommandContext(ctx, "cryptsetup", "luksDump", "--dump-json-metadata", devicePath) + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("Failed to run cryptsetup luksDump: %w", err) + } + + var dump LuksDump + if err := json.Unmarshal(output, &dump); err != nil { + return "", fmt.Errorf("Failed to unmarshal luksDump output: %w", err) + } + + slot, ok := dump.Keyslots[fmt.Sprintf("%d", keySlot)] + if !ok { + return "", errors.New("key slot not found") + } + + return slot.KDF.Salt, nil +} diff --git a/orbit/pkg/luks/luks_stub.go b/orbit/pkg/luks/luks_stub.go index c655a5753891..4358df26c744 100644 --- a/orbit/pkg/luks/luks_stub.go +++ b/orbit/pkg/luks/luks_stub.go @@ -1,7 +1,7 @@ //go:build !linux // +build !linux -package luks_runner +package luks import ( "github.com/fleetdm/fleet/v4/server/fleet" diff --git a/server/service/orbit_client.go b/server/service/orbit_client.go index c9a4543dc464..912d47c86f23 100644 --- a/server/service/orbit_client.go +++ b/server/service/orbit_client.go @@ -22,7 +22,7 @@ import ( "github.com/fleetdm/fleet/v4/orbit/pkg/constant" "github.com/fleetdm/fleet/v4/orbit/pkg/logging" - luks_runner "github.com/fleetdm/fleet/v4/orbit/pkg/luks" + "github.com/fleetdm/fleet/v4/orbit/pkg/luks" "github.com/fleetdm/fleet/v4/orbit/pkg/platform" "github.com/fleetdm/fleet/v4/pkg/retry" "github.com/fleetdm/fleet/v4/server/fleet" @@ -670,15 +670,17 @@ func (oc *OrbitClient) GetSetupExperienceStatus() (*fleet.SetupExperienceStatusP return resp.Results, nil } -func (oc *OrbitClient) SendLinuxKeyEscrowResponse(lr luks_runner.LuksResponse) error { +func (oc *OrbitClient) SendLinuxKeyEscrowResponse(lr luks.LuksResponse) error { verb, path := "POST", "/api/fleet/orbit/luks_data" var resp orbitPostLUKSResponse if err := oc.authenticatedRequest(verb, path, &orbitPostLUKSRequest{ - Passphrase: lr.Key, - SlotKey: "1", + Passphrase: lr.Passphrase, + KeySlot: lr.KeySlot, + Salt: lr.Salt, ClientError: lr.Err, }, &resp); err != nil { return err } + return nil }