From 41033449895ea015c603edfad34373fee34e056c Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Tue, 8 Oct 2024 18:39:00 +0200 Subject: [PATCH] EventManager: add datetime placeholder Signed-off-by: Nicola Murino --- internal/acme/acme.go | 2 +- internal/common/actions.go | 13 ++++++++----- internal/common/actions_test.go | 23 ++++++++++++----------- internal/common/defenderdb.go | 2 +- internal/common/defendermem.go | 2 +- internal/common/eventmanager.go | 16 ++++++++++++---- internal/common/eventmanager_test.go | 22 ++++++++++++++++++++++ internal/common/protocol_test.go | 12 +++++++----- internal/httpd/oidc.go | 2 +- static/locales/en/translation.json | 1 + static/locales/it/translation.json | 1 + templates/webadmin/eventaction.html | 3 +++ 12 files changed, 70 insertions(+), 29 deletions(-) diff --git a/internal/acme/acme.go b/internal/acme/acme.go index 347375e1b..8506f7214 100644 --- a/internal/acme/acme.go +++ b/internal/acme/acme.go @@ -673,7 +673,7 @@ func (c *Configuration) notifyCertificateRenewal(domain string, err error) { params := common.EventParams{ Name: domain, Event: "Certificate renewal", - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), } if err != nil { params.Status = 2 diff --git a/internal/common/actions.go b/internal/common/actions.go index 2f8c39f2b..1da74b8a9 100644 --- a/internal/common/actions.go +++ b/internal/common/actions.go @@ -92,8 +92,9 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str if !hasHook && !hasNotifiersPlugin && !hasRules { return 0, nil } + dateTime := time.Now() event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "", - conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, nil) + conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, dateTime, nil) if hasNotifiersPlugin { plugin.Handler.NotifyFsEvent(event) } @@ -113,7 +114,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str Protocol: event.Protocol, IP: event.IP, Role: event.Role, - Timestamp: event.Timestamp, + Timestamp: dateTime, Email: conn.User.Email, Object: nil, } @@ -138,8 +139,9 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua if !hasHook && !hasNotifiersPlugin && !hasRules { return nil } + dateTime := time.Now() notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd, - conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, metadata) + conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, dateTime, metadata) if hasNotifiersPlugin { plugin.Handler.NotifyFsEvent(notification) } @@ -160,7 +162,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua Protocol: notification.Protocol, IP: notification.IP, Role: notification.Role, - Timestamp: notification.Timestamp, + Timestamp: dateTime, Email: conn.User.Email, Object: nil, Metadata: metadata, @@ -198,6 +200,7 @@ func newActionNotification( operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string, fileSize int64, openFlags, status int, elapsed int64, + datetime time.Time, metadata map[string]string, ) *notifier.FsEvent { var bucket, endpoint string @@ -239,7 +242,7 @@ func newActionNotification( SessionID: sessionID, OpenFlags: openFlags, Role: user.Role, - Timestamp: time.Now().UnixNano(), + Timestamp: datetime.UnixNano(), Elapsed: elapsed, Metadata: metadata, } diff --git a/internal/common/actions_test.go b/internal/common/actions_test.go index 87b168f61..6ac0463c0 100644 --- a/internal/common/actions_test.go +++ b/internal/common/actions_test.go @@ -22,6 +22,7 @@ import ( "path/filepath" "runtime" "testing" + "time" "github.com/lithammer/shortuuid/v4" "github.com/rs/xid" @@ -71,7 +72,7 @@ func TestNewActionNotification(t *testing.T) { c := NewBaseConnection("id", ProtocolSSH, "", "", user) sessionID := xid.New().String() a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, - 123, 0, c.getNotificationStatus(errors.New("fake error")), 0, nil) + 123, 0, c.getNotificationStatus(errors.New("fake error")), 0, time.Now(), nil) assert.Equal(t, user.Username, a.Username) assert.Equal(t, 0, len(a.Bucket)) assert.Equal(t, 0, len(a.Endpoint)) @@ -79,38 +80,38 @@ func TestNewActionNotification(t *testing.T) { user.FsConfig.Provider = sdk.S3FilesystemProvider a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, - 123, 0, c.getNotificationStatus(nil), 0, nil) + 123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil) assert.Equal(t, "s3bucket", a.Bucket) assert.Equal(t, "endpoint", a.Endpoint) assert.Equal(t, 1, a.Status) user.FsConfig.Provider = sdk.GCSFilesystemProvider a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, - 123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, nil) + 123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, time.Now(), nil) assert.Equal(t, "gcsbucket", a.Bucket) assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 3, a.Status) a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, - 123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, nil) + 123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, time.Now(), nil) assert.Equal(t, "gcsbucket", a.Bucket) assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 3, a.Status) user.FsConfig.Provider = sdk.HTTPFilesystemProvider a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, - 123, 0, c.getNotificationStatus(nil), 0, nil) + 123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil) assert.Equal(t, "httpendpoint", a.Endpoint) assert.Equal(t, 1, a.Status) user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, - 123, 0, c.getNotificationStatus(nil), 0, nil) + 123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil) assert.Equal(t, "azcontainer", a.Bucket) assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, 1, a.Status) a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, - 123, os.O_APPEND, c.getNotificationStatus(nil), 0, nil) + 123, os.O_APPEND, c.getNotificationStatus(nil), 0, time.Now(), nil) assert.Equal(t, "azcontainer", a.Bucket) assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, 1, a.Status) @@ -118,7 +119,7 @@ func TestNewActionNotification(t *testing.T) { user.FsConfig.Provider = sdk.SFTPFilesystemProvider a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, - 123, 0, c.getNotificationStatus(nil), 0, nil) + 123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil) assert.Equal(t, "sftpendpoint", a.Endpoint) } @@ -135,7 +136,7 @@ func TestActionHTTP(t *testing.T) { }, } a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", - xid.New().String(), 123, 0, 1, 0, nil) + xid.New().String(), 123, 0, 1, 0, time.Now(), nil) status, err := actionHandler.Handle(a) assert.NoError(t, err) assert.Equal(t, 1, status) @@ -175,7 +176,7 @@ func TestActionCMD(t *testing.T) { } sessionID := shortuuid.New() a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, - 123, 0, 1, 0, map[string]string{"key": "value"}) + 123, 0, 1, 0, time.Now(), map[string]string{"key": "value"}) status, err := actionHandler.Handle(a) assert.NoError(t, err) assert.Equal(t, 1, status) @@ -208,7 +209,7 @@ func TestWrongActions(t *testing.T) { } a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(), - 123, 0, 1, 0, nil) + 123, 0, 1, 0, time.Now(), nil) status, err := actionHandler.Handle(a) assert.Error(t, err, "action with bad command must fail") assert.Equal(t, 1, status) diff --git a/internal/common/defenderdb.go b/internal/common/defenderdb.go index d60879b41..3f0a4849e 100644 --- a/internal/common/defenderdb.go +++ b/internal/common/defenderdb.go @@ -110,7 +110,7 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool { eventManager.handleIPBlockedEvent(EventParams{ Event: ipBlockedEventName, IP: ip, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, }) } diff --git a/internal/common/defendermem.go b/internal/common/defendermem.go index 6a908d04c..b5642c4ed 100644 --- a/internal/common/defendermem.go +++ b/internal/common/defendermem.go @@ -218,7 +218,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool { eventManager.handleIPBlockedEvent(EventParams{ Event: ipBlockedEventName, IP: ip, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, }) } else { diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index eb4b3a2a1..5e79efa0f 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -58,6 +58,7 @@ const ( maxAttachmentsSize = int64(10 * 1024 * 1024) objDataPlaceholder = "{{ObjectData}}" objDataPlaceholderString = "{{ObjectDataString}}" + dateTimeMillisFormat = "2006-01-02T15:04:05.000" ) // Supported IDP login events @@ -89,7 +90,7 @@ func init() { ObjectType: objectType, IP: ip, Role: role, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Object: object, } if u, ok := object.(*dataprovider.User); ok { @@ -557,7 +558,7 @@ type EventParams struct { IP string Role string Email string - Timestamp int64 + Timestamp time.Time UID string IDPCustomFields *map[string]string Object plugin.Renderer @@ -641,7 +642,7 @@ func (p *EventParams) setBackupParams(backupPath string) { p.FsPath = backupPath p.ObjectName = filepath.Base(backupPath) p.VirtualPath = "/" + p.ObjectName - p.Timestamp = time.Now().UnixNano() + p.Timestamp = time.Now() info, err := os.Stat(backupPath) if err == nil { p.FileSize = info.Size() @@ -775,6 +776,12 @@ func (*EventParams) getStringReplacement(val string, jsonEscaped bool) string { } func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []string { + var dateTimeString string + if Config.TZ == "local" { + dateTimeString = p.Timestamp.Local().Format(dateTimeMillisFormat) + } else { + dateTimeString = p.Timestamp.UTC().Format(dateTimeMillisFormat) + } replacements := []string{ "{{Name}}", p.getStringReplacement(p.Name, jsonEscaped), "{{Event}}", p.Event, @@ -791,7 +798,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s "{{IP}}", p.IP, "{{Role}}", p.getStringReplacement(p.Role, jsonEscaped), "{{Email}}", p.getStringReplacement(p.Email, jsonEscaped), - "{{Timestamp}}", strconv.FormatInt(p.Timestamp, 10), + "{{Timestamp}}", strconv.FormatInt(p.Timestamp.UnixNano(), 10), + "{{DateTime}}", dateTimeString, "{{StatusString}}", p.getStatusString(), "{{UID}}", p.getStringReplacement(p.UID, jsonEscaped), "{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped), diff --git a/internal/common/eventmanager_test.go b/internal/common/eventmanager_test.go index f00330b14..f23a31cc3 100644 --- a/internal/common/eventmanager_test.go +++ b/internal/common/eventmanager_test.go @@ -800,6 +800,28 @@ func TestEventManagerErrors(t *testing.T) { stopEventScheduler() } +func TestDateTimePlaceholder(t *testing.T) { + oldTZ := Config.TZ + + Config.TZ = "" + dateTime := time.Now() + params := EventParams{ + Timestamp: dateTime, + } + replacements := params.getStringReplacements(false, false) + r := strings.NewReplacer(replacements...) + res := r.Replace("{{DateTime}}") + assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat), res) + + Config.TZ = "local" + replacements = params.getStringReplacements(false, false) + r = strings.NewReplacer(replacements...) + res = r.Replace("{{DateTime}}") + assert.Equal(t, dateTime.Local().Format(dateTimeMillisFormat), res) + + Config.TZ = oldTZ +} + func TestEventRuleActions(t *testing.T) { actionName := "test rule action" action := dataprovider.BaseEventAction{ diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go index 1e0fcb7eb..840fb3743 100644 --- a/internal/common/protocol_test.go +++ b/internal/common/protocol_test.go @@ -5431,7 +5431,7 @@ func TestBackupAsAttachment(t *testing.T) { common.HandleCertificateEvent(common.EventParams{ Name: "example.com", - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, Event: renewalEvent, }) @@ -7108,7 +7108,7 @@ func TestEventRuleCertificate(t *testing.T) { Recipients: []string{"test@example.com"}, Subject: `"{{Event}} {{StatusString}}"`, ContentType: 0, - Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}", + Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}", }, }, } @@ -7163,7 +7163,7 @@ func TestEventRuleCertificate(t *testing.T) { common.HandleCertificateEvent(common.EventParams{ Name: "example.com", - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, Event: renewalEvent, }) @@ -7178,9 +7178,10 @@ func TestEventRuleCertificate(t *testing.T) { assert.Contains(t, email.Data, `Domain: example.com Timestamp`) lastReceivedEmail.reset() + dateTime := time.Now() params := common.EventParams{ Name: "example.com", - Timestamp: time.Now().UnixNano(), + Timestamp: dateTime, Status: 2, Event: renewalEvent, } @@ -7195,6 +7196,7 @@ func TestEventRuleCertificate(t *testing.T) { assert.True(t, slices.Contains(email.To, "test@example.com")) assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent)) assert.Contains(t, email.Data, `Domain: example.com Timestamp`) + assert.Contains(t, email.Data, dateTime.UTC().Format("2006-01-02T15:04:05.000")) assert.Contains(t, email.Data, errRenew.Error()) _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK) @@ -7208,7 +7210,7 @@ func TestEventRuleCertificate(t *testing.T) { // ignored no more certificate rules common.HandleCertificateEvent(common.EventParams{ Name: "example.com", - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, Event: renewalEvent, }) diff --git a/internal/httpd/oidc.go b/internal/httpd/oidc.go index 19af66b35..2ab221378 100644 --- a/internal/httpd/oidc.go +++ b/internal/httpd/oidc.go @@ -408,7 +408,7 @@ func (t *oidcToken) getUser(r *http.Request) error { Name: t.Username, IP: ipAddr, Protocol: common.ProtocolOIDC, - Timestamp: time.Now().UnixNano(), + Timestamp: time.Now(), Status: 1, } if t.isAdmin() { diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json index 6db16b356..33cdc1b32 100644 --- a/static/locales/en/translation.json +++ b/static/locales/en/translation.json @@ -1057,6 +1057,7 @@ "ip": "Client IP address", "role": "User or admin role", "timestamp": "Event timestamp as nanoseconds since epoch", + "datetime": "Event timestamp formatted as YYYY-MM-DDTHH:MM:SS.ZZZ", "email": "For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases", "object_data": "Provider object data serialized as JSON with sensitive fields removed", "object_data_string": "Provider object data as JSON escaped string with sensitive fields removed", diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json index de5df0d60..c8a2f4a62 100644 --- a/static/locales/it/translation.json +++ b/static/locales/it/translation.json @@ -1057,6 +1057,7 @@ "ip": "Indirizzo IP del client", "role": "Ruolo dell'utente o dell'amministratore", "timestamp": "Timestamp dell'evento in nanosecondi dall'epoch time", + "datetime": "Timestamp dell'evento formattato come YYYY-MM-DDTHH:MM:SS.ZZZ", "email": "Per gli eventi del file system, questa รจ l'e-mail associata all'utente che esegue l'azione. Per gli eventi del provider, si tratta dell'e-mail associata all'utente o all'amministratore interessato. Vuoto in tutti gli altri casi", "object_data": "Dati dell'oggetto provider serializzati come JSON con campi sensibili rimossi", "object_data_string": "Dati dell'oggetto provider serializzati come stringa JSON escaped con campi sensibili rimossi", diff --git a/templates/webadmin/eventaction.html b/templates/webadmin/eventaction.html index a2b77bca6..3773a88d8 100644 --- a/templates/webadmin/eventaction.html +++ b/templates/webadmin/eventaction.html @@ -941,6 +941,9 @@