-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rough exec implementation of presence detection
- Loading branch information
1 parent
97f4287
commit 566d5bd
Showing
7 changed files
with
218 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"os" | ||
"runtime" | ||
|
||
"github.com/kolide/launcher/ee/presencedetection" | ||
"github.com/kolide/launcher/pkg/log/multislogger" | ||
) | ||
|
||
func runDetectPresence(_ *multislogger.MultiSlogger, args []string) error { | ||
reason := "" | ||
|
||
if len(args) != 0 { | ||
reason = args[0] | ||
} | ||
|
||
if reason == "" && runtime.GOOS == "darwin" { | ||
return errors.New("reason is required on darwin") | ||
} | ||
|
||
success, err := presencedetection.Detect(reason) | ||
response := presencedetection.PresenceDetectionResponse{ | ||
Success: success, | ||
} | ||
if err != nil { | ||
response.Error = err.Error() | ||
} | ||
|
||
// serialize response to JSON | ||
responseJSON, err := json.Marshal(response) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// b64 enode response | ||
responseB64 := base64.StdEncoding.EncodeToString(responseJSON) | ||
|
||
// write response to stdout | ||
if _, err := os.Stdout.Write([]byte(responseB64)); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package presencedetection | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"os/user" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/kolide/launcher/ee/consoleuser" | ||
) | ||
|
||
type PresenceDetectionResponse struct { | ||
Success bool `json:"success"` | ||
Error string `json:"error,omitempty"` | ||
} | ||
|
||
type PresenceDetector struct { | ||
lastDetectionUTC time.Time | ||
mutext sync.Mutex | ||
} | ||
|
||
func (pd *PresenceDetector) DetectForConsoleUser(reason string, detectionInterval time.Duration) (bool, error) { | ||
pd.mutext.Lock() | ||
defer pd.mutext.Unlock() | ||
|
||
// Check if the last detection was within the detection interval | ||
if time.Since(pd.lastDetectionUTC) < detectionInterval { | ||
return true, nil | ||
} | ||
|
||
executablePath, err := os.Executable() | ||
if err != nil { | ||
return false, fmt.Errorf("could not get executable path: %w", err) | ||
} | ||
|
||
consoleUserUids, err := consoleuser.CurrentUids(context.TODO()) | ||
if err != nil { | ||
return false, fmt.Errorf("could not get console user: %w", err) | ||
} | ||
|
||
if len(consoleUserUids) == 0 { | ||
return false, errors.New("no console user found") | ||
} | ||
|
||
runningUserUid := consoleUserUids[0] | ||
|
||
// Ensure that we handle a non-root current user appropriately | ||
currentUser, err := user.Current() | ||
if err != nil { | ||
return false, fmt.Errorf("getting current user: %w", err) | ||
} | ||
|
||
runningUser, err := user.LookupId(runningUserUid) | ||
if err != nil || runningUser == nil { | ||
return false, fmt.Errorf("looking up user with uid %s: %w", runningUserUid, err) | ||
} | ||
|
||
cmd := exec.Command(executablePath, "detect-presence", reason) //nolint:forbidigo // We trust that the launcher executable path is correct, so we don't need to use allowedcmd | ||
|
||
// 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", runningUserUid, "sudo", "--preserve-env", "-u", runningUser.Username}, cmd.Args...) | ||
cmd.Args = updatedCmdArgs | ||
|
||
if currentUser.Uid != "0" && currentUser.Uid != runningUserUid { | ||
// if the user is running for another user, we have an error because we can't set credentials | ||
return false, fmt.Errorf("current user %s is not root and does not match running user, can't start process for other user %s", currentUser.Uid, runningUserUid) | ||
} | ||
|
||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return false, fmt.Errorf("could not run command: %w", err) | ||
} | ||
|
||
outStr := string(out) | ||
|
||
// get last line of outstr | ||
lastLine := "" | ||
for _, line := range strings.Split(outStr, "\n") { | ||
if line != "" { | ||
lastLine = line | ||
} | ||
} | ||
|
||
outDecoded, err := base64.StdEncoding.DecodeString(lastLine) | ||
if err != nil { | ||
return false, fmt.Errorf("could not decode output: %w", err) | ||
} | ||
|
||
response := PresenceDetectionResponse{} | ||
if err := json.Unmarshal(outDecoded, &response); err != nil { | ||
return false, fmt.Errorf("could not unmarshal response: %w", err) | ||
} | ||
|
||
if response.Success { | ||
pd.lastDetectionUTC = time.Now().UTC() | ||
} | ||
|
||
if response.Error != "" { | ||
return response.Success, errors.New(response.Error) | ||
} | ||
|
||
return response.Success, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//go:build !darwin | ||
// +build !darwin | ||
|
||
package presencedetection | ||
|
||
import "errors" | ||
|
||
func Detect(reason string) (bool, error) { | ||
// Implement detection logic for non-Darwin platforms | ||
return false, errors.New("detection not implemented for this platform") | ||
} |