Skip to content

Commit

Permalink
presence detector return durtion since last detection, add to local s…
Browse files Browse the repository at this point in the history
…erver response header
  • Loading branch information
James-Pickett committed Sep 26, 2024
1 parent 6f3bb1a commit 15a981d
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 77 deletions.
18 changes: 9 additions & 9 deletions ee/desktop/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/kolide/launcher/ee/desktop/user/client"
"github.com/kolide/launcher/ee/desktop/user/menu"
"github.com/kolide/launcher/ee/desktop/user/notify"
"github.com/kolide/launcher/ee/presencedetection"
"github.com/kolide/launcher/ee/ui/assets"
"github.com/kolide/launcher/pkg/backoff"
"github.com/kolide/launcher/pkg/rungroup"
Expand Down Expand Up @@ -282,26 +283,25 @@ func (r *DesktopUsersProcessesRunner) Interrupt(_ error) {
)
}

func (r *DesktopUsersProcessesRunner) DetectPresence(reason string, interval time.Duration) (bool, error) {
func (r *DesktopUsersProcessesRunner) DetectPresence(reason string, interval time.Duration) (time.Duration, error) {
if r.uidProcs == nil || len(r.uidProcs) == 0 {
return false, errors.New("no desktop processes running")
return presencedetection.DetectionFailedDurationValue, errors.New("no desktop processes running")
}

var lastErr error
for _, proc := range r.uidProcs {
client := client.New(r.userServerAuthToken, proc.socketPath)
success, err := client.DetectPresence(reason, interval)
durationSinceLastDetection, 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
if err != nil {
lastErr = err
continue
}

lastErr = err
return durationSinceLastDetection, nil
}

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

// killDesktopProcesses kills any existing desktop processes
Expand Down
14 changes: 10 additions & 4 deletions ee/desktop/user/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

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

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

func (c *client) DetectPresence(reason string, interval time.Duration) (bool, error) {
func (c *client) DetectPresence(reason string, interval time.Duration) (time.Duration, 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)
return presencedetection.DetectionFailedDurationValue, 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)
return presencedetection.DetectionFailedDurationValue, fmt.Errorf("decoding response: %w", err)
}
}

Expand All @@ -85,7 +86,12 @@ func (c *client) DetectPresence(reason string, interval time.Duration) (bool, er
err = errors.New(response.Error)
}

return response.Success, err
durationSinceLastDetection, parseErr := time.ParseDuration(response.DurationSinceLastDetection)
if parseErr != nil {
return presencedetection.DetectionFailedDurationValue, fmt.Errorf("parsing time since last detection: %w", parseErr)
}

return durationSinceLastDetection, err
}

