Skip to content

Commit

Permalink
do presence detection through desktop server
Browse files Browse the repository at this point in the history
  • Loading branch information
James-Pickett committed Sep 20, 2024
1 parent d7efbab commit 4b99368
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 177 deletions.
48 changes: 0 additions & 48 deletions cmd/launcher/detect_presence.go

This file was deleted.

2 changes: 0 additions & 2 deletions cmd/launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,6 @@ func runSubcommands(systemMultiSlogger *multislogger.MultiSlogger) error {
run = runSecureEnclave
case "watchdog": // note: this is currently only implemented for windows
run = watchdog.RunWatchdogService
case "detect-presence":
run = runDetectPresence
default:
return fmt.Errorf("unknown subcommand %s", os.Args[1])
}
Expand Down
48 changes: 35 additions & 13 deletions ee/desktop/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ func InstanceDesktopProcessRecords() map[string]processRecord {
return instance.uidProcs
}

func InstanceDetectPresence(reason string, interval time.Duration) (bool, error) {
if instance == nil {
return false, errors.New("no instance of DesktopUsersProcessesRunner")
}

if instance.uidProcs == nil || len(instance.uidProcs) == 0 {
return false, errors.New("no desktop processes running")
}

var lastErr error
for _, proc := range instance.uidProcs {
client := client.New(instance.userServerAuthToken, proc.socketPath)
success, err := client.DetectPresence(reason, interval)

// not sure how to handle the possiblity of multiple users
// so just return the first success
if success {
return success, err
}

lastErr = err
}

return false, fmt.Errorf("no desktop processes detected presence, last error: %w", lastErr)
}

// DesktopUsersProcessesRunner creates a launcher desktop process each time it detects
// a new console (GUI) user. If the current console user's desktop process dies, it
// will create a new one.
Expand Down Expand Up @@ -522,20 +548,10 @@ func (r *DesktopUsersProcessesRunner) refreshMenu() {
return
}

