-
Notifications
You must be signed in to change notification settings - Fork 10
/
score.go
244 lines (217 loc) · 6.64 KB
/
score.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
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
package iprepd
import (
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
"time"
)
// Reputation stores information related to the reputation of a given object
type Reputation struct {
// Object is the object associated with the reputation entry. For example
// if the type is "ip", object will be an IP address.
Object string `json:"object"`
// Type describes the type of object the reputation entry is for
Type string `json:"type"`
// Reputation is the reputation score for the object, ranging from 0 to
// 100 where 100 indicates no violations have been applied to it.
Reputation int `json:"reputation"`
// Reviewed is true if the entry has been manually reviewed, this flag indicates
// a firm confidence in the entry.
Reviewed bool `json:"reviewed"`
// LastUpdated indicates when a reputation was last either set manually or via
// a violation on this entry
LastUpdated time.Time `json:"lastupdated"`
// DecayAfter is used to temporarily stop reputation recovery until after the
// current time has passed the time indicated by DecayAfter. This can be used
// to for example enforce a mandatory minimum reputation decrease for an object
// for a set period of time.
DecayAfter time.Time `json:"decayafter,omitempty"`
}
// Validate performs validation of a Reputation type.
func (r *Reputation) Validate() error {
if r.Object == "" {
return fmt.Errorf("reputation entry missing required field object")
}
if r.Type == "" {
return fmt.Errorf("reputation entry missing required field type")
}
if r.Reputation < 0 || r.Reputation > 100 {
return fmt.Errorf("invalid reputation score %v", r.Reputation)
}
return nil
}
func normalizedObjectValue(typestr string, valstr string) (string, error) {
if typestr == TypeIP {
ip := net.ParseIP(valstr)
if ip == nil {
return "", fmt.Errorf("cannot normalize invalid ip address")
}
v4 := ip.To4()
if v4 == nil {
// Mask the address to collapse it to our configured IPv6 address
// width.
return ip.Mask(net.CIDRMask(sruntime.cfg.IP6Prefix, 128)).String(), nil
} else {
// See if the address contains a : character; it's possible this is an
// IPv4 mapped IPv6 address. If so convert it and we will store it as if
// it were submitted as IPv4.
if strings.Contains(valstr, ":") {
return v4.String(), nil
}
}
}
return valstr, nil
}
func keyFromTypeAndValue(typestr string, valstr string) (string, error) {
if typestr == "" || valstr == "" {
return "", fmt.Errorf("type or value was not set")
}
// At this point we assume the validators have run, and we know we have a valid
// type and a value that properly corresponds to that type.
//
// We want to derive the correct key name to use for that type and value. Generally
// this is simply a concatenation of the type and value string, however in certain
// special cases such as with IPv6 addresses, additional information is encoded into
// the key name.
buf, err := normalizedObjectValue(typestr, valstr)
if err != nil {
return "", err
}
if typestr == TypeIP {
if net.ParseIP(valstr).To4() == nil {
// Postfix the address value in the key with a separator and the configured
// IPv6 subnet width; this will invalidate all existing IPv6 reputation
// values if the configuration value is changed.
return typestr + " " + buf +
"#" + strconv.Itoa(sruntime.cfg.IP6Prefix), nil
}
}
return typestr + " " + buf, nil
}
func (r *Reputation) set() error {
err := r.Validate()
if err != nil {
return err
}
r.Object, err = normalizedObjectValue(r.Type, r.Object)
if err != nil {
return err
}
r.LastUpdated = time.Now().UTC()
buf, err := json.Marshal(r)
if err != nil {
return err
}
key, err := keyFromTypeAndValue(r.Type, r.Object)
if err != nil {
return err
}
return sruntime.redis.set(key, buf, time.Hour*336).Err()
}
func (r *Reputation) applyViolation(v string) (found bool, err error) {
viol := sruntime.cfg.getViolation(v)
if viol == nil {
return false, fmt.Errorf("invalid violation: %v", v)
}
found = true
if r.Reputation <= viol.DecreaseLimit {
return
}
if (r.Reputation - viol.Penalty) < viol.DecreaseLimit {
r.Reputation = viol.DecreaseLimit
} else {
r.Reputation -= viol.Penalty
}
return
}
func (r *Reputation) applyDecay() error {
// If DecayAfter is set and we haven't past the indicated timestamp yet
// don't do anything with the current reputation value.
//
// If the value is set and we have passed the indicated point in time, replace
// the value with the zero value.
if !r.DecayAfter.IsZero() {
if r.DecayAfter.After(time.Now().UTC()) {
return nil
}
r.DecayAfter = time.Time{}
}
x := sruntime.cfg.Decay.Points *
int(time.Since(r.LastUpdated)/sruntime.cfg.Decay.Interval)
if r.Reputation+x > 100 {
r.Reputation = 100
r.Reviewed = false
} else {
r.Reputation += x
}
return nil
}
// Violation describes a violation penalty that can be applied to an object.
type Violation struct {
// Name of violation as specified in iprepd cfg
Name string `json:"name"`
// Penalty is how many points a reputation will be decreased by if this
// violation is submitted for an object
Penalty int `json:"penalty"`
// DecreaseLimit is the lowest possible value this violation will decrease a
// reputation to. Since the same violation can be applied multiple times to
// the same object, this can be used to place a lower bound on the total decrease.
DecreaseLimit int `json:"decreaselimit"`
}
func repGet(typestr string, valstr string) (ret Reputation, err error) {
var key string
key, err = keyFromTypeAndValue(typestr, valstr)
if err != nil {
return
}
buf, err := sruntime.redis.get(key)
if err != nil {
return
}
err = json.Unmarshal(buf, &ret)
if err != nil {
return
}
// If the type field is unset in the stored entry, set it to the type that was
// used to make the request
if ret.Type == "" {
ret.Type = typestr
}
err = ret.applyDecay()
return
}
func repDelete(typestr string, valstr string) (err error) {
key, err := keyFromTypeAndValue(typestr, valstr)
if err != nil {
return err
}
_, err = sruntime.redis.del(key).Result()
return
}
func RepDump() (ret []Reputation, err error) {
keys, err := sruntime.redis.keys("*").Result()
if err != nil {
return
}
// Collect and return all entries from the database; note that this is a raw dump
// and no compatibility fixups or any validation occurs on the returned entries.
for _, obj := range keys {
buf, err := sruntime.redis.get(obj)
if err != nil {
return ret, err
}
reputation := Reputation{}
err = json.Unmarshal(buf, &reputation)
if err != nil {
return ret, err
}
err = reputation.applyDecay()
if err != nil {
return ret, err
}
ret = append(ret, reputation)
}
return
}