Add auditing to server CLI. Also: - simplify auditing in API layer - reduce number of AddMeta calls - have models serialize themselves - more consistent field naming
157 строки
4.9 KiB
Go
157 строки
4.9 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os/user"
|
|
|
|
"github.com/mattermost/mattermost-server/v5/audit"
|
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
)
|
|
|
|
const (
|
|
RestLevelID = 240
|
|
RestContentLevelID = 241
|
|
RestPermsLevelID = 242
|
|
CLILevelID = 243
|
|
)
|
|
|
|
var (
|
|
RestLevel = audit.Level{ID: RestLevelID, Name: "audit-rest", Stacktrace: false}
|
|
RestContentLevel = audit.Level{ID: RestContentLevelID, Name: "audit-rest-content", Stacktrace: false}
|
|
RestPermsLevel = audit.Level{ID: RestPermsLevelID, Name: "audit-rest-perms", Stacktrace: false}
|
|
CLILevel = audit.Level{ID: CLILevelID, Name: "audit-cli", Stacktrace: false}
|
|
)
|
|
|
|
func (a *App) GetAudits(userId string, limit int) (model.Audits, *model.AppError) {
|
|
return a.Srv().Store.Audit().Get(userId, 0, limit)
|
|
}
|
|
|
|
func (a *App) GetAuditsPage(userId string, page int, perPage int) (model.Audits, *model.AppError) {
|
|
return a.Srv().Store.Audit().Get(userId, page*perPage, perPage)
|
|
}
|
|
|
|
// LogAuditRec logs an audit record using default CLILevel.
|
|
func (a *App) LogAuditRec(rec *audit.Record, err error) {
|
|
a.LogAuditRecWithLevel(rec, CLILevel, err)
|
|
}
|
|
|
|
// LogAuditRecWithLevel logs an audit record using specified Level.
|
|
func (a *App) LogAuditRecWithLevel(rec *audit.Record, level audit.Level, err error) {
|
|
if rec == nil {
|
|
return
|
|
}
|
|
if err != nil {
|
|
if appErr, ok := err.(*model.AppError); ok {
|
|
rec.AddMeta("err", appErr.Error())
|
|
rec.AddMeta("code", appErr.StatusCode)
|
|
} else {
|
|
rec.AddMeta("err", err)
|
|
}
|
|
rec.Fail()
|
|
}
|
|
a.Srv().Audit.LogRecord(level, *rec)
|
|
}
|
|
|
|
// MakeAuditRecord creates a audit record pre-populated with defaults.
|
|
func (a *App) MakeAuditRecord(event string, initialStatus string) *audit.Record {
|
|
var userID string
|
|
user, err := user.Current()
|
|
if err == nil {
|
|
userID = fmt.Sprintf("%s:%s", user.Uid, user.Username)
|
|
}
|
|
|
|
rec := &audit.Record{
|
|
APIPath: "",
|
|
Event: event,
|
|
Status: initialStatus,
|
|
UserID: userID,
|
|
SessionID: "",
|
|
Client: fmt.Sprintf("server %s-%s", model.BuildNumber, model.BuildHash),
|
|
IPAddress: "",
|
|
Meta: audit.Meta{audit.KeyClusterID: a.GetClusterId()},
|
|
}
|
|
rec.AddMetaTypeConverter(model.AuditModelTypeConv)
|
|
|
|
return rec
|
|
}
|
|
|
|
func (s *Server) configureAudit(adt *audit.Audit) {
|
|
adt.OnQueueFull = s.onAuditTargetQueueFull
|
|
adt.OnError = s.onAuditError
|
|
|
|
// Configure target for SysLog via TLS.
|
|
// See https://www.rsyslog.com/doc/v8-stable/tutorials/tls_cert_summary.html
|
|
if *s.Config().ExperimentalAuditSettings.SysLogEnabled {
|
|
IP := *s.Config().ExperimentalAuditSettings.SysLogIP
|
|
if IP == "" {
|
|
IP = "localhost"
|
|
}
|
|
port := *s.Config().ExperimentalAuditSettings.SysLogPort
|
|
if port <= 0 {
|
|
port = 6514
|
|
}
|
|
raddr := fmt.Sprintf("%s:%d", IP, port)
|
|
maxQSize := *s.Config().ExperimentalAuditSettings.SysLogMaxQueueSize
|
|
if maxQSize <= 0 {
|
|
maxQSize = audit.DefMaxQueueSize
|
|
}
|
|
|
|
params := &audit.SyslogParams{
|
|
Raddr: raddr,
|
|
Cert: *s.Config().ExperimentalAuditSettings.SysLogCert,
|
|
Tag: *s.Config().ExperimentalAuditSettings.SysLogTag,
|
|
Insecure: *s.Config().ExperimentalAuditSettings.SysLogInsecure,
|
|
}
|
|
|
|
filter := adt.MakeFilter(RestLevel, RestContentLevel, RestPermsLevel, CLILevel)
|
|
formatter := adt.MakeJSONFormatter()
|
|
target, err := audit.NewSyslogTLSTarget(filter, formatter, params, maxQSize)
|
|
if err != nil {
|
|
mlog.Error("cannot configure SysLogTLS audit target", mlog.Err(err))
|
|
} else {
|
|
mlog.Debug("SysLogTLS audit target connected successfully", mlog.String("raddr", raddr))
|
|
adt.AddTarget(target)
|
|
}
|
|
}
|
|
|
|
// Configure target for rotating file output
|
|
if *s.Config().ExperimentalAuditSettings.FileEnabled {
|
|
opts := audit.FileOptions{
|
|
Filename: *s.Config().ExperimentalAuditSettings.FileName,
|
|
MaxSize: *s.Config().ExperimentalAuditSettings.FileMaxSizeMB,
|
|
MaxAge: *s.Config().ExperimentalAuditSettings.FileMaxAgeDays,
|
|
MaxBackups: *s.Config().ExperimentalAuditSettings.FileMaxBackups,
|
|
Compress: *s.Config().ExperimentalAuditSettings.FileCompress,
|
|
}
|
|
|
|
maxQueueSize := *s.Config().ExperimentalAuditSettings.FileMaxQueueSize
|
|
if maxQueueSize <= 0 {
|
|
maxQueueSize = audit.DefMaxQueueSize
|
|
}
|
|
|
|
filter := adt.MakeFilter(RestLevel, RestContentLevel, RestPermsLevel, CLILevel)
|
|
formatter := adt.MakeJSONFormatter()
|
|
formatter.DisableTimestamp = false
|
|
formatter.Indent = "\n"
|
|
target, err := audit.NewFileTarget(filter, formatter, opts, maxQueueSize)
|
|
if err != nil {
|
|
mlog.Error("cannot configure File audit target", mlog.Err(err))
|
|
} else {
|
|
mlog.Debug("File audit target created successfully", mlog.String("filename", opts.Filename))
|
|
adt.AddTarget(target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) onAuditTargetQueueFull(qname string, maxQSize int) {
|
|
mlog.Warn("Audit Queue Full", mlog.String("qname", qname), mlog.Int("maxQSize", maxQSize))
|
|
}
|
|
|
|
func (s *Server) onAuditError(err error) {
|
|
mlog.Error("Audit Error", mlog.Err(err))
|
|
}
|