func (c *client) Notify(n notify.Notification) error {
Expand Down
15 changes: 11 additions & 4 deletions ee/desktop/user/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ func (s *UserServer) showDesktop(w http.ResponseWriter, req *http.Request) {
}

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

func (s *UserServer) detectPresence(w http.ResponseWriter, req *http.Request) {
Expand All @@ -201,13 +201,20 @@ func (s *UserServer) detectPresence(w http.ResponseWriter, req *http.Request) {
}

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

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

s.slogger.Log(context.TODO(), slog.LevelDebug,
"detecting presence",
"reason", reason,
"interval", interval,
"err", err,
)
}

// convert response to json
Expand Down
13 changes: 7 additions & 6 deletions ee/localserver/krypto-ec-middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ import (
)

const (
timestampValidityRange = 150
kolideKryptoEccHeader20230130Value = "2023-01-30"
kolideKryptoHeaderKey = "X-Kolide-Krypto"
kolideSessionIdHeaderKey = "X-Kolide-Session"
kolidePresenceDetectionInterval = "X-Kolide-Presence-Detection-Interval"
kolidePresenceDetectionReason = "X-Kolide-Presence-Detection-Reason"
timestampValidityRange = 150
kolideKryptoEccHeader20230130Value = "2023-01-30"
kolideKryptoHeaderKey = "X-Kolide-Krypto"
kolideSessionIdHeaderKey = "X-Kolide-Session"
kolidePresenceDetectionInterval = "X-Kolide-Presence-Detection-Interval"
kolidePresenceDetectionReason = "X-Kolide-Presence-Detection-Reason"
kolideDurationSinceLastPresenceDetection = "X-Kolide-Duration-Since-Last-Presence-Detection"
)

type v2CmdRequestType struct {
Expand Down
10 changes: 5 additions & 5 deletions ee/localserver/mocks/presenceDetector.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 38 additions & 26 deletions ee/localserver/presence-detection-middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/kolide/launcher/ee/localserver/mocks"
"github.com/kolide/launcher/ee/presencedetection"
"github.com/kolide/launcher/pkg/log/multislogger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
Expand All @@ -15,46 +18,51 @@ func TestPresenceDetectionHandler(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expectDetectPresenceCall bool
intervalHeader, reasonHeader string
presenceDetectionSuccess bool
presenceDetectionError error
expectedStatusCode int
name string
expectDetectPresenceCall bool
intervalHeader, reasonHeader string
durationSinceLastDetection time.Duration
presenceDetectionError error
shouldHavePresenceDetectionDurationResponseHeader bool
expectedStatusCode int
}{
{
name: "no presence detection headers",
expectedStatusCode: http.StatusOK,
shouldHavePresenceDetectionDurationResponseHeader: false,
},
{
name: "invalid presence detection interval",
intervalHeader: "invalid-interval",
expectedStatusCode: http.StatusBadRequest,
},
{
name: "valid presence detection, detection fails",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
presenceDetectionSuccess: false,
expectedStatusCode: http.StatusUnauthorized,
name: "valid presence detection, detection fails",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
durationSinceLastDetection: presencedetection.DetectionFailedDurationValue,
expectedStatusCode: http.StatusOK,
shouldHavePresenceDetectionDurationResponseHeader: true,
},
{
name: "valid presence detection, detection succeeds",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
presenceDetectionSuccess: true,
expectedStatusCode: http.StatusOK,
name: "valid presence detection, detection succeeds",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
durationSinceLastDetection: 0,
expectedStatusCode: http.StatusOK,
shouldHavePresenceDetectionDurationResponseHeader: true,
},
{
name: "presence detection error",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
presenceDetectionSuccess: false,
presenceDetectionError: assert.AnError,
expectedStatusCode: http.StatusUnauthorized,
name: "presence detection error",
expectDetectPresenceCall: true,
intervalHeader: "10s",
reasonHeader: "test reason",
durationSinceLastDetection: presencedetection.DetectionFailedDurationValue,
presenceDetectionError: assert.AnError,
expectedStatusCode: http.StatusOK,
shouldHavePresenceDetectionDurationResponseHeader: true,
},
}

Expand All @@ -66,11 +74,12 @@ func TestPresenceDetectionHandler(t *testing.T) {
mockPresenceDetector := mocks.NewPresenceDetector(t)

if tt.expectDetectPresenceCall {
mockPresenceDetector.On("DetectPresence", mock.AnythingOfType("string"), mock.AnythingOfType("Duration")).Return(tt.presenceDetectionSuccess, tt.presenceDetectionError)
mockPresenceDetector.On("DetectPresence", mock.AnythingOfType("string"), mock.AnythingOfType("Duration")).Return(tt.durationSinceLastDetection, tt.presenceDetectionError)
}

server := &localServer{
presenceDetector: mockPresenceDetector,
slogger: multislogger.NewNopLogger(),
}

// Create a test handler for the middleware to call
Expand All @@ -94,6 +103,9 @@ func TestPresenceDetectionHandler(t *testing.T) {
rr := httptest.NewRecorder()
handlerToTest.ServeHTTP(rr, req)

if tt.shouldHavePresenceDetectionDurationResponseHeader {
require.NotEmpty(t, rr.Header().Get(kolideDurationSinceLastPresenceDetection))
}
require.Equal(t, tt.expectedStatusCode, rr.Code)
})
}
Expand Down
35 changes: 24 additions & 11 deletions ee/localserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"log/slog"
"net"
"net/http"
"runtime"
"strings"
"time"

Expand Down Expand Up @@ -66,7 +67,7 @@ const (
)

type presenceDetector interface {
DetectPresence(reason string, interval time.Duration) (bool, error)
DetectPresence(reason string, interval time.Duration) (time.Duration, error)
}

func New(ctx context.Context, k types.Knapsack, presenceDetector presenceDetector) (*localServer, error) {
Expand Down Expand Up @@ -127,7 +128,7 @@ func New(ctx context.Context, k types.Knapsack, presenceDetector presenceDetecto
// curl localhost:40978/acceleratecontrol --data '{"interval":"250ms", "duration":"1s"}'
// mux.Handle("/acceleratecontrol", ls.requestAccelerateControlHandler())
// curl localhost:40978/id
// mux.Handle("/id", ls.requestIdHandler())
mux.Handle("/id", ls.requestIdHandler())

srv := &http.Server{
Handler: otelhttp.NewHandler(
Expand Down Expand Up @@ -411,6 +412,12 @@ 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) {

if runtime.GOOS != "darwin" {
next.ServeHTTP(w, r)
return
}

// can test this by adding an unauthed endpoint to the mux and running, for example:
// curl -H "X-Kolide-Presence-Detection-Interval: 10s" -H "X-Kolide-Presence-Detection-Reason: my reason" localhost:12519/id
detectionIntervalStr := r.Header.Get(kolidePresenceDetectionInterval)
Expand All @@ -422,6 +429,8 @@ func (ls *localServer) presenceDetectionHandler(next http.Handler) http.Handler

detectionIntervalDuration, err := time.ParseDuration(detectionIntervalStr)
if err != nil {
// this is the only time this should returna non-200 status code
// asked for presence detection, but the interval is invalid
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
Expand All @@ -433,17 +442,21 @@ func (ls *localServer) presenceDetectionHandler(next http.Handler) http.Handler
reason = reasonHeader
}

success, err := ls.presenceDetector.DetectPresence(reason, detectionIntervalDuration)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
durationSinceLastDetection, err := ls.presenceDetector.DetectPresence(reason, detectionIntervalDuration)

if !success {
http.Error(w, "presence detection failed", http.StatusUnauthorized)
return
}
ls.slogger.Log(r.Context(), slog.LevelError,
"presence_detection",
"reason", reason,
"interval", detectionIntervalDuration,
"duration_since_last_detection", durationSinceLastDetection,
"err", err,
)

// if there was an error, we still want to return a 200 status code
// and send the request through
// allow the server to decide what to do based on last detection duration

w.Header().Add(kolideDurationSinceLastPresenceDetection, durationSinceLastDetection.String())
next.ServeHTTP(w, r)
})
}
Loading

0 comments on commit 15a981d

Please sign in to comment.