Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux key escrow progress windows #24069

Merged
merged 7 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{
lucasmrod marked this conversation as resolved.
Show resolved Hide resolved
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,
lucasmrod marked this conversation as resolved.
Show resolved Hide resolved
})
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
Loading