Skip to content

Commit

Permalink
add salt and keyslot
Browse files Browse the repository at this point in the history
  • Loading branch information
mostlikelee committed Nov 20, 2024
1 parent 3db95ae commit f321a8b
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 30 deletions.
17 changes: 14 additions & 3 deletions orbit/pkg/luks/luks.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package luks_runner
package luks

import (
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
Expand All @@ -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 {
Expand Down
87 changes: 65 additions & 22 deletions orbit/pkg/luks/luks_linux.go
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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 (
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)

Check failure on line 136 in orbit/pkg/luks/luks_linux.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

G115: integer overflow conversion uint -> int (gosec)

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
}
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion orbit/pkg/luks/luks_stub.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build !linux
// +build !linux

package luks_runner
package luks

import (
"github.com/fleetdm/fleet/v4/server/fleet"
Expand Down
10 changes: 6 additions & 4 deletions server/service/orbit_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

0 comments on commit f321a8b

Please sign in to comment.