Skip to content

Commit

Permalink
linux key escrow progress windows (#24069)
Browse files Browse the repository at this point in the history
  • Loading branch information
mostlikelee authored Nov 22, 2024
1 parent 5dab4f5 commit 7b932e8
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 84 deletions.
13 changes: 0 additions & 13 deletions orbit/pkg/execuser/execuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// SYSTEM service on Windows) as the current login user.
package execuser

import "context"

type eopts struct {
env [][2]string
args [][2]string
Expand Down Expand Up @@ -51,14 +49,3 @@ func RunWithOutput(path string, opts ...Option) (output []byte, exitCode int, er
}
return runWithOutput(path, o)
}

// RunWithWait runs an application as the current login user and waits for it to finish
// or to be canceled by the context. Canceling the context will not return an error.
// It assumes the caller is running with high privileges (root on UNIX).
func RunWithWait(ctx context.Context, path string, opts ...Option) error {
var o eopts
for _, fn := range opts {
fn(&o)
}
return runWithWait(ctx, path, o)
}
7 changes: 2 additions & 5 deletions orbit/pkg/execuser/execuser_darwin.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package execuser

import (
"context"
"errors"
"fmt"
"io"
Expand All @@ -10,6 +9,8 @@ import (
)

// run uses macOS open command to start application as the current login user.
// Note that the child process spawns a new process in user space and thus it is not
// effective to add a context to this function to cancel the child process.
func run(path string, opts eopts) (lastLogs string, err error) {
info, err := os.Stat(path)
if err != nil {
Expand Down Expand Up @@ -53,7 +54,3 @@ func run(path string, opts eopts) (lastLogs string, err error) {
func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err error) {
return nil, 0, errors.New("not implemented")
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
return errors.New("not implemented")
}
38 changes: 6 additions & 32 deletions orbit/pkg/execuser/execuser_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package execuser
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -33,6 +32,12 @@ func run(path string, opts eopts) (lastLogs string, err error) {
path,
)

if len(opts.args) > 0 {
for _, arg := range opts.args {
args = append(args, arg[0], arg[1])
}
}

cmd := exec.Command("sudo", args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
Expand Down Expand Up @@ -74,37 +79,6 @@ func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err er
return output, exitCode, nil
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
args, err := getUserAndDisplayArgs(path, opts)
if err != nil {
return fmt.Errorf("get args: %w", err)
}

args = append(args, path)

if len(opts.args) > 0 {
for _, arg := range opts.args {
args = append(args, arg[0], arg[1])
}
}

cmd := exec.CommandContext(ctx, "sudo", args...)
log.Printf("cmd=%s", cmd.String())

if err := cmd.Start(); err != nil {
return fmt.Errorf("cmd start %q: %w", path, err)
}

if err := cmd.Wait(); err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
return nil
}
return fmt.Errorf("cmd wait %q: %w", path, err)
}

return nil
}

func getUserAndDisplayArgs(path string, opts eopts) ([]string, error) {
user, err := getLoginUID()
if err != nil {
Expand Down
5 changes: 0 additions & 5 deletions orbit/pkg/execuser/execuser_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package execuser
// To view what was modified/added, you can use the execuser_windows_diff.sh script.

import (
"context"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -122,10 +121,6 @@ func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err er
return nil, 0, errors.New("not implemented")
}

func runWithWait(ctx context.Context, path string, opts eopts) error {
return errors.New("not implemented")
}

// getCurrentUserSessionId will attempt to resolve
// the session ID of the user currently active on
// the system.
Expand Down
70 changes: 54 additions & 16 deletions orbit/pkg/luks/luks_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ const (
entryDialogTitle = "Enter disk encryption passphrase"
entryDialogText = "Passphrase:"
retryEntryDialogText = "Passphrase incorrect. Please try again."
infoFailedTitle = "Encryption key escrow"
infoTitle = "Disk encryption"
infoFailedText = "Failed to escrow key. Please try again later."
infoSuccessTitle = "Encryption key escrow"
infoSuccessText = "Key escrowed successfully."
infoSuccessText = "Success! Now, return to your browser window and follow the instructions to verify disk encryption."
timeoutMessage = "Please visit Fleet Desktop > My device and click Create key"
maxKeySlots = 8
userKeySlot = 0 // Key slot 0 is assumed to be the location of the user's passphrase
)
Expand All @@ -53,6 +53,11 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
response.Err = err.Error()
}

if len(key) == 0 && err == nil {
// dialog was canceled or timed out
return nil
}

response.Passphrase = string(key)
response.KeySlot = keyslot

Expand All @@ -76,22 +81,22 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
}

// Show error in dialog
if err := lr.infoPrompt(ctx, infoFailedTitle, infoFailedText); err != nil {
if err := lr.infoPrompt(ctx, infoTitle, infoFailedText); err != nil {
log.Info().Err(err).Msg("failed to show failed escrow key dialog")
}

return fmt.Errorf("escrower escrowKey err: %w", err)
}

if response.Err != "" {
if err := lr.infoPrompt(ctx, infoFailedTitle, response.Err); err != nil {
if err := lr.infoPrompt(ctx, infoTitle, response.Err); err != nil {
log.Info().Err(err).Msg("failed to show response error dialog")
}
return fmt.Errorf("error getting linux escrow key: %s", response.Err)
}

// Show success dialog
if err := lr.infoPrompt(ctx, infoSuccessTitle, infoSuccessText); err != nil {
if err := lr.infoPrompt(ctx, infoTitle, infoSuccessText); err != nil {
log.Info().Err(err).Msg("failed to show success escrow key dialog")
}

Expand All @@ -108,6 +113,19 @@ func (lr *LuksRunner) getEscrowKey(ctx context.Context, devicePath string) ([]by
return nil, nil, fmt.Errorf("Failed to show passphrase entry prompt: %w", err)
}

if len(passphrase) == 0 {
log.Debug().Msg("Passphrase is empty, no password supplied, dialog was canceled, or timed out")
return nil, nil, nil
}

err = lr.notifier.ShowProgress(ctx, dialog.ProgressOptions{
Title: infoTitle,
Text: "Validating passphrase...",
})
if err != nil {
log.Error().Err(err).Msg("failed to show progress dialog")
}

// Validate the passphrase
for {
valid, err := lr.passphraseIsValid(ctx, device, devicePath, passphrase, userKeySlot)
Expand All @@ -123,11 +141,27 @@ func (lr *LuksRunner) getEscrowKey(ctx context.Context, devicePath string) ([]by
if err != nil {
return nil, nil, fmt.Errorf("Failed re-prompting for passphrase: %w", err)
}

if len(passphrase) == 0 {
log.Debug().Msg("Passphrase is empty, no password supplied, dialog was canceled, or timed out")
return nil, nil, nil
}

err = lr.notifier.ShowProgress(ctx, dialog.ProgressOptions{
Title: infoTitle,
Text: "Validating passphrase...",
})
if err != nil {
log.Error().Err(err).Msg("failed to show progress dialog after retry")
}
}

if len(passphrase) == 0 {
log.Debug().Msg("Passphrase is empty, no password supplied, dialog was canceled, or timed out")
return nil, nil, nil
err = lr.notifier.ShowProgress(ctx, dialog.ProgressOptions{
Title: infoTitle,
Text: "Key escrow in progress...",
})
if err != nil {
log.Error().Err(err).Msg("failed to show progress dialog")
}

escrowPassphrase, err := generateRandomPassphrase()
Expand Down Expand Up @@ -216,14 +250,18 @@ func (lr *LuksRunner) entryPrompt(ctx context.Context, title, text string) ([]by
TimeOut: 1 * time.Minute,
})
if err != nil {
switch err {
case dialog.ErrCanceled:
switch {
case errors.Is(err, dialog.ErrCanceled):
log.Debug().Msg("end user canceled key escrow dialog")
return nil, nil
case dialog.ErrTimeout:
case errors.Is(err, dialog.ErrTimeout):
log.Debug().Msg("key escrow dialog timed out")
err := lr.infoPrompt(ctx, infoTitle, timeoutMessage)
if err != nil {
log.Info().Err(err).Msg("failed to show timeout dialog")
}
return nil, nil
case dialog.ErrUnknown:
case errors.Is(err, dialog.ErrUnknown):
return nil, err
default:
return nil, err
Expand All @@ -237,11 +275,11 @@ func (lr *LuksRunner) infoPrompt(ctx context.Context, title, text string) error
err := lr.notifier.ShowInfo(ctx, dialog.InfoOptions{
Title: title,
Text: text,
TimeOut: 30 * time.Second,
TimeOut: 1 * time.Minute,
})
if err != nil {
switch err {
case dialog.ErrTimeout:
switch {
case errors.Is(err, dialog.ErrTimeout):
log.Debug().Msg("successPrompt timed out")
return nil
default:
Expand Down
33 changes: 27 additions & 6 deletions orbit/pkg/zenity/zenity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,37 @@ import (

"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
"github.com/fleetdm/fleet/v4/orbit/pkg/platform"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/rs/zerolog/log"
)

const zenityProcessName = "zenity"

type Zenity struct {
// cmdWithOutput can be set in tests to mock execution of the dialog.
cmdWithOutput func(ctx context.Context, args ...string) ([]byte, int, error)
// cmdWithWait can be set in tests to mock execution of the dialog.
cmdWithWait func(ctx context.Context, args ...string) error
// killZenityFunc can be set in tests to mock killing the zenity process.
killZenityFunc func()
}

// New creates a new Zenity dialog instance for zenity v4 on Linux.
// Zenity implements the Dialog interface.
func New() *Zenity {
return &Zenity{
cmdWithOutput: execCmdWithOutput,
cmdWithWait: execCmdWithWait,
cmdWithOutput: execCmdWithOutput,
cmdWithWait: execCmdWithWait,
killZenityFunc: killZenityProcesses,
}
}

// ShowEntry displays an dialog that accepts end user input. It returns the entered
// text or errors ErrCanceled, ErrTimeout, or ErrUnknown.
func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byte, error) {
z.killZenityFunc()

args := []string{"--entry"}
if opts.Title != "" {
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
Expand All @@ -47,9 +56,9 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byt
if err != nil {
switch statusCode {
case 1:
return nil, ctxerr.Wrap(ctx, dialog.ErrCanceled)
return nil, dialog.ErrCanceled
case 5:
return nil, ctxerr.Wrap(ctx, dialog.ErrTimeout)
return nil, dialog.ErrTimeout
default:
return nil, ctxerr.Wrap(ctx, dialog.ErrUnknown, err.Error())
}
Expand All @@ -60,6 +69,8 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byt

// ShowInfo displays an information dialog. It returns errors ErrTimeout or ErrUnknown.
func (z *Zenity) ShowInfo(ctx context.Context, opts dialog.InfoOptions) error {
z.killZenityFunc()

args := []string{"--info"}
if opts.Title != "" {
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
Expand Down Expand Up @@ -94,6 +105,8 @@ func (z *Zenity) ShowInfo(ctx context.Context, opts dialog.InfoOptions) error {
// Use this function for cases where a progress dialog is needed to run
// alongside other operations, with explicit cancellation or termination.
func (z *Zenity) ShowProgress(ctx context.Context, opts dialog.ProgressOptions) error {
z.killZenityFunc()

args := []string{"--progress"}
if opts.Title != "" {
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
Expand Down Expand Up @@ -122,7 +135,7 @@ func execCmdWithOutput(ctx context.Context, args ...string) ([]byte, int, error)
opts = append(opts, execuser.WithArg(arg, "")) // Using empty value for positional args
}

output, exitCode, err := execuser.RunWithOutput("zenity", opts...)
output, exitCode, err := execuser.RunWithOutput(zenityProcessName, opts...)

// Trim the newline from zenity output
output = bytes.TrimSuffix(output, []byte("\n"))
Expand All @@ -136,5 +149,13 @@ func execCmdWithWait(ctx context.Context, args ...string) error {
opts = append(opts, execuser.WithArg(arg, "")) // Using empty value for positional args
}

return execuser.RunWithWait(ctx, "zenity", opts...)
_, err := execuser.Run(zenityProcessName, opts...)
return err
}

func killZenityProcesses() {
_, err := platform.KillAllProcessByName(zenityProcessName)
if err != nil {
log.Warn().Err(err).Msg("failed to kill zenity process")
}
}
Loading

0 comments on commit 7b932e8

Please sign in to comment.