forked from elastic/beats
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcef.go
183 lines (155 loc) · 5.54 KB
/
cef.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
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package cef
import (
"strings"
"github.com/pkg/errors"
"go.uber.org/multierr"
)
// Parser is generated from a ragel state machine using the following command:
//go:generate ragel -Z -G1 cef.rl -o parser.go
//go:generate goimports -l -w parser.go
//
// Run go vet and remove any unreachable code in the generated parser.go.
// The go generator outputs duplicated goto statements sometimes.
//
// An SVG rendering of the state machine can be viewed by opening cef.svg in
// Chrome / Firefox.
//go:generate ragel -V -p cef.rl -o cef.dot
//go:generate dot -T svg cef.dot -o cef.svg
// Field is CEF extension field value.
type Field struct {
String string // Raw value.
Type DataType // Data type from CEF guide.
Interface interface{} // Converted value.
}
// Event is a single CEF message.
type Event struct {
// CEF version.
Version int `json:"version"`
// Vendor of the sending device.
DeviceVendor string `json:"device_vendor"`
// Product of the sending device.
DeviceProduct string `json:"device_product"`
// Version of the sending device.
DeviceVersion string `json:"device_version"`
// Device Event Class ID identifies the type of event reported
DeviceEventClassID string `json:"device_event_class_id"`
// Human-readable and understandable description of the event.
Name string `json:"name"`
// Importance of the event. The valid string values are Unknown, Low,
// Medium, High, and Very-High. The valid integer values are 0-3=Low,
// 4-6=Medium, 7- 8=High, and 9-10=Very-High.
Severity string `json:"severity"`
// Extensions is a collection of key-value pairs. The keys are part of a
// predefined set. The standard allows for including additional keys as
// outlined in "ArcSight Extension Directory". An event can contain any
// number of key-value pairs in any order.
Extensions map[string]*Field `json:"extensions,omitempty"`
}
func (e *Event) init(data string) {
e.Version = -1
e.DeviceVendor = ""
e.DeviceProduct = ""
e.DeviceVersion = ""
e.DeviceEventClassID = ""
e.Name = ""
e.Severity = ""
e.Extensions = nil
// Estimate length of the extensions. But limit the allocation because
// it's based on user input. This doesn't account for escaped equals.
if n := strings.Count(data, "="); n > 0 {
const maxLen = 50
if n <= maxLen {
e.Extensions = make(map[string]*Field, n)
} else {
e.Extensions = make(map[string]*Field, maxLen)
}
}
}
func (e *Event) pushExtension(key, value string) {
if e.Extensions == nil {
e.Extensions = map[string]*Field{}
}
field := &Field{String: value}
e.Extensions[key] = field
}
// Unpack unpacks a common event format (CEF) message. The data is expected to
// be UTF-8 encoded and must begin with the CEF message header (e.g. starts
// with "CEF:").
//
// The CEF message consists of a header followed by a series of key-value pairs.
//
// CEF:Version|Device Vendor|Device Product|Device Version|Device Event Class ID|Name|Severity|[Extension]
//
// The header is a series of pipe delimited values. If a pipe (|) is used in a
// header value, it has to be escaped with a backslash (\). If a backslash is
// used is must be escaped with another backslash.
//
// The extension contains key-value pairs. The equals sign (=) separates each
// key from value. And key-value pairs are separated by a single space
// (e.g. "src=1.2.3.4 dst=8.8.8.8"). If an equals sign is used as part of the
// value then it must be escaped with a backslash (\). If a backslash is used is
// must be escaped with another backslash.
//
// Extension keys must begin with an alphanumeric or underscore (_) character
// and may contain alphanumeric, underscore (_), period (.), comma (,), and
// brackets ([) (]). This is less strict than the CEF specification, but aligns
// the key names used in practice.
func (e *Event) Unpack(data string, opts ...Option) error {
var settings Settings
for _, opt := range opts {
opt.Apply(&settings)
}
var errs []error
var err error
if err = e.unpack(data); err != nil {
errs = append(errs, err)
}
for key, field := range e.Extensions {
mapping, found := extensionMappingLowerCase[strings.ToLower(key)]
if !found {
continue
}
// Mark the data type and do the actual conversion.
field.Type = mapping.Type
field.Interface, err = ToType(field.String, mapping.Type)
if err != nil {
// Drop the key because the field value is invalid.
delete(e.Extensions, key)
errs = append(errs, errors.Wrapf(err, "error in field '%v'", key))
continue
}
// Rename extension.
if settings.fullExtensionNames && key != mapping.Target {
e.Extensions[mapping.Target] = field
delete(e.Extensions, key)
}
}
return multierr.Combine(errs...)
}
const (
backslash = `\`
escapedBackslash = `\\`
pipe = `|`
escapedPipe = `\|`
equalsSign = `=`
escapedEqualsSign = `\=`
)
var (
headerEscapes = strings.NewReplacer(escapedBackslash, backslash, escapedPipe, pipe)
extensionEscapes = strings.NewReplacer(escapedBackslash, backslash, escapedEqualsSign, equalsSign)
)
func replaceHeaderEscapes(b string) string {
if strings.Index(b, escapedBackslash) != -1 || strings.Index(b, escapedPipe) != -1 {
return headerEscapes.Replace(b)
}
return b
}
func replaceExtensionEscapes(b string) string {
if strings.Index(b, escapedBackslash) != -1 || strings.Index(b, escapedEqualsSign) != -1 {
return extensionEscapes.Replace(b)
}
return b
}