forked from slackhq/go-audit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
184 lines (154 loc) · 4.73 KB
/
parser.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
176
177
178
179
180
181
182
183
184
package main
import (
"bytes"
"os/user"
"strconv"
"strings"
"syscall"
"time"
)
var uidMap = map[string]string{}
var headerEndChar = []byte{")"[0]}
var headerSepChar = byte(':')
var spaceChar = byte(' ')
const (
HEADER_MIN_LENGTH = 7 // Minimum length of an audit header
HEADER_START_POS = 6 // Position in the audit header that the data starts
COMPLETE_AFTER = time.Second * 2 // Log a message after this time or EOE
)
type AuditMessage struct {
Type uint16 `json:"type"`
Data string `json:"data"`
Seq int `json:"-"`
AuditTime string `json:"-"`
}
type AuditMessageGroup struct {
Seq int `json:"sequence"`
AuditTime string `json:"timestamp"`
CompleteAfter time.Time `json:"-"`
Msgs []*AuditMessage `json:"messages"`
UidMap map[string]string `json:"uid_map"`
Syscall string `json:"-"`
}
// Creates a new message group from the details parsed from the message
func NewAuditMessageGroup(am *AuditMessage) *AuditMessageGroup {
//TODO: allocating 6 msgs per group is lame and we _should_ know ahead of time roughly how many we need
amg := &AuditMessageGroup{
Seq: am.Seq,
AuditTime: am.AuditTime,
CompleteAfter: time.Now().Add(COMPLETE_AFTER),
UidMap: make(map[string]string, 2), // Usually only 2 individual uids per execve
Msgs: make([]*AuditMessage, 0, 6),
}
amg.AddMessage(am)
return amg
}
// Creates a new go-audit message from a netlink message
func NewAuditMessage(nlm *syscall.NetlinkMessage) *AuditMessage {
aTime, seq := parseAuditHeader(nlm)
return &AuditMessage{
Type: nlm.Header.Type,
Data: string(nlm.Data),
Seq: seq,
AuditTime: aTime,
}
}
// Gets the timestamp and audit sequence id from a netlink message
func parseAuditHeader(msg *syscall.NetlinkMessage) (time string, seq int) {
headerStop := bytes.Index(msg.Data, headerEndChar)
// If the position the header appears to stop is less than the minimum length of a header, bail out
if headerStop < HEADER_MIN_LENGTH {
return
}
header := string(msg.Data[:headerStop])
if header[:HEADER_START_POS] == "audit(" {
//TODO: out of range check, possibly fully binary?
sep := strings.IndexByte(header, headerSepChar)
time = header[HEADER_START_POS:sep]
seq, _ = strconv.Atoi(header[sep+1:])
// Remove the header from data
msg.Data = msg.Data[headerStop+3:]
}
return time, seq
}
// Add a new message to the current message group
func (amg *AuditMessageGroup) AddMessage(am *AuditMessage) {
amg.Msgs = append(amg.Msgs, am)
//TODO: need to find more message types that won't contain uids, also make these constants
switch am.Type {
case 1309, 1307, 1306:
// Don't map uids here
case 1300:
amg.findSyscall(am)
amg.mapUids(am)
default:
amg.mapUids(am)
}
}
// Find all `uid=` occurrences in a message and adds the username to the UidMap object
func (amg *AuditMessageGroup) mapUids(am *AuditMessage) {
data := am.Data
start := 0
end := 0
for {
if start = strings.Index(data, "uid="); start < 0 {
break
}
// Progress the start point beyon the = sign
start += 4
if end = strings.IndexByte(data[start:], spaceChar); end < 0 {
// There was no ending space, maybe the uid is at the end of the line
end = len(data) - start
// If the end of the line is greater than 5 characters away (overflows a 16 bit uint) then it can't be a uid
if end > 5 {
break
}
}
uid := data[start : start+end]
// Don't bother re-adding if the existing group already has the mapping
if _, ok := amg.UidMap[uid]; !ok {
amg.UidMap[uid] = getUsername(data[start : start+end])
}
// Find the next uid= if we have space for one
next := start + end + 1
if next >= len(data) {
break
}
data = data[next:]
}
}
func (amg *AuditMessageGroup) findSyscall(am *AuditMessage) {
data := am.Data
start := 0
end := 0
if start = strings.Index(data, "syscall="); start < 0 {
return
}
// Progress the start point beyond the = sign
start += 8
if end = strings.IndexByte(data[start:], spaceChar); end < 0 {
// There was no ending space, maybe the syscall id is at the end of the line
end = len(data) - start
// If the end of the line is greater than 5 characters away (overflows a 16 bit uint) then it can't be a syscall id
if end > 5 {
return
}
}
amg.Syscall = data[start : start+end]
}
// Gets a username for a user id
func getUsername(uid string) string {
uname := "UNKNOWN_USER"
// Make sure we have a uid element to work with.
// Give a default value in case we don't find something.
if lUser, ok := uidMap[uid]; ok {
uname = lUser
} else {
lUser, err := user.LookupId(uid)
if err == nil {
uname = lUser.Username
}
uidMap[uid] = uname
}
return uname
}