forked from corazawaf/coraza
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrulegroup.go
189 lines (173 loc) · 4.59 KB
/
rulegroup.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
// Copyright 2021 Juan Pablo Tosso
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package coraza
import (
"fmt"
"strconv"
"sync"
"time"
"github.com/jptosso/coraza-waf/utils"
"go.uber.org/zap"
)
type RuleGroup struct {
rules []*Rule
mux *sync.RWMutex
}
// Adds a rule to the collection
// Will return an error if the ID is already used
func (rg *RuleGroup) Add(rule *Rule) error {
if rule == nil {
// this is an ugly solution but chains should not return rules
return nil
}
if rg.FindById(rule.Id) != nil && rule.Id != 0 {
return fmt.Errorf("there is a another rule with id %d", rule.Id)
}
rg.rules = append(rg.rules, rule)
return nil
}
// GetRules returns the slice of rules,
// it's concurrent safe.
func (rg *RuleGroup) GetRules() []*Rule {
rg.mux.RLock()
defer rg.mux.RUnlock()
return rg.rules
}
// FindById return a Rule with the requested Id
func (rg *RuleGroup) FindById(id int) *Rule {
for _, r := range rg.rules {
if r.Id == id {
return r
}
}
return nil
}
// DeleteById removes a rule by it's Id
func (rg *RuleGroup) DeleteById(id int) {
for i, r := range rg.rules {
if r != nil && r.Id == id {
copy(rg.rules[i:], rg.rules[i+1:])
rg.rules[len(rg.rules)-1] = nil
rg.rules = rg.rules[:len(rg.rules)-1]
}
}
}
// FindByMsg returns a slice of rules that matches the msg
func (rg *RuleGroup) FindByMsg(msg string) []*Rule {
rules := []*Rule{}
for _, r := range rg.rules {
if r.Msg == msg {
rules = append(rules, r)
}
}
return rules
}
// FindByTag returns a slice of rules that matches the tag
func (rg *RuleGroup) FindByTag(tag string) []*Rule {
rules := []*Rule{}
for _, r := range rg.rules {
if utils.StringInSlice(tag, r.Tags) {
rules = append(rules, r)
}
}
return rules
}
// Count returns the count of rules
func (rg *RuleGroup) Count() int {
return len(rg.rules)
}
// Clear will remove each and every rule stored
func (rg *RuleGroup) Clear() {
rg.rules = []*Rule{}
}
// Eval rules for the specified phase, between 1 and 5
// Returns true if transaction is disrupted
func (rg *RuleGroup) Eval(phase Phase, tx *Transaction) bool {
tx.Waf.Logger.Debug("Evaluating phase",
zap.String("event", "EVALUATE_PHASE"),
zap.String("txid", tx.Id),
zap.Int("phase", int(phase)),
)
tx.LastPhase = phase
usedRules := 0
ts := time.Now().UnixNano()
for _, r := range tx.Waf.Rules.GetRules() {
if tx.Interruption != nil {
tx.Waf.Logger.Debug("Finished phase",
zap.String("event", "FINISH_PHASE"),
zap.String("txid", tx.Id),
zap.Int("phase", int(phase)),
zap.Int("rules", usedRules),
)
return true
}
// Rules with phase 0 will always run
if r.Phase != phase && r.Phase != 0 {
continue
}
rid := strconv.Itoa(r.Id)
if r.Id == 0 {
rid = strconv.Itoa(r.ParentId)
}
if utils.IntInSlice(r.Id, tx.RuleRemoveById) {
continue
}
//we always evaluate secmarkers
if tx.SkipAfter != "" {
if r.SecMark == tx.SkipAfter {
tx.Waf.Logger.Debug("SkipAfter was finished", zap.String("txid", tx.Id),
zap.String("secmark", r.SecMark),
zap.String("event", "FINISH_SECMARK"),
)
tx.SkipAfter = ""
} else {
tx.Waf.Logger.Debug("Skipping rule because of SkipAfter", zap.String("txid", tx.Id),
zap.Int("rule", r.Id),
zap.String("secmark", tx.SkipAfter),
zap.String("event", "SKIP_RULE_BY_SECMARK"),
)
}
continue
}
if tx.Skip > 0 {
tx.Skip--
//Skipping rule
continue
}
txr := tx.GetCollection(VARIABLE_RULE)
txr.Set("id", []string{rid})
txr.Set("rev", []string{r.Rev})
severity := strconv.Itoa(r.Severity)
txr.Set("severity", []string{severity})
//txr.Set("logdata", []string{r.LogData})
txr.Set("msg", []string{r.Msg})
r.Evaluate(tx)
tx.Capture = false //we reset the capture flag on every run
usedRules++
}
tx.Waf.Logger.Debug("Finished phase",
zap.String("event", "FINISH_PHASE"),
zap.String("txid", tx.Id),
zap.Int("phase", int(phase)),
zap.Int("rules", usedRules),
)
tx.StopWatches[phase] = int(time.Now().UnixNano() - ts)
return tx.Interruption != nil
}
func NewRuleGroup() *RuleGroup {
return &RuleGroup{
rules: []*Rule{},
mux: &sync.RWMutex{},
}
}