-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathalert.go
More file actions
245 lines (206 loc) · 6.87 KB
/
alert.go
File metadata and controls
245 lines (206 loc) · 6.87 KB
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Package deepalert provides types and utilities for the DeepAlert security alert aggregation pipeline.
package deepalert
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"github.com/m-mizutani/golambda"
)
// AttrType shows type of alert attribute.
type AttrType string
const (
// TypeIPAddr means IP address (v4/v6)
TypeIPAddr AttrType = "ipaddr"
// TypeDomainName means domain name
TypeDomainName AttrType = "domain"
// TypeUserName means user name on the system
TypeUserName AttrType = "username"
// TypeFileHashValue means hash value of a file. Hash algorithm is not specified for now.
TypeFileHashValue AttrType = "filehashvalue"
// TypeJSON means raw JSON data. It should be displayed as preformatted text
TypeJSON AttrType = "json"
// TypeURL means URL of some object
TypeURL AttrType = "url"
)
// AttrContext describes context of the attribute.
type AttrContext string
// AttrContexts is set of AttrContext
type AttrContexts []AttrContext
const (
// CtxRemote means an entity of the attribute is outside of your organization.
// E.g. External Web site, Attacker Host
CtxRemote AttrContext = "remote"
// CtxLocal means an entity of the attribute is inside of your organization.
// E.g. Staff's workstation, Owned cloud instance.
CtxLocal AttrContext = "local"
// CtxSubject means an entity of the attribute is subject of the event.
CtxSubject AttrContext = "subject"
// CtxObject means an entity of the attribute is target of the event.
CtxObject AttrContext = "object"
// CtxClient means a network entity works as client (requester).
CtxClient AttrContext = "client"
// CtxServer means a network entity works as server (responder).
CtxServer AttrContext = "server"
// CtxFile means the attribute comes from file object.
CtxFile AttrContext = "file"
// CtxAdditionalInfo means the attribute is meta contexts
CtxAdditionalInfo AttrContext = "additional"
)
// Attribute is element of alert
type Attribute struct {
Type AttrType `json:"type"`
// Key should be unique in alert.Attributes, but not must.
Key string `json:"key"`
// Value is main value of attribute.
Value string `json:"value"`
// Context explains background of the attribute value.
Context AttrContexts `json:"context"`
// Timestamp indicates observed time of the attribute.
Timestamp *time.Time `json:"timestamp,omitempty"`
}
// Alert is extranted data from KinesisStream
type Alert struct {
Detector string `json:"detector"`
RuleName string `json:"rule_name"`
RuleID string `json:"rule_id"`
AlertKey string `json:"alert_key"`
Description string `json:"description"`
Timestamp time.Time `json:"timestamp"`
Attributes []Attribute `json:"attributes"`
Body interface{} `json:"body,omitempty"`
}
// AddAttribute just appends the attribute to the Alert
func (x *Alert) AddAttribute(attr Attribute) {
x.Attributes = append(x.Attributes, attr)
}
// AddAttributes appends set of attribute to the Alert
func (x *Alert) AddAttributes(attrs []Attribute) {
x.Attributes = append(x.Attributes, attrs...)
}
// FindAttributes searches and returns matched attributes
func (x *Alert) FindAttributes(key string) []Attribute {
var attrs []Attribute
for _, attr := range x.Attributes {
if attr.Key == key {
attrs = append(attrs, attr)
}
}
return attrs
}
// AlertID calculate ID of the alert from Detector, RuleID and AlertKey.
func (x *Alert) AlertID() string {
key := strings.Join([]string{
base64.StdEncoding.EncodeToString([]byte(x.Detector)),
base64.StdEncoding.EncodeToString([]byte(x.RuleID)),
base64.StdEncoding.EncodeToString([]byte(x.AlertKey)),
}, ":")
hasher := sha256.New()
_, err := hasher.Write([]byte(key))
if err != nil {
golambda.Logger.With("err", err).Error("Failed sha256.Write")
panic(fmt.Sprintf("sha256.Write failed: %v", err))
}
return fmt.Sprintf("alert:%x", hasher.Sum(nil))
}
var (
// ErrInvalidAlert represents not enough parameter(s) for Alert
ErrInvalidAlert = golambda.NewError("Invalid Alert parameter")
)
const (
maxFieldLen = 1024
maxDescriptionLen = 4096
maxBodyLen = 65536
maxAttributes = 100
)
// Validate checks own parameters of Alert and returns error if something wrong
func (x *Alert) Validate() error {
if x.Detector == "" {
return golambda.WrapError(ErrInvalidAlert, "Alert.Detector is required")
}
if x.RuleID == "" {
return golambda.WrapError(ErrInvalidAlert, "Alert.RuleID is required")
}
if len(x.Detector) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.Detector exceeds maximum length")
}
if len(x.RuleID) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.RuleID exceeds maximum length")
}
if len(x.RuleName) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.RuleName exceeds maximum length")
}
if len(x.AlertKey) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.AlertKey exceeds maximum length")
}
if len(x.Description) > maxDescriptionLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.Description exceeds maximum length")
}
if len(x.Attributes) > maxAttributes {
return golambda.WrapError(ErrInvalidAlert, "Alert.Attributes exceeds maximum count")
}
for _, attr := range x.Attributes {
if len(attr.Key) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Attribute.Key exceeds maximum length")
}
if len(attr.Value) > maxFieldLen {
return golambda.WrapError(ErrInvalidAlert, "Attribute.Value exceeds maximum length")
}
}
if x.Body != nil {
raw, err := json.Marshal(x.Body)
if err != nil {
return golambda.WrapError(ErrInvalidAlert, "Alert.Body is not serializable")
}
if len(raw) > maxBodyLen {
return golambda.WrapError(ErrInvalidAlert, "Alert.Body exceeds maximum size")
}
}
return nil
}
// Match checks attribute type and context.
func (x *Attribute) Match(context AttrContext, attrType AttrType) bool {
if x.Type != attrType {
return false
}
if x.Context == nil {
return false
}
for _, ctx := range x.Context {
if ctx == context {
return true
}
}
return false
}
// Hash provides an unique value for the Attribute.
// Hash value must be same if it has same Type, Key, Value and Context.
func (x Attribute) Hash() string {
sort.Slice(x.Context, func(i, j int) bool {
return x.Context[i] < x.Context[j]
})
raw, err := json.Marshal(x)
if err != nil {
golambda.Logger.With("err", err).With("attr", x).Error("Failed to marshal attribute")
panic(fmt.Sprintf("failed to marshal attribute: %v", err))
}
hasher := sha256.New()
if _, err := hasher.Write(raw); err != nil {
golambda.Logger.With("err", err).Error("Failed sha256.Write")
panic(fmt.Sprintf("sha256.Write failed: %v", err))
}
sha := fmt.Sprintf("%x", hasher.Sum(nil))
return sha
}
// Have of AttrContexts checks if context is in AttrContexts
func (x AttrContexts) Have(context AttrContext) bool {
for _, ctx := range x {
if ctx == context {
return true
}
}
return false
}