-
Notifications
You must be signed in to change notification settings - Fork 111
Expand file tree
/
Copy pathrunner_darwin.go
More file actions
99 lines (87 loc) · 4.06 KB
/
Copy pathrunner_darwin.go
File metadata and controls
99 lines (87 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//go:build darwin
package runner
import (
"context"
"fmt"
"log/slog"
"os/user"
"strings"
"time"
"github.com/kolide/launcher/v2/ee/allowedcmd"
"github.com/kolide/launcher/v2/ee/observability"
"github.com/kolide/launcher/v2/pkg/backoff"
"golang.org/x/sys/unix"
)
// Starts the provided cmd and returns any errors from spawning the process. If the uid differs from the user
// running the current process, runAsUser uses `launchctl runas` to start cmd in the user's context (required for
// notifications to work). Otherwise it runs cmd directly.
func (r *DesktopUsersProcessesRunner) runAsUser(ctx context.Context, uid string, cmd *allowedcmd.TracedCmd) error {
_, span := observability.StartSpan(ctx, "uid", uid)
defer span.End()
// we do not need to launch into our own user
if r.isCurrentUser(uid) {
return cmd.Start()
}
runningUser, err := user.LookupId(uid)
if err != nil || runningUser == nil {
return fmt.Errorf("looking up user with uid %s: %w", uid, err)
}
// Update command so that we're prepending `launchctl asuser $UID sudo --preserve-env -u $runningUser` to the launcher desktop command.
// We need to run with `launchctl asuser` in order to get the user context, which is required to be able to send notifications.
// We need `sudo -u $runningUser` to set the UID on the command correctly -- necessary for, among other things, correctly observing
// light vs dark mode.
// We need --preserve-env for sudo in order to avoid clearing SOCKET_PATH, AUTHTOKEN, etc that are necessary for the desktop
// process to run.
cmd.Path = "/bin/launchctl"
updatedCmdArgs := append([]string{"/bin/launchctl", "asuser", uid, "sudo", "--preserve-env", "-u", runningUser.Username}, cmd.Args...)
cmd.Args = updatedCmdArgs
// the remaining code in this function is not covered by unit test since it requires root privileges
// We may be able to run passwordless sudo in GitHub actions, could possibly exec the tests as sudo.
// But we may not have a console user?
return cmd.Start()
}
func osversion() (string, error) {
return unix.Sysctl("kern.osrelease")
}
// logIndicatesSystrayNeedsRestart is Windows-only functionality
func logIndicatesSystrayNeedsRestart(_ string) bool {
return false
}
// waitForReadyToSpawnDesktopState repeatedly checks to see if the ControlCenter is running,
// since we need it for our menu bar icon to appear.
func (r *DesktopUsersProcessesRunner) waitForReadyToSpawnDesktopState(ctx context.Context, uid string) {
if err := backoff.WaitFor(func() error {
controlCenterService := fmt.Sprintf("gui/%s/com.apple.controlcenter", uid)
cmd, err := allowedcmd.Launchctl.Cmd(ctx, "print", controlCenterService)
if err != nil {
return fmt.Errorf("creating `launchctl print %s` command: %w", controlCenterService, err)
}
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("running `launchctl print %s`: %w", controlCenterService, err)
}
outStr := string(out)
// The output for launchctl print is not guaranteed to remain the same,
// so we have a couple different checks here to see if we have a running Control Center.
// First, we check for "state = running" in the output. If it's there, then
// we assume the service is running.
if strings.Contains(outStr, "state = running") {
return nil
}
// Next, we look for our expected error string, "Could not find service".
// Usually when we see this case, launchctl print exits with a non-zero error code,
// returning an error on `CombinedOutput` above. But just in case that changes,
// we check for the error output string here as well.
if strings.Contains(outStr, "Could not find service") {
return fmt.Errorf("%s not yet running", controlCenterService)
}
// If we make it here, we assume the service isn't running yet. We separate
// this case out from the above for improving output parsing in the future.
return fmt.Errorf("unexpected launchctl output, assuming service is not running: %s", outStr)
}, 30*time.Second, 1*time.Second); err != nil {
r.slogger.Log(ctx, slog.LevelWarn,
"ControlCenter process not found before timeout",
"err", err,
)
}
}