github.com/TeaOSLab/EdgeNode@v1.3.8/internal/waf/waf.go (about) 1 package waf 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 7 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 8 "github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints" 9 "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 10 "github.com/iwind/TeaGo/Tea" 11 "github.com/iwind/TeaGo/files" 12 "gopkg.in/yaml.v3" 13 "net/http" 14 "os" 15 "reflect" 16 ) 17 18 type WAF struct { 19 Id int64 `yaml:"id" json:"id"` 20 IsOn bool `yaml:"isOn" json:"isOn"` 21 Name string `yaml:"name" json:"name"` 22 Inbound []*RuleGroup `yaml:"inbound" json:"inbound"` 23 Outbound []*RuleGroup `yaml:"outbound" json:"outbound"` 24 CreatedVersion string `yaml:"createdVersion" json:"createdVersion"` 25 Mode firewallconfigs.FirewallMode `yaml:"mode" json:"mode"` 26 UseLocalFirewall bool `yaml:"useLocalFirewall" json:"useLocalFirewall"` 27 SYNFlood *firewallconfigs.SYNFloodConfig `yaml:"synFlood" json:"synFlood"` 28 29 DefaultBlockAction *BlockAction 30 DefaultPageAction *PageAction 31 DefaultCaptchaAction *CaptchaAction 32 DefaultJSCookieAction *JSCookieAction 33 DefaultPost307Action *Post307Action 34 DefaultGet302Action *Get302Action 35 36 hasInboundRules bool 37 hasOutboundRules bool 38 39 checkpointsMap map[string]checkpoints.CheckpointInterface // prefix => checkpoint 40 actionMap map[int64]ActionInterface // actionId => ActionInterface 41 } 42 43 func NewWAF() *WAF { 44 return &WAF{ 45 IsOn: true, 46 actionMap: map[int64]ActionInterface{}, 47 } 48 } 49 50 func NewWAFFromFile(path string) (waf *WAF, err error) { 51 if len(path) == 0 { 52 return nil, errors.New("'path' should not be empty") 53 } 54 file := files.NewFile(path) 55 if !file.Exists() { 56 return nil, errors.New("'" + path + "' not exist") 57 } 58 59 reader, err := file.Reader() 60 if err != nil { 61 return nil, err 62 } 63 defer func() { 64 _ = reader.Close() 65 }() 66 67 waf = &WAF{} 68 err = reader.ReadYAML(waf) 69 if err != nil { 70 return nil, err 71 } 72 return waf, nil 73 } 74 75 func (this *WAF) Init() (resultErrors []error) { 76 // checkpoint 77 this.checkpointsMap = map[string]checkpoints.CheckpointInterface{} 78 for _, def := range checkpoints.AllCheckpoints { 79 instance := reflect.New(reflect.Indirect(reflect.ValueOf(def.Instance)).Type()).Interface().(checkpoints.CheckpointInterface) 80 instance.Init() 81 instance.SetPriority(def.Priority) 82 this.checkpointsMap[def.Prefix] = instance 83 } 84 85 // action map 86 this.actionMap = map[int64]ActionInterface{} 87 88 // rules 89 this.hasInboundRules = len(this.Inbound) > 0 90 this.hasOutboundRules = len(this.Outbound) > 0 91 92 if this.hasInboundRules { 93 for _, group := range this.Inbound { 94 // finder 95 for _, set := range group.RuleSets { 96 for _, rule := range set.Rules { 97 rule.SetCheckpointFinder(this.FindCheckpointInstance) 98 } 99 } 100 101 err := group.Init(this) 102 if err != nil { 103 // 这里我们不阻止其他规则正常加入 104 resultErrors = append(resultErrors, fmt.Errorf("init group '%d' failed: %w", group.Id, err)) 105 } 106 } 107 } 108 109 if this.hasOutboundRules { 110 for _, group := range this.Outbound { 111 // finder 112 for _, set := range group.RuleSets { 113 for _, rule := range set.Rules { 114 rule.SetCheckpointFinder(this.FindCheckpointInstance) 115 } 116 } 117 118 err := group.Init(this) 119 if err != nil { 120 // 这里我们不阻止其他规则正常加入 121 resultErrors = append(resultErrors, err) 122 } 123 } 124 } 125 126 return nil 127 } 128 129 func (this *WAF) AddRuleGroup(ruleGroup *RuleGroup) { 130 if ruleGroup.IsInbound { 131 this.Inbound = append(this.Inbound, ruleGroup) 132 } else { 133 this.Outbound = append(this.Outbound, ruleGroup) 134 } 135 } 136 137 func (this *WAF) RemoveRuleGroup(ruleGroupId int64) { 138 { 139 result := []*RuleGroup{} 140 for _, group := range this.Inbound { 141 if group.Id == ruleGroupId { 142 continue 143 } 144 result = append(result, group) 145 } 146 this.Inbound = result 147 } 148 149 { 150 result := []*RuleGroup{} 151 for _, group := range this.Outbound { 152 if group.Id == ruleGroupId { 153 continue 154 } 155 result = append(result, group) 156 } 157 this.Outbound = result 158 } 159 } 160 161 func (this *WAF) FindRuleGroup(ruleGroupId int64) *RuleGroup { 162 for _, group := range this.Inbound { 163 if group.Id == ruleGroupId { 164 return group 165 } 166 } 167 for _, group := range this.Outbound { 168 if group.Id == ruleGroupId { 169 return group 170 } 171 } 172 return nil 173 } 174 175 func (this *WAF) FindRuleGroupWithCode(ruleGroupCode string) *RuleGroup { 176 if len(ruleGroupCode) == 0 { 177 return nil 178 } 179 for _, group := range this.Inbound { 180 if group.Code == ruleGroupCode { 181 return group 182 } 183 } 184 for _, group := range this.Outbound { 185 if group.Code == ruleGroupCode { 186 return group 187 } 188 } 189 return nil 190 } 191 192 func (this *WAF) MoveInboundRuleGroup(fromIndex int, toIndex int) { 193 if fromIndex < 0 || fromIndex >= len(this.Inbound) { 194 return 195 } 196 if toIndex < 0 || toIndex >= len(this.Inbound) { 197 return 198 } 199 if fromIndex == toIndex { 200 return 201 } 202 203 group := this.Inbound[fromIndex] 204 result := []*RuleGroup{} 205 for i := 0; i < len(this.Inbound); i++ { 206 if i == fromIndex { 207 continue 208 } 209 if fromIndex > toIndex && i == toIndex { 210 result = append(result, group) 211 } 212 result = append(result, this.Inbound[i]) 213 if fromIndex < toIndex && i == toIndex { 214 result = append(result, group) 215 } 216 } 217 218 this.Inbound = result 219 } 220 221 func (this *WAF) MoveOutboundRuleGroup(fromIndex int, toIndex int) { 222 if fromIndex < 0 || fromIndex >= len(this.Outbound) { 223 return 224 } 225 if toIndex < 0 || toIndex >= len(this.Outbound) { 226 return 227 } 228 if fromIndex == toIndex { 229 return 230 } 231 232 group := this.Outbound[fromIndex] 233 result := []*RuleGroup{} 234 for i := 0; i < len(this.Outbound); i++ { 235 if i == fromIndex { 236 continue 237 } 238 if fromIndex > toIndex && i == toIndex { 239 result = append(result, group) 240 } 241 result = append(result, this.Outbound[i]) 242 if fromIndex < toIndex && i == toIndex { 243 result = append(result, group) 244 } 245 } 246 247 this.Outbound = result 248 } 249 250 func (this *WAF) MatchRequest(req requests.Request, writer http.ResponseWriter, defaultCaptchaType firewallconfigs.ServerCaptchaType) (result MatchResult, err error) { 251 if !this.hasInboundRules { 252 return MatchResult{ 253 GoNext: true, 254 }, nil 255 } 256 257 // validate captcha 258 var rawPath = req.WAFRaw().URL.Path 259 if rawPath == CaptchaPath { 260 req.DisableAccessLog() 261 req.DisableStat() 262 captchaValidator.Run(req, writer, defaultCaptchaType) 263 return 264 } 265 266 // Get 302验证 267 if rawPath == Get302Path { 268 req.DisableAccessLog() 269 req.DisableStat() 270 get302Validator.Run(req, writer) 271 return 272 } 273 274 // match rules 275 var hasRequestBody bool 276 for _, group := range this.Inbound { 277 if !group.IsOn { 278 continue 279 } 280 b, hasCheckedRequestBody, set, matchErr := group.MatchRequest(req) 281 if hasCheckedRequestBody { 282 hasRequestBody = true 283 } 284 if matchErr != nil { 285 return MatchResult{ 286 GoNext: true, 287 HasRequestBody: hasRequestBody, 288 }, matchErr 289 } 290 if b { 291 var performResult = set.PerformActions(this, group, req, writer) 292 if !performResult.GoNextSet { 293 if performResult.GoNextGroup { 294 continue 295 } 296 return MatchResult{ 297 GoNext: performResult.ContinueRequest, 298 HasRequestBody: hasRequestBody, 299 Group: group, 300 Set: set, 301 IsAllowed: performResult.IsAllowed, 302 AllowScope: performResult.AllowScope, 303 }, nil 304 } 305 } 306 } 307 return MatchResult{ 308 GoNext: true, 309 HasRequestBody: hasRequestBody, 310 }, nil 311 } 312 313 func (this *WAF) MatchResponse(req requests.Request, rawResp *http.Response, writer http.ResponseWriter) (result MatchResult, err error) { 314 if !this.hasOutboundRules { 315 return MatchResult{ 316 GoNext: true, 317 }, nil 318 } 319 var hasRequestBody bool 320 var resp = requests.NewResponse(rawResp) 321 for _, group := range this.Outbound { 322 if !group.IsOn { 323 continue 324 } 325 b, hasCheckedRequestBody, set, matchErr := group.MatchResponse(req, resp) 326 if hasCheckedRequestBody { 327 hasRequestBody = true 328 } 329 if matchErr != nil { 330 return MatchResult{ 331 GoNext: true, 332 HasRequestBody: hasRequestBody, 333 }, matchErr 334 } 335 if b { 336 var performResult = set.PerformActions(this, group, req, writer) 337 if !performResult.GoNextSet { 338 if performResult.GoNextGroup { 339 continue 340 } 341 return MatchResult{ 342 GoNext: performResult.ContinueRequest, 343 HasRequestBody: hasRequestBody, 344 Group: group, 345 Set: set, 346 IsAllowed: performResult.IsAllowed, 347 AllowScope: performResult.AllowScope, 348 }, nil 349 } 350 } 351 } 352 return MatchResult{ 353 GoNext: true, 354 HasRequestBody: hasRequestBody, 355 }, nil 356 } 357 358 // Save to file path 359 func (this *WAF) Save(path string) error { 360 if len(path) == 0 { 361 return errors.New("path should not be empty") 362 } 363 if len(this.CreatedVersion) == 0 { 364 this.CreatedVersion = teaconst.Version 365 } 366 data, err := yaml.Marshal(this) 367 if err != nil { 368 return err 369 } 370 return os.WriteFile(path, data, 0644) 371 } 372 373 func (this *WAF) ContainsGroupCode(code string) bool { 374 if len(code) == 0 { 375 return false 376 } 377 for _, group := range this.Inbound { 378 if group.Code == code { 379 return true 380 } 381 } 382 for _, group := range this.Outbound { 383 if group.Code == code { 384 return true 385 } 386 } 387 return false 388 } 389 390 func (this *WAF) AddAction(action ActionInterface) { 391 this.actionMap[action.ActionId()] = action 392 } 393 394 func (this *WAF) FindAction(actionId int64) ActionInterface { 395 return this.actionMap[actionId] 396 } 397 398 func (this *WAF) Copy() *WAF { 399 var waf = &WAF{ 400 Id: this.Id, 401 IsOn: this.IsOn, 402 Name: this.Name, 403 Inbound: this.Inbound, 404 Outbound: this.Outbound, 405 } 406 return waf 407 } 408 409 func (this *WAF) CountInboundRuleSets() int { 410 count := 0 411 for _, group := range this.Inbound { 412 count += len(group.RuleSets) 413 } 414 return count 415 } 416 417 func (this *WAF) CountOutboundRuleSets() int { 418 count := 0 419 for _, group := range this.Outbound { 420 count += len(group.RuleSets) 421 } 422 return count 423 } 424 425 func (this *WAF) FindCheckpointInstance(prefix string) checkpoints.CheckpointInterface { 426 instance, ok := this.checkpointsMap[prefix] 427 if ok { 428 return instance 429 } 430 return nil 431 } 432 433 // Start 434 func (this *WAF) Start() { 435 for _, checkpoint := range this.checkpointsMap { 436 checkpoint.Start() 437 } 438 } 439 440 // Stop call stop() when the waf was deleted 441 func (this *WAF) Stop() { 442 for _, checkpoint := range this.checkpointsMap { 443 checkpoint.Stop() 444 } 445 } 446 447 // MergeTemplate merge with template 448 func (this *WAF) MergeTemplate() (changedItems []string, err error) { 449 changedItems = []string{} 450 451 // compare versions 452 if !Tea.IsTesting() && this.CreatedVersion == teaconst.Version { 453 return 454 } 455 this.CreatedVersion = teaconst.Version 456 457 template, err := Template() 458 if err != nil { 459 return nil, err 460 } 461 groups := []*RuleGroup{} 462 groups = append(groups, template.Inbound...) 463 groups = append(groups, template.Outbound...) 464 465 var newGroupId int64 = 1_000_000_000 466 467 for _, group := range groups { 468 oldGroup := this.FindRuleGroupWithCode(group.Code) 469 if oldGroup == nil { 470 newGroupId++ 471 group.Id = newGroupId 472 this.AddRuleGroup(group) 473 changedItems = append(changedItems, "+group "+group.Name) 474 continue 475 } 476 477 // check rule sets 478 for _, set := range group.RuleSets { 479 oldSet := oldGroup.FindRuleSetWithCode(set.Code) 480 if oldSet == nil { 481 oldGroup.AddRuleSet(set) 482 changedItems = append(changedItems, "+group "+group.Name+" rule set:"+set.Name) 483 } else if len(oldSet.Rules) < len(set.Rules) { 484 oldSet.Rules = set.Rules 485 changedItems = append(changedItems, "*group "+group.Name+" rule set:"+set.Name) 486 } 487 } 488 } 489 return 490 }