File: alertifier.go

package info (click to toggle)
fever 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 920 kB
  • sloc: sh: 41; makefile: 18
file content (199 lines) | stat: -rw-r--r-- 6,376 bytes parent folder | download | duplicates (4)
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package util

// DCSO FEVER
// Copyright (c) 2020, DCSO GmbH

import (
	"fmt"
	"time"

	"github.com/DCSO/fever/types"

	"github.com/buger/jsonparser"
	log "github.com/sirupsen/logrus"
)

// ExtraModifier is a function type that describes a function that adds the
// appropriate `_extra` sub-object entries to a EVE-JSON event.
type ExtraModifier func(inputAlert *types.Entry, ioc string) error

// AlertJSONProvider is an interface describing a component that returns an
// `alert` JSON sub-object to use in an EVE-JSON event.
type AlertJSONProvider interface {
	// GetAlertJSON is a function that returns a byte slice containing the
	// JSON data for an `alert` EVE-JSON sub-object.
	GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error)
}

// Alertifier is a component that creates EVE-JSON alerts from arbitrary
// EVE-JSON events. It does this by cloning the original event and adding
// alert-specific fields, depending on the given ExtraModifier and a set of
// AlertJSONProviders, selectable using a string tag.
type Alertifier struct {
	alertPrefix   string
	extraModifier ExtraModifier
	addedFields   string
	matchTypes    map[string]AlertJSONProvider
}

// MakeAlertifier returns a new Alertifier, with no AlertJSONProviders set for
// any match types, but with the given alert prefix preconfigured.
// The alert prefix is a string that is prepended to all alert.signature values,
// as in "DCSO TIE-BLF" or "ETPRO CURRENT_EVENTS", etc.
func MakeAlertifier(prefix string) *Alertifier {
	a := &Alertifier{
		alertPrefix: prefix,
		matchTypes:  make(map[string]AlertJSONProvider),
	}
	return a
}

// RegisterMatchType associates a given AlertJSONProvider with a match type tag.
// It makes it callable in the MakeAlert() function in this Alertifier.
func (a *Alertifier) RegisterMatchType(matchTypeName string, mt AlertJSONProvider) {
	a.matchTypes[matchTypeName] = mt
}

// SetPrefix sets the signature prefix of the current Alertifier to the given
// string value.
func (a *Alertifier) SetPrefix(prefix string) {
	a.alertPrefix = prefix
}

// SetExtraModifier sets the _extra modifier of the current Alertifier to the
// passed function. Set it to nil to disable modification of the _extra
// sub-object.
func (a *Alertifier) SetExtraModifier(em ExtraModifier) {
	a.extraModifier = em
}

// SetAddedFields adds string key-value pairs to be added as extra JSON
// values.
func (a *Alertifier) SetAddedFields(fields map[string]string) error {
	af, err := PreprocessAddedFields(fields)
	if err != nil {
		return err
	}
	a.addedFields = af
	return nil
}

// MakeAlert generates a new Entry representing an `alert` event based on the
// given input metadata event. It uses the information from the Alertifier as
// well as the given IoC to craft an `alert` sub-object in the resulting
// alert, which is built by the AlertJSONProvider registered under the specified
// matchType.
func (a *Alertifier) MakeAlert(inputEvent types.Entry, ioc string,
	matchType string) (*types.Entry, error) {
	v, ok := a.matchTypes[matchType]
	if !ok {
		return nil, fmt.Errorf("cannot create alert for metadata, unknown "+
			"matchtype '%s'", matchType)
	}
	// clone the original event
	newEntry := inputEvent

	// set a new event type in Entry
	newEntry.EventType = "alert"
	// update JSON text
	l, err := jsonparser.Set([]byte(newEntry.JSONLine),
		[]byte(`"alert"`), "event_type")
	if err != nil {
		return nil, err
	}
	newEntry.JSONLine = string(l)

	// generate alert sub-object JSON
	val, err := v.GetAlertJSON(inputEvent, a.alertPrefix, ioc)
	if err != nil {
		return nil, err
	}
	// update JSON text
	l, err = jsonparser.Set([]byte(newEntry.JSONLine), val, "alert")
	if err != nil {
		return nil, err
	}
	newEntry.JSONLine = string(l)

	// add custom extra modifier
	if a.extraModifier != nil {
		err = a.extraModifier(&newEntry, ioc)
		if err != nil {
			return nil, err
		}
	}

	// ensure consistent timestamp formatting: try to parse as Suricata timestamp
	eventTimestampFormatted := newEntry.Timestamp
	inTimestampParsed, err := time.Parse(types.SuricataTimestampFormat, newEntry.Timestamp)
	if err != nil {
		// otherwise try to parse without zone information
		inTimestampParsed, err = time.Parse("2006-01-02T15:04:05.999999", newEntry.Timestamp)
		if err == nil {
			eventTimestampFormatted = inTimestampParsed.Format(types.SuricataTimestampFormat)
		} else {
			log.Warningf("keeping non-offset timestamp '%s', could not be transformed: %s", newEntry.Timestamp, err.Error())
		}
	}
	// Set received original timestamp as "timestamp_event" field
	escapedTimestamp, err := EscapeJSON(eventTimestampFormatted)
	if err != nil {
		return nil, err
	}
	l, err = jsonparser.Set([]byte(newEntry.JSONLine), escapedTimestamp, "timestamp_event")
	if err != nil {
		return nil, err
	}
	// Add current (alerting) timestamp as "timestamp" field
	nowTimestampEscaped, err := EscapeJSON(time.Now().UTC().Format(types.SuricataTimestampFormat))
	if err != nil {
		return nil, err
	}
	l, err = jsonparser.Set(l, []byte(nowTimestampEscaped), "timestamp")
	if err != nil {
		return nil, err
	}
	// Append added fields string, if present
	if len(a.addedFields) > 1 {
		j := l
		jlen := len(j)
		j = j[:jlen-1]
		j = append(j, a.addedFields...)
		l = j
	}
	// update returned entry
	newEntry.Timestamp = eventTimestampFormatted
	newEntry.JSONLine = string(l)
	return &newEntry, nil
}

// GenericGetAlertObjForIoc is a simple helper function that takes a format
// string with string ('%s') placeholders for the prefix and the IoC. It also
// sets basic other alert fields such as `category` and `action`.
func GenericGetAlertObjForIoc(inputEvent types.Entry,
	prefix string, ioc string, msg string) ([]byte, error) {
	sig := fmt.Sprintf(msg, prefix, ioc)
	val, err := EscapeJSON(sig)
	if err != nil {
		return nil, err
	}
	newAlertSubObj := "{}"
	if l, err := jsonparser.Set([]byte(newAlertSubObj), val, "signature"); err != nil {
		log.Warning(err)
	} else {
		newAlertSubObj = string(l)
	}
	if l, err := jsonparser.Set([]byte(newAlertSubObj),
		[]byte(`"Potentially Bad Traffic"`), "category"); err != nil {
		log.Warning(err)
	} else {
		newAlertSubObj = string(l)
	}
	if l, err := jsonparser.Set([]byte(newAlertSubObj),
		[]byte(`"allowed"`), "action"); err != nil {
		log.Warning(err)
	} else {
		newAlertSubObj = string(l)
	}
	return []byte(newAlertSubObj), err
}