-
Notifications
You must be signed in to change notification settings - Fork 7.6k
/
Copy pathaudit.go
175 lines (147 loc) · 4.67 KB
/
audit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package audit
import (
"fmt"
"sort"
"github.com/mattermost/logr"
"github.com/mattermost/logr/format"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
type Audit struct {
lgr *logr.Logr
logger logr.Logger
// OnQueueFull is called on an attempt to add an audit record to a full queue.
// Return true to drop record, or false to block until there is room in queue.
OnQueueFull func(qname string, maxQueueSize int) bool
// OnError is called when an error occurs while writing an audit record.
OnError func(err error)
}
func (a *Audit) Init(maxQueueSize int) {
a.lgr = &logr.Logr{MaxQueueSize: maxQueueSize}
a.logger = a.lgr.NewLogger()
a.lgr.OnQueueFull = a.onQueueFull
a.lgr.OnTargetQueueFull = a.onTargetQueueFull
a.lgr.OnLoggerError = a.onLoggerError
}
// MakeFilter creates a filter which only allows the specified audit levels to be output.
func (a *Audit) MakeFilter(level ...mlog.LogLevel) *logr.CustomFilter {
filter := &logr.CustomFilter{}
for _, l := range level {
filter.Add(logr.Level(l))
}
return filter
}
// MakeJSONFormatter creates a formatter that outputs JSON suitable for audit records.
func (a *Audit) MakeJSONFormatter() *format.JSON {
f := &format.JSON{
DisableTimestamp: true,
DisableMsg: true,
DisableStacktrace: true,
DisableLevel: true,
ContextSorter: sortAuditFields,
}
return f
}
// LogRecord emits an audit record with complete info.
func (a *Audit) LogRecord(level mlog.LogLevel, rec Record) {
flds := logr.Fields{}
flds[KeyAPIPath] = rec.APIPath
flds[KeyEvent] = rec.Event
flds[KeyStatus] = rec.Status
flds[KeyUserID] = rec.UserID
flds[KeySessionID] = rec.SessionID
flds[KeyClient] = rec.Client
flds[KeyIPAddress] = rec.IPAddress
for k, v := range rec.Meta {
flds[k] = v
}
l := a.logger.WithFields(flds)
l.Log(logr.Level(level))
}
// Log emits an audit record based on minimum required info.
func (a *Audit) Log(level mlog.LogLevel, path string, evt string, status string, userID string, sessionID string, meta Meta) {
a.LogRecord(level, Record{
APIPath: path,
Event: evt,
Status: status,
UserID: userID,
SessionID: sessionID,
Meta: meta,
})
}
// AddTarget adds a Logr target to the list of targets each audit record will be output to.
func (a *Audit) AddTarget(target logr.Target) {
a.lgr.AddTarget(target)
}
// Shutdown cleanly stops the audit engine after making best efforts to flush all targets.
func (a *Audit) Shutdown() {
err := a.lgr.Shutdown()
if err != nil {
a.onLoggerError(err)
}
}
func (a *Audit) onQueueFull(rec *logr.LogRec, maxQueueSize int) bool {
if a.OnQueueFull != nil {
return a.OnQueueFull("main", maxQueueSize)
}
mlog.Error("Audit logging queue full, dropping record.", mlog.Int("queueSize", maxQueueSize))
return true
}
func (a *Audit) onTargetQueueFull(target logr.Target, rec *logr.LogRec, maxQueueSize int) bool {
if a.OnQueueFull != nil {
return a.OnQueueFull(fmt.Sprintf("%v", target), maxQueueSize)
}
mlog.Error("Audit logging queue full for target, dropping record.", mlog.Any("target", target), mlog.Int("queueSize", maxQueueSize))
return true
}
func (a *Audit) onLoggerError(err error) {
if a.OnError != nil {
a.OnError(err)
}
}
// sortAuditFields sorts the context fields of an audit record such that some fields
// are prepended in order, some are appended in order, and the rest are sorted alphabetically.
// This is done to make reading the records easier since common fields will appear in the same order.
func sortAuditFields(fields logr.Fields) []format.ContextField {
prependKeys := []string{KeyEvent, KeyStatus, KeyUserID, KeySessionID, KeyIPAddress}
appendKeys := []string{KeyClusterID, KeyClient}
// sort alphabetically any fields not in the prepend/append lists.
keys := make([]string, 0, len(fields))
for k := range fields {
if !findIn(k, prependKeys, appendKeys) {
keys = append(keys, k)
}
}
sort.Strings(keys)
allKeys := make([]string, 0, len(fields))
// add any prepends that exist in fields
for _, k := range prependKeys {
if _, ok := fields[k]; ok {
allKeys = append(allKeys, k)
}
}
// sorted
allKeys = append(allKeys, keys...)
// add any appends that exist in fields
for _, k := range appendKeys {
if _, ok := fields[k]; ok {
allKeys = append(allKeys, k)
}
}
cfs := make([]format.ContextField, 0, len(allKeys))
for _, k := range allKeys {
cfs = append(cfs, format.ContextField{Key: k, Val: fields[k]})
}
return cfs
}
func findIn(s string, arrs ...[]string) bool {
for _, list := range arrs {
for _, key := range list {
if s == key {
return true
}
}
}
return false
}