diff --git a/api/routes/routes.go b/api/routes/routes.go index 5068b6fec1f..9205569ddaf 100644 --- a/api/routes/routes.go +++ b/api/routes/routes.go @@ -67,6 +67,7 @@ func NewRouter(service services.Service, opts ...Option) *echo.Echo { internalAPI.GET(GetPublicKeyURL, gateway.Handler(handler.GetPublicKey)) internalAPI.POST(CreatePrivateKeyURL, gateway.Handler(handler.CreatePrivateKey)) internalAPI.POST(EvaluateKeyURL, gateway.Handler(handler.EvaluateKey)) + internalAPI.POST(EventsSessionsURL, gateway.Handler(handler.EventSession)) // Public routes for external access through API gateway publicAPI := e.Group("/api") diff --git a/api/routes/session.go b/api/routes/session.go index fe0476954bf..210472583b3 100644 --- a/api/routes/session.go +++ b/api/routes/session.go @@ -19,6 +19,7 @@ const ( KeepAliveSessionURL = "/sessions/:uid/keepalive" RecordSessionURL = "/sessions/:uid/record" PlaySessionURL = "/sessions/:uid/play" + EventsSessionsURL = "/sessions/:uid/events" ) const ( @@ -133,3 +134,20 @@ func (h *Handler) PlaySession(c gateway.Context) error { func (h *Handler) DeleteRecordedSession(c gateway.Context) error { return c.NoContent(http.StatusOK) } + +func (h *Handler) EventSession(c gateway.Context) error { + var req requests.SessionEvent + if err := c.Bind(&req); err != nil { + return err + } + + if err := c.Validate(&req); err != nil { + return err + } + + return h.service.EventSession(c.Ctx(), models.UID(req.UID), &models.SessionEvent{ + Type: req.Type, + Timestamp: req.Timestamp, + Data: req.Data, + }) +} diff --git a/api/services/mocks/services.go b/api/services/mocks/services.go index 2a1de248299..98a9ec997ce 100644 --- a/api/services/mocks/services.go +++ b/api/services/mocks/services.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.49.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -28,10 +28,6 @@ type Service struct { func (_m *Service) AddNamespaceMember(ctx context.Context, req *requests.NamespaceAddMember) (*models.Namespace, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for AddNamespaceMember") - } - var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceAddMember) (*models.Namespace, error)); ok { @@ -58,10 +54,6 @@ func (_m *Service) AddNamespaceMember(ctx context.Context, req *requests.Namespa func (_m *Service) AddPublicKeyTag(ctx context.Context, tenant string, fingerprint string, tag string) error { ret := _m.Called(ctx, tenant, fingerprint, tag) - if len(ret) == 0 { - panic("no return value specified for AddPublicKeyTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, fingerprint, tag) @@ -76,10 +68,6 @@ func (_m *Service) AddPublicKeyTag(ctx context.Context, tenant string, fingerpri func (_m *Service) AuthAPIKey(ctx context.Context, key string) (*models.APIKey, error) { ret := _m.Called(ctx, key) - if len(ret) == 0 { - panic("no return value specified for AuthAPIKey") - } - var r0 *models.APIKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.APIKey, error)); ok { @@ -106,10 +94,6 @@ func (_m *Service) AuthAPIKey(ctx context.Context, key string) (*models.APIKey, func (_m *Service) AuthCacheToken(ctx context.Context, tenant string, id string, token string) error { ret := _m.Called(ctx, tenant, id, token) - if len(ret) == 0 { - panic("no return value specified for AuthCacheToken") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, id, token) @@ -124,10 +108,6 @@ func (_m *Service) AuthCacheToken(ctx context.Context, tenant string, id string, func (_m *Service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remoteAddr string) (*models.DeviceAuthResponse, error) { ret := _m.Called(ctx, req, remoteAddr) - if len(ret) == 0 { - panic("no return value specified for AuthDevice") - } - var r0 *models.DeviceAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.DeviceAuth, string) (*models.DeviceAuthResponse, error)); ok { @@ -154,10 +134,6 @@ func (_m *Service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remo func (_m *Service) AuthIsCacheToken(ctx context.Context, tenant string, id string) (bool, error) { ret := _m.Called(ctx, tenant, id) - if len(ret) == 0 { - panic("no return value specified for AuthIsCacheToken") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { @@ -182,10 +158,6 @@ func (_m *Service) AuthIsCacheToken(ctx context.Context, tenant string, id strin func (_m *Service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser, sourceIP string) (*models.UserAuthResponse, int64, string, error) { ret := _m.Called(ctx, req, sourceIP) - if len(ret) == 0 { - panic("no return value specified for AuthLocalUser") - } - var r0 *models.UserAuthResponse var r1 int64 var r2 string @@ -226,10 +198,6 @@ func (_m *Service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUse func (_m *Service) AuthPublicKey(ctx context.Context, req requests.PublicKeyAuth) (*models.PublicKeyAuthResponse, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for AuthPublicKey") - } - var r0 *models.PublicKeyAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.PublicKeyAuth) (*models.PublicKeyAuthResponse, error)); ok { @@ -256,10 +224,6 @@ func (_m *Service) AuthPublicKey(ctx context.Context, req requests.PublicKeyAuth func (_m *Service) AuthUncacheToken(ctx context.Context, tenant string, id string) error { ret := _m.Called(ctx, tenant, id) - if len(ret) == 0 { - panic("no return value specified for AuthUncacheToken") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenant, id) @@ -274,10 +238,6 @@ func (_m *Service) AuthUncacheToken(ctx context.Context, tenant string, id strin func (_m *Service) BillingEvaluate(_a0 internalclient.Client, _a1 string) (bool, error) { ret := _m.Called(_a0, _a1) - if len(ret) == 0 { - panic("no return value specified for BillingEvaluate") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(internalclient.Client, string) (bool, error)); ok { @@ -302,10 +262,6 @@ func (_m *Service) BillingEvaluate(_a0 internalclient.Client, _a1 string) (bool, func (_m *Service) BillingReport(_a0 internalclient.Client, _a1 string, _a2 string) error { ret := _m.Called(_a0, _a1, _a2) - if len(ret) == 0 { - panic("no return value specified for BillingReport") - } - var r0 error if rf, ok := ret.Get(0).(func(internalclient.Client, string, string) error); ok { r0 = rf(_a0, _a1, _a2) @@ -320,10 +276,6 @@ func (_m *Service) BillingReport(_a0 internalclient.Client, _a1 string, _a2 stri func (_m *Service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) (*responses.CreateAPIKey, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for CreateAPIKey") - } - var r0 *responses.CreateAPIKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.CreateAPIKey) (*responses.CreateAPIKey, error)); ok { @@ -350,10 +302,6 @@ func (_m *Service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) func (_m *Service) CreateDeviceTag(ctx context.Context, uid models.UID, tag string) error { ret := _m.Called(ctx, uid, tag) - if len(ret) == 0 { - panic("no return value specified for CreateDeviceTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tag) @@ -368,10 +316,6 @@ func (_m *Service) CreateDeviceTag(ctx context.Context, uid models.UID, tag stri func (_m *Service) CreateNamespace(ctx context.Context, namespace *requests.NamespaceCreate) (*models.Namespace, error) { ret := _m.Called(ctx, namespace) - if len(ret) == 0 { - panic("no return value specified for CreateNamespace") - } - var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceCreate) (*models.Namespace, error)); ok { @@ -398,10 +342,6 @@ func (_m *Service) CreateNamespace(ctx context.Context, namespace *requests.Name func (_m *Service) CreatePrivateKey(ctx context.Context) (*models.PrivateKey, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for CreatePrivateKey") - } - var r0 *models.PrivateKey var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.PrivateKey, error)); ok { @@ -428,10 +368,6 @@ func (_m *Service) CreatePrivateKey(ctx context.Context) (*models.PrivateKey, er func (_m *Service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCreate, tenant string) (*responses.PublicKeyCreate, error) { ret := _m.Called(ctx, req, tenant) - if len(ret) == 0 { - panic("no return value specified for CreatePublicKey") - } - var r0 *responses.PublicKeyCreate var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.PublicKeyCreate, string) (*responses.PublicKeyCreate, error)); ok { @@ -458,10 +394,6 @@ func (_m *Service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCr func (_m *Service) CreateSession(ctx context.Context, session requests.SessionCreate) (*models.Session, error) { ret := _m.Called(ctx, session) - if len(ret) == 0 { - panic("no return value specified for CreateSession") - } - var r0 *models.Session var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.SessionCreate) (*models.Session, error)); ok { @@ -488,10 +420,6 @@ func (_m *Service) CreateSession(ctx context.Context, session requests.SessionCr func (_m *Service) CreateUserToken(ctx context.Context, req *requests.CreateUserToken) (*models.UserAuthResponse, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for CreateUserToken") - } - var r0 *models.UserAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.CreateUserToken) (*models.UserAuthResponse, error)); ok { @@ -518,10 +446,6 @@ func (_m *Service) CreateUserToken(ctx context.Context, req *requests.CreateUser func (_m *Service) DeactivateSession(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) - if len(ret) == 0 { - panic("no return value specified for DeactivateSession") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -536,10 +460,6 @@ func (_m *Service) DeactivateSession(ctx context.Context, uid models.UID) error func (_m *Service) DeleteAPIKey(ctx context.Context, req *requests.DeleteAPIKey) error { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for DeleteAPIKey") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.DeleteAPIKey) error); ok { r0 = rf(ctx, req) @@ -554,10 +474,6 @@ func (_m *Service) DeleteAPIKey(ctx context.Context, req *requests.DeleteAPIKey) func (_m *Service) DeleteDevice(ctx context.Context, uid models.UID, tenant string) error { ret := _m.Called(ctx, uid, tenant) - if len(ret) == 0 { - panic("no return value specified for DeleteDevice") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tenant) @@ -572,10 +488,6 @@ func (_m *Service) DeleteDevice(ctx context.Context, uid models.UID, tenant stri func (_m *Service) DeleteNamespace(ctx context.Context, tenantID string) error { ret := _m.Called(ctx, tenantID) - if len(ret) == 0 { - panic("no return value specified for DeleteNamespace") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, tenantID) @@ -590,10 +502,6 @@ func (_m *Service) DeleteNamespace(ctx context.Context, tenantID string) error { func (_m *Service) DeletePublicKey(ctx context.Context, fingerprint string, tenant string) error { ret := _m.Called(ctx, fingerprint, tenant) - if len(ret) == 0 { - panic("no return value specified for DeletePublicKey") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, fingerprint, tenant) @@ -608,10 +516,6 @@ func (_m *Service) DeletePublicKey(ctx context.Context, fingerprint string, tena func (_m *Service) DeleteTag(ctx context.Context, tenant string, tag string) error { ret := _m.Called(ctx, tenant, tag) - if len(ret) == 0 { - panic("no return value specified for DeleteTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenant, tag) @@ -626,10 +530,6 @@ func (_m *Service) DeleteTag(ctx context.Context, tenant string, tag string) err func (_m *Service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit) (*models.Namespace, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for EditNamespace") - } - var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceEdit) (*models.Namespace, error)); ok { @@ -656,10 +556,6 @@ func (_m *Service) EditNamespace(ctx context.Context, req *requests.NamespaceEdi func (_m *Service) EditSessionRecordStatus(ctx context.Context, sessionRecord bool, tenantID string) error { ret := _m.Called(ctx, sessionRecord, tenantID) - if len(ret) == 0 { - panic("no return value specified for EditSessionRecordStatus") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, bool, string) error); ok { r0 = rf(ctx, sessionRecord, tenantID) @@ -674,10 +570,6 @@ func (_m *Service) EditSessionRecordStatus(ctx context.Context, sessionRecord bo func (_m *Service) EvaluateKeyFilter(ctx context.Context, key *models.PublicKey, dev models.Device) (bool, error) { ret := _m.Called(ctx, key, dev) - if len(ret) == 0 { - panic("no return value specified for EvaluateKeyFilter") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.PublicKey, models.Device) (bool, error)); ok { @@ -702,10 +594,6 @@ func (_m *Service) EvaluateKeyFilter(ctx context.Context, key *models.PublicKey, func (_m *Service) EvaluateKeyUsername(ctx context.Context, key *models.PublicKey, username string) (bool, error) { ret := _m.Called(ctx, key, username) - if len(ret) == 0 { - panic("no return value specified for EvaluateKeyUsername") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.PublicKey, string) (bool, error)); ok { @@ -726,14 +614,24 @@ func (_m *Service) EvaluateKeyUsername(ctx context.Context, key *models.PublicKe return r0, r1 } +// EventSession provides a mock function with given fields: ctx, uid, event +func (_m *Service) EventSession(ctx context.Context, uid models.UID, event *models.SessionEvent) error { + ret := _m.Called(ctx, uid, event) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, models.UID, *models.SessionEvent) error); ok { + r0 = rf(ctx, uid, event) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // GetDevice provides a mock function with given fields: ctx, uid func (_m *Service) GetDevice(ctx context.Context, uid models.UID) (*models.Device, error) { ret := _m.Called(ctx, uid) - if len(ret) == 0 { - panic("no return value specified for GetDevice") - } - var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) (*models.Device, error)); ok { @@ -760,10 +658,6 @@ func (_m *Service) GetDevice(ctx context.Context, uid models.UID) (*models.Devic func (_m *Service) GetDeviceByPublicURLAddress(ctx context.Context, address string) (*models.Device, error) { ret := _m.Called(ctx, address) - if len(ret) == 0 { - panic("no return value specified for GetDeviceByPublicURLAddress") - } - var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Device, error)); ok { @@ -790,10 +684,6 @@ func (_m *Service) GetDeviceByPublicURLAddress(ctx context.Context, address stri func (_m *Service) GetNamespace(ctx context.Context, tenantID string) (*models.Namespace, error) { ret := _m.Called(ctx, tenantID) - if len(ret) == 0 { - panic("no return value specified for GetNamespace") - } - var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Namespace, error)); ok { @@ -820,10 +710,6 @@ func (_m *Service) GetNamespace(ctx context.Context, tenantID string) (*models.N func (_m *Service) GetPublicKey(ctx context.Context, fingerprint string, tenant string) (*models.PublicKey, error) { ret := _m.Called(ctx, fingerprint, tenant) - if len(ret) == 0 { - panic("no return value specified for GetPublicKey") - } - var r0 *models.PublicKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.PublicKey, error)); ok { @@ -850,10 +736,6 @@ func (_m *Service) GetPublicKey(ctx context.Context, fingerprint string, tenant func (_m *Service) GetSession(ctx context.Context, uid models.UID) (*models.Session, error) { ret := _m.Called(ctx, uid) - if len(ret) == 0 { - panic("no return value specified for GetSession") - } - var r0 *models.Session var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) (*models.Session, error)); ok { @@ -880,10 +762,6 @@ func (_m *Service) GetSession(ctx context.Context, uid models.UID) (*models.Sess func (_m *Service) GetSessionRecord(ctx context.Context, tenantID string) (bool, error) { ret := _m.Called(ctx, tenantID) - if len(ret) == 0 { - panic("no return value specified for GetSessionRecord") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { @@ -908,10 +786,6 @@ func (_m *Service) GetSessionRecord(ctx context.Context, tenantID string) (bool, func (_m *Service) GetStats(ctx context.Context) (*models.Stats, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for GetStats") - } - var r0 *models.Stats var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.Stats, error)); ok { @@ -938,10 +812,6 @@ func (_m *Service) GetStats(ctx context.Context) (*models.Stats, error) { func (_m *Service) GetTags(ctx context.Context, tenant string) ([]string, int, error) { ret := _m.Called(ctx, tenant) - if len(ret) == 0 { - panic("no return value specified for GetTags") - } - var r0 []string var r1 int var r2 error @@ -975,10 +845,6 @@ func (_m *Service) GetTags(ctx context.Context, tenant string) ([]string, int, e func (_m *Service) GetUserRole(ctx context.Context, tenantID string, userID string) (string, error) { ret := _m.Called(ctx, tenantID, userID) - if len(ret) == 0 { - panic("no return value specified for GetUserRole") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok { @@ -1003,10 +869,6 @@ func (_m *Service) GetUserRole(ctx context.Context, tenantID string, userID stri func (_m *Service) KeepAliveSession(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) - if len(ret) == 0 { - panic("no return value specified for KeepAliveSession") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -1021,10 +883,6 @@ func (_m *Service) KeepAliveSession(ctx context.Context, uid models.UID) error { func (_m *Service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespace) (*models.UserAuthResponse, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for LeaveNamespace") - } - var r0 *models.UserAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.LeaveNamespace) (*models.UserAuthResponse, error)); ok { @@ -1051,10 +909,6 @@ func (_m *Service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamesp func (_m *Service) ListAPIKeys(ctx context.Context, req *requests.ListAPIKey) ([]models.APIKey, int, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for ListAPIKeys") - } - var r0 []models.APIKey var r1 int var r2 error @@ -1088,10 +942,6 @@ func (_m *Service) ListAPIKeys(ctx context.Context, req *requests.ListAPIKey) ([ func (_m *Service) ListDevices(ctx context.Context, req *requests.DeviceList) ([]models.Device, int, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for ListDevices") - } - var r0 []models.Device var r1 int var r2 error @@ -1125,10 +975,6 @@ func (_m *Service) ListDevices(ctx context.Context, req *requests.DeviceList) ([ func (_m *Service) ListNamespaces(ctx context.Context, req *requests.NamespaceList) ([]models.Namespace, int, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for ListNamespaces") - } - var r0 []models.Namespace var r1 int var r2 error @@ -1162,10 +1008,6 @@ func (_m *Service) ListNamespaces(ctx context.Context, req *requests.NamespaceLi func (_m *Service) ListPublicKeys(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { ret := _m.Called(ctx, paginator) - if len(ret) == 0 { - panic("no return value specified for ListPublicKeys") - } - var r0 []models.PublicKey var r1 int var r2 error @@ -1199,10 +1041,6 @@ func (_m *Service) ListPublicKeys(ctx context.Context, paginator query.Paginator func (_m *Service) ListSessions(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { ret := _m.Called(ctx, paginator) - if len(ret) == 0 { - panic("no return value specified for ListSessions") - } - var r0 []models.Session var r1 int var r2 error @@ -1236,10 +1074,6 @@ func (_m *Service) ListSessions(ctx context.Context, paginator query.Paginator) func (_m *Service) LookupDevice(ctx context.Context, namespace string, name string) (*models.Device, error) { ret := _m.Called(ctx, namespace, name) - if len(ret) == 0 { - panic("no return value specified for LookupDevice") - } - var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.Device, error)); ok { @@ -1266,10 +1100,6 @@ func (_m *Service) LookupDevice(ctx context.Context, namespace string, name stri func (_m *Service) OfflineDevice(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) - if len(ret) == 0 { - panic("no return value specified for OfflineDevice") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -1284,10 +1114,6 @@ func (_m *Service) OfflineDevice(ctx context.Context, uid models.UID) error { func (_m *Service) PublicKey() *rsa.PublicKey { ret := _m.Called() - if len(ret) == 0 { - panic("no return value specified for PublicKey") - } - var r0 *rsa.PublicKey if rf, ok := ret.Get(0).(func() *rsa.PublicKey); ok { r0 = rf() @@ -1304,10 +1130,6 @@ func (_m *Service) PublicKey() *rsa.PublicKey { func (_m *Service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag string) error { ret := _m.Called(ctx, uid, tag) - if len(ret) == 0 { - panic("no return value specified for RemoveDeviceTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tag) @@ -1322,10 +1144,6 @@ func (_m *Service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag stri func (_m *Service) RemoveNamespaceMember(ctx context.Context, req *requests.NamespaceRemoveMember) (*models.Namespace, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for RemoveNamespaceMember") - } - var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceRemoveMember) (*models.Namespace, error)); ok { @@ -1352,10 +1170,6 @@ func (_m *Service) RemoveNamespaceMember(ctx context.Context, req *requests.Name func (_m *Service) RemovePublicKeyTag(ctx context.Context, tenant string, fingerprint string, tag string) error { ret := _m.Called(ctx, tenant, fingerprint, tag) - if len(ret) == 0 { - panic("no return value specified for RemovePublicKeyTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, fingerprint, tag) @@ -1370,10 +1184,6 @@ func (_m *Service) RemovePublicKeyTag(ctx context.Context, tenant string, finger func (_m *Service) RenameDevice(ctx context.Context, uid models.UID, name string, tenant string) error { ret := _m.Called(ctx, uid, name, tenant) - if len(ret) == 0 { - panic("no return value specified for RenameDevice") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string, string) error); ok { r0 = rf(ctx, uid, name, tenant) @@ -1388,10 +1198,6 @@ func (_m *Service) RenameDevice(ctx context.Context, uid models.UID, name string func (_m *Service) RenameTag(ctx context.Context, tenant string, oldTag string, newTag string) error { ret := _m.Called(ctx, tenant, oldTag, newTag) - if len(ret) == 0 { - panic("no return value specified for RenameTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, oldTag, newTag) @@ -1406,10 +1212,6 @@ func (_m *Service) RenameTag(ctx context.Context, tenant string, oldTag string, func (_m *Service) Setup(ctx context.Context, req requests.Setup) error { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for Setup") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, requests.Setup) error); ok { r0 = rf(ctx, req) @@ -1424,10 +1226,6 @@ func (_m *Service) Setup(ctx context.Context, req requests.Setup) error { func (_m *Service) SetupVerify(ctx context.Context, sign string) error { ret := _m.Called(ctx, sign) - if len(ret) == 0 { - panic("no return value specified for SetupVerify") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, sign) @@ -1442,10 +1240,6 @@ func (_m *Service) SetupVerify(ctx context.Context, sign string) error { func (_m *Service) SystemDownloadInstallScript(ctx context.Context) (string, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for SystemDownloadInstallScript") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { @@ -1470,10 +1264,6 @@ func (_m *Service) SystemDownloadInstallScript(ctx context.Context) (string, err func (_m *Service) SystemGetInfo(ctx context.Context, req requests.SystemGetInfo) (*models.SystemInfo, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for SystemGetInfo") - } - var r0 *models.SystemInfo var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.SystemGetInfo) (*models.SystemInfo, error)); ok { @@ -1500,10 +1290,6 @@ func (_m *Service) SystemGetInfo(ctx context.Context, req requests.SystemGetInfo func (_m *Service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) error { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for UpdateAPIKey") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.UpdateAPIKey) error); ok { r0 = rf(ctx, req) @@ -1518,10 +1304,6 @@ func (_m *Service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) func (_m *Service) UpdateDevice(ctx context.Context, tenant string, uid models.UID, name *string, publicURL *bool) error { ret := _m.Called(ctx, tenant, uid, name, publicURL) - if len(ret) == 0 { - panic("no return value specified for UpdateDevice") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, models.UID, *string, *bool) error); ok { r0 = rf(ctx, tenant, uid, name, publicURL) @@ -1536,10 +1318,6 @@ func (_m *Service) UpdateDevice(ctx context.Context, tenant string, uid models.U func (_m *Service) UpdateDeviceStatus(ctx context.Context, tenant string, uid models.UID, status models.DeviceStatus) error { ret := _m.Called(ctx, tenant, uid, status) - if len(ret) == 0 { - panic("no return value specified for UpdateDeviceStatus") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, models.UID, models.DeviceStatus) error); ok { r0 = rf(ctx, tenant, uid, status) @@ -1554,10 +1332,6 @@ func (_m *Service) UpdateDeviceStatus(ctx context.Context, tenant string, uid mo func (_m *Service) UpdateDeviceTag(ctx context.Context, uid models.UID, tags []string) error { ret := _m.Called(ctx, uid, tags) - if len(ret) == 0 { - panic("no return value specified for UpdateDeviceTag") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, []string) error); ok { r0 = rf(ctx, uid, tags) @@ -1572,10 +1346,6 @@ func (_m *Service) UpdateDeviceTag(ctx context.Context, uid models.UID, tags []s func (_m *Service) UpdateNamespaceMember(ctx context.Context, req *requests.NamespaceUpdateMember) error { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for UpdateNamespaceMember") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceUpdateMember) error); ok { r0 = rf(ctx, req) @@ -1590,10 +1360,6 @@ func (_m *Service) UpdateNamespaceMember(ctx context.Context, req *requests.Name func (_m *Service) UpdatePasswordUser(ctx context.Context, id string, currentPassword string, newPassword string) error { ret := _m.Called(ctx, id, currentPassword, newPassword) - if len(ret) == 0 { - panic("no return value specified for UpdatePasswordUser") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, id, currentPassword, newPassword) @@ -1608,10 +1374,6 @@ func (_m *Service) UpdatePasswordUser(ctx context.Context, id string, currentPas func (_m *Service) UpdatePublicKey(ctx context.Context, fingerprint string, tenant string, key requests.PublicKeyUpdate) (*models.PublicKey, error) { ret := _m.Called(ctx, fingerprint, tenant, key) - if len(ret) == 0 { - panic("no return value specified for UpdatePublicKey") - } - var r0 *models.PublicKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, requests.PublicKeyUpdate) (*models.PublicKey, error)); ok { @@ -1638,10 +1400,6 @@ func (_m *Service) UpdatePublicKey(ctx context.Context, fingerprint string, tena func (_m *Service) UpdatePublicKeyTags(ctx context.Context, tenant string, fingerprint string, tags []string) error { ret := _m.Called(ctx, tenant, fingerprint, tags) - if len(ret) == 0 { - panic("no return value specified for UpdatePublicKeyTags") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) error); ok { r0 = rf(ctx, tenant, fingerprint, tags) @@ -1656,10 +1414,6 @@ func (_m *Service) UpdatePublicKeyTags(ctx context.Context, tenant string, finge func (_m *Service) UpdateSession(ctx context.Context, uid models.UID, model models.SessionUpdate) error { ret := _m.Called(ctx, uid, model) - if len(ret) == 0 { - panic("no return value specified for UpdateSession") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, models.SessionUpdate) error); ok { r0 = rf(ctx, uid, model) @@ -1674,10 +1428,6 @@ func (_m *Service) UpdateSession(ctx context.Context, uid models.UID, model mode func (_m *Service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([]string, error) { ret := _m.Called(ctx, req) - if len(ret) == 0 { - panic("no return value specified for UpdateUser") - } - var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.UpdateUser) ([]string, error)); ok { @@ -1700,12 +1450,13 @@ func (_m *Service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([] return r0, r1 } -// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewService(t interface { +type mockConstructorTestingTNewService interface { mock.TestingT Cleanup(func()) -}) *Service { +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewService(t mockConstructorTestingTNewService) *Service { mock := &Service{} mock.Mock.Test(t) diff --git a/api/services/session.go b/api/services/session.go index d0077cdd998..e8adf010400 100644 --- a/api/services/session.go +++ b/api/services/session.go @@ -17,6 +17,7 @@ type SessionService interface { DeactivateSession(ctx context.Context, uid models.UID) error KeepAliveSession(ctx context.Context, uid models.UID) error UpdateSession(ctx context.Context, uid models.UID, model models.SessionUpdate) error + EventSession(ctx context.Context, uid models.UID, event *models.SessionEvent) error } func (s *service) ListSessions(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { @@ -92,3 +93,12 @@ func (s *service) UpdateSession(ctx context.Context, uid models.UID, model model return nil } + +func (s *service) EventSession(ctx context.Context, uid models.UID, event *models.SessionEvent) error { + sess, err := s.store.SessionGet(ctx, uid) + if err != nil { + return NewErrSessionNotFound(uid, err) + } + + return s.store.SessionEvent(ctx, models.UID(sess.UID), event) +} diff --git a/api/store/mocks/store.go b/api/store/mocks/store.go index 6510e4ef6fd..ef863ef06a7 100644 --- a/api/store/mocks/store.go +++ b/api/store/mocks/store.go @@ -1500,6 +1500,20 @@ func (_m *Store) SessionDeleteActives(ctx context.Context, uid models.UID) error return r0 } +// SessionEvent provides a mock function with given fields: ctx, uid, event +func (_m *Store) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { + ret := _m.Called(ctx, uid, event) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, models.UID, *models.SessionEvent) error); ok { + r0 = rf(ctx, uid, event) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SessionGet provides a mock function with given fields: ctx, uid func (_m *Store) SessionGet(ctx context.Context, uid models.UID) (*models.Session, error) { ret := _m.Called(ctx, uid) diff --git a/api/store/mongo/migrations/main.go b/api/store/mongo/migrations/main.go index a09f6316651..d7993bbc900 100644 --- a/api/store/mongo/migrations/main.go +++ b/api/store/mongo/migrations/main.go @@ -93,6 +93,7 @@ func GenerateMigrations() []migrate.Migration { migration81, migration82, migration83, + migration84, } } diff --git a/api/store/mongo/migrations/migration_84.go b/api/store/mongo/migrations/migration_84.go new file mode 100644 index 00000000000..722f2189dc3 --- /dev/null +++ b/api/store/mongo/migrations/migration_84.go @@ -0,0 +1,48 @@ +package migrations + +import ( + "context" + + "github.com/sirupsen/logrus" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var migration84 = migrate.Migration{ + Version: 84, + Description: "create index for sessions' type", + Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 84, + "action": "Up", + }).Info("Applying migration up") + name := "events.types" + if _, err := db.Collection("sessions").Indexes().CreateOne(context.Background(), mongo.IndexModel{ + Keys: bson.M{ + "events.types": 1, + }, + Options: &options.IndexOptions{ //nolint:exhaustruct + Name: &name, + }, + }); err != nil { + return err + } + + return nil + }), + Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 84, + "action": "Down", + }).Info("Applying migration down") + if _, err := db.Collection("sessions").Indexes().DropOne(context.Background(), "events.types"); err != nil { + return err + } + + return nil + }), +} diff --git a/api/store/mongo/migrations/migration_84_test.go b/api/store/mongo/migrations/migration_84_test.go new file mode 100644 index 00000000000..76e84b5fb32 --- /dev/null +++ b/api/store/mongo/migrations/migration_84_test.go @@ -0,0 +1,99 @@ +package migrations + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" +) + +func TestMigration84(t *testing.T) { + cases := []struct { + description string + test func() error + }{ + { + "Success to apply up on migration 84", + func() error { + migrations := GenerateMigrations()[83:84] + migrates := migrate.NewMigrate(c.Database("test"), migrations...) + err := migrates.Up(context.Background(), migrate.AllAvailable) + if err != nil { + return err + } + + cursor, err := c.Database("test").Collection("sessions").Indexes().List(context.Background()) + if err != nil { + return err + } + + var found bool + for cursor.Next(context.Background()) { + var index bson.M + if err := cursor.Decode(&index); err != nil { + return err + } + + if index["name"] == "events.types" { + found = true + } + } + + if !found { + return errors.New("index not created") + } + + return nil + }, + }, + { + "Success to apply down on migration 84", + func() error { + migrations := GenerateMigrations()[83:84] + migrates := migrate.NewMigrate(c.Database("test"), migrations...) + err := migrates.Down(context.Background(), migrate.AllAvailable) + if err != nil { + return err + } + + cursor, err := c.Database("test").Collection("sessions").Indexes().List(context.Background()) + if err != nil { + return errors.New("index not dropped") + } + + var found bool + for cursor.Next(context.Background()) { + var index bson.M + if err := cursor.Decode(&index); err != nil { + return err + } + + if index["name"] == "events.types" { + found = true + } + } + + if found { + return errors.New("index not dropped") + } + + return nil + }, + }, + } + + for _, test := range cases { + tc := test + t.Run(tc.description, func(t *testing.T) { + t.Cleanup(func() { + assert.NoError(t, srv.Reset()) + }) + + err := tc.test() + assert.NoError(t, err) + }) + } +} diff --git a/api/store/mongo/session.go b/api/store/mongo/session.go index 5fc4e72bbff..d454233e320 100644 --- a/api/store/mongo/session.go +++ b/api/store/mongo/session.go @@ -266,3 +266,21 @@ func (s *Store) SessionActiveCreate(ctx context.Context, uid models.UID, session return nil } + +// SessionEvent saves a [models.SessionEvent] into the database. +// +// It pushes the event into events type array, and the event type into a separated set. The set is used to improve the +// performance of indexing when looking for sessions. +func (s *Store) SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error { + if _, err := s.db.Collection("sessions").UpdateOne(ctx, + bson.M{"uid": uid}, + bson.M{ + "$addToSet": bson.M{"events.types": event.Type}, + "$push": bson.M{"events.items": event}, + }, + ); err != nil { + return FromMongoError(err) + } + + return nil +} diff --git a/api/store/session.go b/api/store/session.go index a5426a18d72..cce9fa5af54 100644 --- a/api/store/session.go +++ b/api/store/session.go @@ -17,4 +17,6 @@ type SessionStore interface { SessionUpdateDeviceUID(ctx context.Context, oldUID models.UID, newUID models.UID) error SessionSetRecorded(ctx context.Context, uid models.UID, recorded bool) error SessionActiveCreate(ctx context.Context, uid models.UID, session *models.Session) error + // SessionEvent register a log event into the session. + SessionEvent(ctx context.Context, uid models.UID, event *models.SessionEvent) error } diff --git a/pkg/api/internalclient/mocks/internalclient.go b/pkg/api/internalclient/mocks/internalclient.go index bfa97ca156f..e3b71fa0c46 100644 --- a/pkg/api/internalclient/mocks/internalclient.go +++ b/pkg/api/internalclient/mocks/internalclient.go @@ -182,6 +182,20 @@ func (_m *Client) EvaluateKey(fingerprint string, dev *models.Device, username s return r0, r1 } +// EventSession provides a mock function with given fields: uid, log +func (_m *Client) EventSession(uid string, log *models.SessionEvent) error { + ret := _m.Called(uid, log) + + var r0 error + if rf, ok := ret.Get(0).(func(string, *models.SessionEvent) error); ok { + r0 = rf(uid, log) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // FinishSession provides a mock function with given fields: uid func (_m *Client) FinishSession(uid string) []error { ret := _m.Called(uid) diff --git a/pkg/api/internalclient/session.go b/pkg/api/internalclient/session.go index cd5bddad8ea..2c174508610 100644 --- a/pkg/api/internalclient/session.go +++ b/pkg/api/internalclient/session.go @@ -33,6 +33,8 @@ type sessionAPI interface { // UpdateSession updates some fields of [models.Session] using [models.SessionUpdate]. UpdateSession(uid string, model *models.SessionUpdate) error + + EventSession(uid string, log *models.SessionEvent) error } func (c *client) SessionCreate(session requests.SessionCreate) error { @@ -121,3 +123,22 @@ func (c *client) UpdateSession(uid string, model *models.SessionUpdate) error { return nil } + +func (c *client) EventSession(uid string, log *models.SessionEvent) error { + res, err := c.http. + R(). + SetPathParams(map[string]string{ + "tenant": uid, + }). + SetBody(log). + Post("/internal/sessions/{tenant}/events") + if err != nil { + return errors.Join(errors.New("failed to log the event on the session due error"), err) + } + + if res.StatusCode() != 200 { + return errors.New("failed to send the log event") + } + + return nil +} diff --git a/pkg/api/requests/session.go b/pkg/api/requests/session.go index 5c08211e1e6..839c714fa88 100644 --- a/pkg/api/requests/session.go +++ b/pkg/api/requests/session.go @@ -1,5 +1,7 @@ package requests +import "time" + // SessionIDParam is a structure to represent and validate a session UID as path param. type SessionIDParam struct { // UID is the session's UID. @@ -42,3 +44,10 @@ type SessionUpdate struct { Authenticated *bool `json:"authenticated"` Type *string `json:"type"` } + +type SessionEvent struct { + SessionIDParam + Type string `json:"type" validate:"required"` + Timestamp time.Time `json:"timestamp" validate:"required"` + Data any `json:"data" validate:"required"` +} diff --git a/pkg/models/session.go b/pkg/models/session.go index e191052e97f..84cb523198b 100644 --- a/pkg/models/session.go +++ b/pkg/models/session.go @@ -25,6 +25,7 @@ type Session struct { Type string `json:"type" bson:"type"` Term string `json:"term" bson:"term"` Position SessionPosition `json:"position" bson:"position"` + Events SessionEvents `json:"events" bson:"events"` } type ActiveSession struct { @@ -62,3 +63,21 @@ type SessionUpdate struct { Authenticated *bool `json:"authenticated"` Type *string `json:"type"` } + +// SessionEvent represents a session event. +type SessionEvent struct { + // Type of the session. Normally, it is the SSH request name. + Type string `json:"type" bson:"type"` + // Timestamp contains the time when the event was logged. + Timestamp time.Time `json:"timestamp" bson:"timestamp"` + // Data is a generic structure containing data of the event, normally the unmarshaling data of the request. + Data any `json:"data" bson:"data"` +} + +// SessionEvents stores the events registered in a session. +type SessionEvents struct { + // Types field is a set of sessions type to simplify the indexing on the database. + Types []string `json:"types" bson:"types,omitempty"` + // Items contains a list of events happened in a session. + Items []SessionEvent `json:"items" bson:"items,omitempty"` +} diff --git a/ssh/server/channels/session.go b/ssh/server/channels/session.go index 15e6d850ab1..98ea00838f2 100644 --- a/ssh/server/channels/session.go +++ b/ssh/server/channels/session.go @@ -48,14 +48,18 @@ const ( // // https://www.rfc-editor.org/rfc/rfc4254#section-6.7 WindowChangeRequestType = "window-change" - // In a defined interval, the Agent sends a keepalive request to maintain the session apoint, even when no data is - // send. + // It is a custom request type that the Agent sends to maintain the session alive, even when no data is sent. KeepAliveRequestType = KeepAliveRequestTypePrefix + "@shellhub.io" // When the command running at the other end terminates, the following message can be sent to return the exit // status of the command. Returning the status is RECOMMENDED. // // https://www.rfc-editor.org/rfc/rfc4254#section-6.10 ExitStatusRequest = "exit-status" + // The remote command may also terminate violently due to a signal. Such a condition can be indicated by the + // following message. A zero 'exit_status' usually means that the command terminated successfully. + // + // https://datatracker.ietf.org/doc/html/rfc4254#section-6.10 + ExitSignalRequest = "exit-signal" ) // A client may request agent forwarding for a previously-opened session using the following channel request. This @@ -175,6 +179,15 @@ func DefaultSessionHandler() gliderssh.ChannelHandler { return } + switch req.Type { + case ExitStatusRequest: + session.Event[session.Status](sess, req.Type, req.Payload) + case ExitSignalRequest: + session.Event[session.Signal](sess, req.Type, req.Payload) + default: + sess.Event(req.Type, req.Payload) + } + logger.Debugf("request from agent to client: %s", req.Type) ok, err := client.SendRequest(req.Type, req.WantReply, req.Payload) @@ -203,6 +216,10 @@ func DefaultSessionHandler() gliderssh.ChannelHandler { logger.WithError(err).Warn("failed to get the namespace announcement") } } + + sess.Event(req.Type, req.Payload) + case ExecRequestType, SubsystemRequestType: + session.Event[session.Command](sess, req.Type, req.Payload) case PtyRequestType: var pty session.Pty @@ -211,6 +228,8 @@ func DefaultSessionHandler() gliderssh.ChannelHandler { } sess.Pty = pty + + sess.Event(req.Type, pty) //nolint:errcheck case WindowChangeRequestType: var dimensions session.Dimensions @@ -220,9 +239,12 @@ func DefaultSessionHandler() gliderssh.ChannelHandler { sess.Pty.Columns = dimensions.Columns sess.Pty.Rows = dimensions.Rows + + sess.Event(req.Type, dimensions) //nolint:errcheck case AuthRequestOpenSSHRequest: gliderssh.SetAgentRequested(ctx) + sess.Event(req.Type, req.Payload) go func() { clientConn := ctx.Value(gliderssh.ContextKeyConn).(gossh.Conn) agentChannels := sess.AgentClient.HandleChannelOpen(AuthRequestOpenSSHChannel) @@ -260,6 +282,8 @@ func DefaultSessionHandler() gliderssh.ChannelHandler { logger.WithError(err).Trace("auth request channel piping done") } }() + default: + sess.Event(req.Type, req.Payload) } logger.Debugf("request from client to agent: %s", req.Type) diff --git a/ssh/server/channels/tcpip.go b/ssh/server/channels/tcpip.go index 944b7a240ac..8324aeeece2 100644 --- a/ssh/server/channels/tcpip.go +++ b/ssh/server/channels/tcpip.go @@ -30,10 +30,10 @@ func DefaultDirectTCPIPHandler(server *gliderssh.Server, conn *gossh.ServerConn, }).Trace("handling direct-tcpip channel") type channelData struct { - DestAddr string - DestPort uint32 - OriginAddr string - OriginPort uint32 + DestAddr string `json:"dest_addr"` + DestPort uint32 `json:"dest_port"` + OriginAddr string `json:"origin_addr"` + OriginPort uint32 `json:"origin_port"` } data := new(channelData) @@ -65,6 +65,8 @@ func DefaultDirectTCPIPHandler(server *gliderssh.Server, conn *gossh.ServerConn, return } + sess.Event(DirectTCPIPChannel, data) //nolint:errcheck + dest := net.JoinHostPort(data.DestAddr, strconv.FormatInt(int64(data.DestPort), 10)) // NOTE: Certain SSH connections may not necessitate a dedicated handler, such as an SSH handler. diff --git a/ssh/session/session.go b/ssh/session/session.go index 9c98406abe1..510eeddc2aa 100644 --- a/ssh/session/session.go +++ b/ssh/session/session.go @@ -25,21 +25,40 @@ import ( gossh "golang.org/x/crypto/ssh" ) +type Command struct { + Command string `json:"command"` +} + +type Subsystem struct { + Subsystem string `json:"subsystem"` +} + +type Status struct { + Status uint32 `json:"status"` +} + +type Signal struct { + Name uint32 `json:"status"` + Dumped bool `json:"dumped"` + Message string `json:"message"` + Lang string `json:"lang"` +} + type Dimensions struct { - Columns uint32 - Rows uint32 - Width uint32 - Height uint32 + Columns uint32 `json:"columns"` + Rows uint32 `json:"rows"` + Width uint32 `json:"width"` + Height uint32 `json:"height"` } -// NOTICE: [Pty] cannot use [Dimensions] inside itself ude [ssh.Unmarshal] issues. +// NOTICE: [Pty] cannot use [Dimensions] inside itself due [ssh.Unmarshal] issues. type Pty struct { - Term string - Columns uint32 - Rows uint32 - Width uint32 - Height uint32 - Modelist string + Term string `json:"term"` + Columns uint32 `json:"columns" ` + Rows uint32 `json:"rows"` + Width uint32 `json:"width"` + Height uint32 `json:"height"` + Modelist []byte `json:"modelist"` } type Data struct { @@ -417,6 +436,28 @@ func (s *Session) Record(ctx context.Context, url string) (*websocket.Conn, erro return conn, nil } +func Event[D any](sess *Session, t string, data []byte) { + d := new(D) + if err := gossh.Unmarshal(data, d); err != nil { + return + } + + go sess.api.EventSession(sess.UID, &models.SessionEvent{ //nolint:errcheck + Type: t, + Timestamp: clock.Now(), + Data: d, + }) +} + +// Events register a event to the session. +func (s *Session) Event(t string, data any) { + go s.api.EventSession(s.UID, &models.SessionEvent{ //nolint:errcheck + Type: t, + Timestamp: clock.Now(), + Data: data, + }) +} + func (s *Session) KeepAlive() error { if errs := s.api.KeepAliveSession(s.UID); len(errs) > 0 { log.Error(errs[0]) @@ -496,10 +537,3 @@ func (s *Session) Finish() (err error) { return nil } - -// Type updates the session's type on the database. -func (s *Session) Type(kind string) error { - return s.api.UpdateSession(s.UID, &models.SessionUpdate{ - Type: &kind, - }) -}