// Tell any running desktop user processes that they should refresh the latest menu data
// Tell any running desktop user processes that they should show menu and refresh the latest menu data
for uid, proc := range r.uidProcs {
client := client.New(r.userServerAuthToken, proc.socketPath)

if err := client.ShowDesktop(); err != nil {
r.slogger.Log(context.TODO(), slog.LevelError,
"sending refresh command to user desktop process",
"uid", uid,
"pid", proc.Process.Pid,
"path", proc.path,
"err", err,
)
}

if err := client.Refresh(); err != nil {
r.slogger.Log(context.TODO(), slog.LevelError,
"sending refresh command to user desktop process",
Expand Down Expand Up @@ -696,13 +712,19 @@ func (r *DesktopUsersProcessesRunner) spawnForUser(ctx context.Context, uid stri
r.waitOnProcessAsync(uid, cmd.Process)

client := client.New(r.userServerAuthToken, socketPath)
if err := backoff.WaitFor(client.Ping, 10*time.Second, 1*time.Second); err != nil {

pingFunc := client.Ping
if r.knapsack.DesktopEnabled() {
pingFunc = client.ShowDesktop
}

if err := backoff.WaitFor(pingFunc, 10*time.Second, 1*time.Second); err != nil {
// unregister proc from desktop server so server will not respond to its requests
r.runnerServer.DeRegisterClient(uid)

if err := cmd.Process.Kill(); err != nil {
r.slogger.Log(ctx, slog.LevelError,
"killing user desktop process after startup ping failed",
"killing user desktop process after startup ping / show desktop failed",
"uid", uid,
"pid", cmd.Process.Pid,
"path", cmd.Path,
Expand Down
29 changes: 29 additions & 0 deletions ee/desktop/user/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"time"

"github.com/kolide/launcher/ee/desktop/user/notify"
"github.com/kolide/launcher/ee/desktop/user/server"
)

type transport struct {
Expand Down Expand Up @@ -59,6 +62,32 @@ func (c *client) ShowDesktop() error {
return c.get("show")
}

func (c *client) DetectPresence(reason string, interval time.Duration) (bool, error) {
encodedReason := url.QueryEscape(reason)
encodedInterval := url.QueryEscape(interval.String())

resp, requestErr := c.base.Get(fmt.Sprintf("http://unix/detect_presence?reason=%s&interval=%s", encodedReason, encodedInterval))
if requestErr != nil {
return false, fmt.Errorf("getting presence: %w", requestErr)
}

var response server.DetectPresenceResponse
if resp.Body != nil {
defer resp.Body.Close()

if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return false, fmt.Errorf("decoding response: %w", err)
}
}

var err error
if response.Error != "" {
err = errors.New(response.Error)
}

return response.Success, err
}

func (c *client) Notify(n notify.Notification) error {
notificationToSend := notify.Notification{
Title: n.Title,
Expand Down
89 changes: 71 additions & 18 deletions ee/desktop/user/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"time"

"github.com/kolide/launcher/ee/desktop/user/notify"
"github.com/kolide/launcher/ee/presencedetection"
"github.com/kolide/launcher/pkg/backoff"
)

Expand All @@ -27,15 +28,16 @@ type notificationSender interface {
// UserServer provides IPC for the root desktop runner to communicate with the user desktop processes.
// It allows the runner process to send notficaitons and commands to the desktop processes.
type UserServer struct {
slogger *slog.Logger
server *http.Server
listener net.Listener
shutdownChan chan<- struct{}
showDesktopChan chan<- struct{}
authToken string
socketPath string
notifier notificationSender
refreshListeners []func()
slogger *slog.Logger
server *http.Server
listener net.Listener
shutdownChan chan<- struct{}
authToken string
socketPath string
notifier notificationSender
refreshListeners []func()
presenceDetector presencedetection.PresenceDetector
showDesktopOnceFunc func()
}

func New(slogger *slog.Logger,
Expand All @@ -45,12 +47,14 @@ func New(slogger *slog.Logger,
showDesktopChan chan<- struct{},
notifier notificationSender) (*UserServer, error) {
userServer := &UserServer{
shutdownChan: shutdownChan,
showDesktopChan: showDesktopChan,
authToken: authToken,
slogger: slogger.With("component", "desktop_server"),
socketPath: socketPath,
notifier: notifier,
shutdownChan: shutdownChan,
authToken: authToken,
slogger: slogger.With("component", "desktop_server"),
socketPath: socketPath,
notifier: notifier,
showDesktopOnceFunc: sync.OnceFunc(func() {
showDesktopChan <- struct{}{}
}),
}

authedMux := http.NewServeMux()
Expand All @@ -59,6 +63,7 @@ func New(slogger *slog.Logger,
authedMux.HandleFunc("/notification", userServer.notificationHandler)
authedMux.HandleFunc("/refresh", userServer.refreshHandler)
authedMux.HandleFunc("/show", userServer.showDesktop)
authedMux.HandleFunc("/detect_presence", userServer.detectPresence)

userServer.server = &http.Server{
Handler: userServer.authMiddleware(authedMux),
Expand Down Expand Up @@ -165,9 +170,57 @@ func (s *UserServer) showDesktop(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

sync.OnceFunc(func() {
s.showDesktopChan <- struct{}{}
})()
s.showDesktopOnceFunc()
}

type DetectPresenceResponse struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}

func (s *UserServer) detectPresence(w http.ResponseWriter, req *http.Request) {
// get reason url param from req
reason := req.URL.Query().Get("reason")

if reason == "" {
http.Error(w, "reason is required", http.StatusBadRequest)
return
}

// get intervalString from url param
intervalString := req.URL.Query().Get("interval")
if intervalString == "" {
http.Error(w, "interval is required", http.StatusBadRequest)
return
}

interval, err := time.ParseDuration(intervalString)
if err != nil {
http.Error(w, "interval is not a valid duration", http.StatusBadRequest)
return
}

// detect presence
success, err := s.presenceDetector.DetectPresence(reason, interval)
response := DetectPresenceResponse{
Success: success,
}

if err != nil {
response.Error = err.Error()
}

// convert response to json
responseBytes, err := json.Marshal(response)
if err != nil {
http.Error(w, "could not marshal response", http.StatusInternalServerError)
return
}

// write response
w.Header().Set("Content-Type", "application/json")
w.Write(responseBytes)
w.WriteHeader(http.StatusOK)
}

func (s *UserServer) refreshHandler(w http.ResponseWriter, req *http.Request) {
Expand Down
10 changes: 5 additions & 5 deletions ee/localserver/krypto-ec-middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import (
)

const (
timestampValidityRange = 150
kolideKryptoEccHeader20230130Value = "2023-01-30"
kolideKryptoHeaderKey = "X-Kolide-Krypto"
kolideSessionIdHeaderKey = "X-Kolide-Session"
kolidePresenceDetectionIntervalSecondsKey = "X-Kolide-Presence-Detection-Interval"
timestampValidityRange = 150
kolideKryptoEccHeader20230130Value = "2023-01-30"
kolideKryptoHeaderKey = "X-Kolide-Krypto"
kolideSessionIdHeaderKey = "X-Kolide-Session"
kolidePresenceDetectionInterval = "X-Kolide-Presence-Detection-Interval"
)

type v2CmdRequestType struct {
Expand Down
18 changes: 10 additions & 8 deletions ee/localserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (
"log/slog"
"net"
"net/http"
"strconv"
"strings"
"time"

"github.com/kolide/krypto"
"github.com/kolide/krypto/pkg/echelper"
"github.com/kolide/launcher/ee/agent"
"github.com/kolide/launcher/ee/agent/types"
"github.com/kolide/launcher/ee/desktop/runner"
"github.com/kolide/launcher/ee/presencedetection"
"github.com/kolide/launcher/pkg/osquery"
"github.com/kolide/launcher/pkg/traces"
Expand Down Expand Up @@ -123,6 +123,8 @@ func New(ctx context.Context, k types.Knapsack) (*localServer, error) {
// mux.Handle("/scheduledquery", ls.requestScheduledQueryHandler())
// curl localhost:40978/acceleratecontrol --data '{"interval":"250ms", "duration":"1s"}'
// mux.Handle("/acceleratecontrol", ls.requestAccelerateControlHandler())
// curl localhost:40978/id
// mux.Handle("/id", ls.requestIdHandler())

srv := &http.Server{
Handler: otelhttp.NewHandler(
Expand Down Expand Up @@ -406,23 +408,23 @@ func (ls *localServer) rateLimitHandler(next http.Handler) http.Handler {

func (ls *localServer) presenceDetectionHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// can test this by adding an unauthed endpoint to the mux and running, for example:
// curl -H "X-Kolide-Presence-Detection-Interval: 0" localhost:12519/id
detectionIntervalSecondsStr := r.Header.Get(kolidePresenceDetectionIntervalSecondsKey)
// curl -H "X-Kolide-Presence-Detection-Interval: 10s" localhost:12519/id
detectionIntervalStr := r.Header.Get(kolidePresenceDetectionInterval)

// no presence detection requested
if detectionIntervalSecondsStr == "" {
if detectionIntervalStr == "" {
next.ServeHTTP(w, r)
}

detectionIntervalSeconds, err := strconv.Atoi(detectionIntervalSecondsStr)
detectionIntervalDuration, err := time.ParseDuration(detectionIntervalStr)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

success, err := ls.presenceDetector.DetectForConsoleUser("detect user presence", time.Duration(detectionIntervalSeconds)*time.Second)
// TODO: decide how we want to get the reason. Pull from header?
success, err := runner.InstanceDetectPresence("authenticate a thing", detectionIntervalDuration)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
Expand Down
Loading

0 comments on commit 4b99368

Please sign in to comment.