Switch to SQLite for activity tracking
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"emperror.dev/errors"
|
||||
"encoding/gob"
|
||||
"github.com/apex/log"
|
||||
"github.com/pterodactyl/wings/internal/database"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"regexp"
|
||||
"github.com/pterodactyl/wings/server"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -17,7 +13,6 @@ type eventHandler struct {
|
||||
server string
|
||||
}
|
||||
|
||||
type Event string
|
||||
type FileAction struct {
|
||||
// Entity is the targeted file or directory (depending on the event) that the action
|
||||
// is being performed _against_, such as "/foo/test.txt". This will always be the full
|
||||
@@ -29,53 +24,33 @@ type FileAction struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
type EventRecord struct {
|
||||
Event Event
|
||||
Action FileAction
|
||||
IP string
|
||||
User string
|
||||
Timestamp time.Time
|
||||
}
|
||||
// Log parses a SFTP specific file activity event and then passes it off to be stored
|
||||
// in the normal activity database.
|
||||
func (eh *eventHandler) Log(e server.Event, fa FileAction) error {
|
||||
metadata := map[string]interface{}{
|
||||
"files": []string{fa.Entity},
|
||||
}
|
||||
if fa.Target != "" {
|
||||
metadata["files"] = []map[string]string{
|
||||
{"from": fa.Entity, "to": fa.Target},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
EventWrite = Event("write")
|
||||
EventCreate = Event("create")
|
||||
EventCreateDirectory = Event("create-directory")
|
||||
EventRename = Event("rename")
|
||||
EventDelete = Event("delete")
|
||||
)
|
||||
|
||||
var ipTrimRegex = regexp.MustCompile(`(:\d*)?$`)
|
||||
|
||||
// Log logs an event into the Wings bucket for SFTP activity which then allows a seperate
|
||||
// cron to run and parse the events into a more manageable stream of event data to send
|
||||
// back to the Panel instance.
|
||||
func (eh *eventHandler) Log(e Event, fa FileAction) error {
|
||||
r := EventRecord{
|
||||
Event: e,
|
||||
Action: fa,
|
||||
IP: ipTrimRegex.ReplaceAllString(eh.ip, ""),
|
||||
r := server.Activity{
|
||||
User: eh.user,
|
||||
Server: eh.server,
|
||||
Event: e,
|
||||
Metadata: metadata,
|
||||
IP: eh.ip,
|
||||
Timestamp: time.Now().UTC(),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
if err := enc.Encode(r); err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to encode event")
|
||||
}
|
||||
|
||||
return database.DB().Update(func(tx *nutsdb.Tx) error {
|
||||
if err := tx.RPush(database.SftpActivityBucket, []byte(eh.server), buf.Bytes()); err != nil {
|
||||
return errors.Wrap(err, "sftp: failed to push event to stack")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return errors.Wrap(r.Save(), "sftp: failed to store file event")
|
||||
}
|
||||
|
||||
// MustLog is a wrapper around log that will trigger a fatal error and exit the application
|
||||
// if an error is encountered during the logging of the event.
|
||||
func (eh *eventHandler) MustLog(e Event, fa FileAction) {
|
||||
func (eh *eventHandler) MustLog(e server.Event, fa FileAction) {
|
||||
if err := eh.Log(e, fa); err != nil {
|
||||
log.WithField("error", err).Fatal("sftp: failed to log event")
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ func (h *Handler) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
||||
// Chown may or may not have been called in the touch function, so always do
|
||||
// it at this point to avoid the file being improperly owned.
|
||||
_ = h.fs.Chown(request.Filepath)
|
||||
event := EventWrite
|
||||
event := server.ActivitySftpWrite
|
||||
if permission == PermissionFileCreate {
|
||||
event = EventCreate
|
||||
event = server.ActivitySftpCreate
|
||||
}
|
||||
h.events.MustLog(event, FileAction{Entity: request.Filepath})
|
||||
return f, nil
|
||||
@@ -185,7 +185,7 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||
l.WithField("error", err).Error("failed to rename file")
|
||||
return sftp.ErrSSHFxFailure
|
||||
}
|
||||
h.events.MustLog(EventRename, FileAction{Entity: request.Filepath, Target: request.Target})
|
||||
h.events.MustLog(server.ActivitySftpRename, FileAction{Entity: request.Filepath, Target: request.Target})
|
||||
break
|
||||
// Handle deletion of a directory. This will properly delete all of the files and
|
||||
// folders within that directory if it is not already empty (unlike a lot of SFTP
|
||||
@@ -199,7 +199,7 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||
l.WithField("error", err).Error("failed to remove directory")
|
||||
return sftp.ErrSSHFxFailure
|
||||
}
|
||||
h.events.MustLog(EventDelete, FileAction{Entity: request.Filepath})
|
||||
h.events.MustLog(server.ActivitySftpDelete, FileAction{Entity: request.Filepath})
|
||||
return sftp.ErrSSHFxOk
|
||||
// Handle requests to create a new Directory.
|
||||
case "Mkdir":
|
||||
@@ -212,7 +212,7 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||
l.WithField("error", err).Error("failed to create directory")
|
||||
return sftp.ErrSSHFxFailure
|
||||
}
|
||||
h.events.MustLog(EventCreateDirectory, FileAction{Entity: request.Filepath})
|
||||
h.events.MustLog(server.ActivitySftpCreateDirectory, FileAction{Entity: request.Filepath})
|
||||
break
|
||||
// Support creating symlinks between files. The source and target must resolve within
|
||||
// the server home directory.
|
||||
@@ -245,7 +245,7 @@ func (h *Handler) Filecmd(request *sftp.Request) error {
|
||||
l.WithField("error", err).Error("failed to remove a file")
|
||||
return sftp.ErrSSHFxFailure
|
||||
}
|
||||
h.events.MustLog(EventDelete, FileAction{Entity: request.Filepath})
|
||||
h.events.MustLog(server.ActivitySftpDelete, FileAction{Entity: request.Filepath})
|
||||
return sftp.ErrSSHFxOk
|
||||
default:
|
||||
return sftp.ErrSSHFxOpUnsupported
|
||||
|
||||
Reference in New Issue
Block a user