From 92d6de95b2f4029430bdc76abc3d481ba6434d9f Mon Sep 17 00:00:00 2001 From: Vegar Sechmann Molvig Date: Wed, 26 Jun 2024 13:53:47 +0200 Subject: [PATCH] append issue if device not seen recently --- cmd/apiserver/main.go | 5 +++ internal/apiserver/database/database.go | 4 ++ internal/apiserver/database/database_test.go | 5 ++- internal/apiserver/kolide/check.go | 2 - internal/integration_test/integration_test.go | 15 +++++-- internal/pb/devices.go | 44 +++++++++++++++++++ 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index fca95ad5..f17f7a65 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -334,6 +334,11 @@ func run(log *logrus.Entry, cfg config.Config) error { if err != nil { return err } + + if issue := device.MaybeLstSeenIssue(); issue != nil { + device.Issues = append(device.GetIssues(), issue) + } + grpcHandler.SendDeviceConfiguration(device) grpcHandler.SendAllGatewayConfigurations() return nil diff --git a/internal/apiserver/database/database.go b/internal/apiserver/database/database.go index 21fc481d..0ddbd091 100644 --- a/internal/apiserver/database/database.go +++ b/internal/apiserver/database/database.go @@ -626,6 +626,10 @@ func sqlcDeviceToPbDevice(sqlcDevice sqlc.Device) (*pb.Device, error) { pbDevice.LastSeen = timestamppb.New(stringToTime(sqlcDevice.LastSeen.String)) } + if issue := pbDevice.MaybeLstSeenIssue(); issue != nil { + pbDevice.Issues = append(pbDevice.Issues, issue) + } + return pbDevice, nil } diff --git a/internal/apiserver/database/database_test.go b/internal/apiserver/database/database_test.go index b180dfff..27d6218b 100644 --- a/internal/apiserver/database/database_test.go +++ b/internal/apiserver/database/database_test.go @@ -8,6 +8,7 @@ import ( "github.com/nais/device/internal/apiserver/testdatabase" "github.com/nais/device/internal/pb" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" ) const timeout = time.Second * 5 @@ -101,7 +102,7 @@ func TestAddDevice(t *testing.T) { Title: "integration test issue", }, } - d := &pb.Device{Username: "username", PublicKey: "publickey", Serial: serial, Platform: "darwin"} + d := &pb.Device{Username: "username", PublicKey: "publickey", Serial: serial, Platform: "darwin", LastSeen: timestamppb.Now()} err := db.AddDevice(ctx, d) assert.NoError(t, err) @@ -121,7 +122,7 @@ func TestAddDevice(t *testing.T) { assert.NoError(t, err) newUsername, newPublicKey := "newUsername", "newPublicKey" - dUpdated := &pb.Device{Username: newUsername, PublicKey: newPublicKey, Serial: serial, Platform: "darwin"} + dUpdated := &pb.Device{Username: newUsername, PublicKey: newPublicKey, Serial: serial, Platform: "darwin", LastSeen: timestamppb.Now()} err = db.AddDevice(ctx, dUpdated) assert.NoError(t, err) diff --git a/internal/apiserver/kolide/check.go b/internal/apiserver/kolide/check.go index e3139903..3c3413f5 100644 --- a/internal/apiserver/kolide/check.go +++ b/internal/apiserver/kolide/check.go @@ -66,8 +66,6 @@ func (failure *DeviceFailure) Relevant() bool { return failure.Check.Severity() != pb.Severity_Info } -const MaxTimeSinceKolideLastSeen = 25 * time.Hour - func (f DeviceFailure) AsDeviceIssue() *pb.DeviceIssue { graceTime := GraceTime(f.Check.Severity()) if graceTime == DurationUnknown { diff --git a/internal/integration_test/integration_test.go b/internal/integration_test/integration_test.go index 65f34111..406f0751 100644 --- a/internal/integration_test/integration_test.go +++ b/internal/integration_test/integration_test.go @@ -41,6 +41,7 @@ func TestIntegration(t *testing.T) { Serial: "test-serial", PublicKey: "publicKey", Username: "tester", + LastSeen: timestamppb.Now(), Platform: "linux", Issues: []*pb.DeviceIssue{ { @@ -62,6 +63,7 @@ func TestIntegration(t *testing.T) { PublicKey: "publicKey", Username: "tester", Platform: "linux", + LastSeen: timestamppb.Now(), }, endState: pb.AgentState_Connected, expectedGateways: map[string]*pb.Gateway{ @@ -371,12 +373,19 @@ func tableTest(t *testing.T, log *logrus.Entry, testDevice *pb.Device, endState func assertEqualIssueLists(t *testing.T, expected, actual []*pb.DeviceIssue) { t.Helper() equalIssues := func(a, b *pb.DeviceIssue) bool { - t.Logf("comparing issues (%v,%v,%v,%v,%v)", a.Title == b.Title, a.Message == b.Message, a.Severity == b.Severity, a.DetectedAt.AsTime().Equal(b.DetectedAt.AsTime()), a.LastUpdated.AsTime().Equal(b.LastUpdated.AsTime())) + t.Logf("comparing issues (%v,%v,%v,%v,%v,%v)", + a.Title == b.Title, + a.Message == b.Message, + a.Severity == b.Severity, + a.GetDetectedAt().AsTime().Equal(b.GetDetectedAt().AsTime()), + a.GetLastUpdated().AsTime().Equal(b.GetLastUpdated().AsTime()), + a.GetResolveBefore().AsTime().Equal(b.GetResolveBefore().AsTime())) return a.Title == b.Title && a.Message == b.Message && a.Severity == b.Severity && - a.DetectedAt.AsTime().Equal(b.DetectedAt.AsTime()) && - a.LastUpdated.AsTime().Equal(b.LastUpdated.AsTime()) + a.GetDetectedAt().AsTime().Equal(b.GetDetectedAt().AsTime()) && + a.GetLastUpdated().AsTime().Equal(b.GetLastUpdated().AsTime()) && + a.GetResolveBefore().AsTime().Equal(b.GetResolveBefore().AsTime()) } for _, expectedIssue := range expected { diff --git a/internal/pb/devices.go b/internal/pb/devices.go index 2532d481..4c850c60 100644 --- a/internal/pb/devices.go +++ b/internal/pb/devices.go @@ -1,8 +1,11 @@ package pb import ( + "fmt" "slices" "time" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) // Satisfy WireGuard interface. @@ -38,3 +41,44 @@ func (x *Device) Healthy() bool { return !slices.ContainsFunc(x.GetIssues(), AfterGracePeriod) } + +const lastSeenGracePeriod = time.Hour + +func (x *Device) MaybeLstSeenIssue() *DeviceIssue { + if x == nil { + return nil + } + + if x.LastSeen == nil { + return lastSeenIssue("This device has never been seen by Kolide. Enroll device by asking @Kolide for a new installer on Slack. `/msg @Kolide installers`", x.LastUpdated) + } + + lastSeenAfter := time.Now().Add(-lastSeenGracePeriod) + if x.LastSeen.AsTime().After(lastSeenAfter) { + return nil + } + + // best effort to convert time to Oslo timezone + lastSeen := x.LastSeen.AsTime() + location, err := time.LoadLocation("Europe/Oslo") + if err == nil { + lastSeen = lastSeen.In(location) + } + + msg := fmt.Sprintf(`This device has not been seen by Kolide since %v. +This is a problem because we have no idea what state the device is in. +To fix this make sure the Kolide launcher is running. +If it's not and you don't know why - re-install the launcher by asking @Kolide for a new installer on Slack.`, lastSeen.Format(time.RFC3339)) + return lastSeenIssue(msg, x.LastSeen) +} + +func lastSeenIssue(msg string, lastUpdated *timestamppb.Timestamp) *DeviceIssue { + return &DeviceIssue{ + Title: "Device has not been seen recently", + Message: msg, + Severity: Severity_Critical, + DetectedAt: lastUpdated, + LastUpdated: lastUpdated, + ResolveBefore: timestamppb.New(time.Now().Add(-lastSeenGracePeriod)), + } +}