yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/waf.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package azure 16 17 import ( 18 "fmt" 19 "net/url" 20 "strings" 21 22 "yunion.io/x/jsonutils" 23 "yunion.io/x/log" 24 "yunion.io/x/pkg/errors" 25 26 api "yunion.io/x/cloudmux/pkg/apis/compute" 27 "yunion.io/x/cloudmux/pkg/cloudprovider" 28 "yunion.io/x/cloudmux/pkg/multicloud" 29 ) 30 31 type SMatchvariable struct { 32 Variablename string `json:"variableName"` 33 Selector string `json:"selector"` 34 } 35 36 type SMatchcondition struct { 37 Matchvariables []SMatchvariable `json:"matchVariables"` 38 Operator string `json:"operator"` 39 Negationconditon bool `json:"negationConditon"` 40 Matchvalues []string `json:"matchValues"` 41 Transforms []string `json:"transforms"` 42 } 43 44 type CustomRule struct { 45 waf *SAppGatewayWaf 46 47 Name string `json:"name"` 48 Priority int `json:"priority"` 49 Ruletype string `json:"ruleType"` 50 //RateLimitThreshold *int `json:"rateLimitThreshold"` 51 Matchconditions []SMatchcondition `json:"matchConditions"` 52 Action string `json:"action"` 53 } 54 55 func (self *CustomRule) GetName() string { 56 return self.Name 57 } 58 59 func (self *CustomRule) GetGlobalId() string { 60 return fmt.Sprintf("%s-%s", self.waf.GetGlobalId(), self.GetName()) 61 } 62 63 func (self *CustomRule) GetDesc() string { 64 return "" 65 } 66 67 func (self *CustomRule) GetPriority() int { 68 return self.Priority 69 } 70 71 func (self *CustomRule) Delete() error { 72 rules := []CustomRule{} 73 for _, rule := range self.waf.Properties.Customrules { 74 if rule.Name != self.Name { 75 rules = append(rules, rule) 76 } 77 } 78 self.waf.Properties.Customrules = rules 79 return self.waf.region.update(jsonutils.Marshal(self.waf), nil) 80 } 81 82 func wafMatchFieldAndKeyLocal2Cloud(opts cloudprovider.SWafStatement) ([]SMatchvariable, error) { 83 ret := []SMatchvariable{} 84 switch opts.MatchField { 85 case cloudprovider.WafMatchFieldQuery: 86 ret = append(ret, SMatchvariable{ 87 Variablename: "QueryString", 88 }) 89 case cloudprovider.WafMatchFieldMethod: 90 ret = append(ret, SMatchvariable{ 91 Variablename: "RequestMethod", 92 }) 93 case cloudprovider.WafMatchFiledUriPath: 94 ret = append(ret, SMatchvariable{ 95 Variablename: "RequestUri", 96 }) 97 case cloudprovider.WafMatchFiledHeader: 98 ret = append(ret, SMatchvariable{ 99 Variablename: "RequestHeaders", 100 Selector: opts.MatchFieldKey, 101 }) 102 case cloudprovider.WafMatchFiledPostArgs: 103 ret = append(ret, SMatchvariable{ 104 Variablename: "PostArgs", 105 Selector: opts.MatchFieldKey, 106 }) 107 case cloudprovider.WafMatchFieldBody: 108 ret = append(ret, SMatchvariable{ 109 Variablename: "RequestBody", 110 }) 111 case cloudprovider.WafMatchFiledCookie: 112 ret = append(ret, SMatchvariable{ 113 Variablename: "RequestCookies", 114 Selector: opts.MatchFieldKey, 115 }) 116 default: 117 return ret, fmt.Errorf("unsupported match filed %s", opts.MatchField) 118 } 119 return ret, nil 120 } 121 122 func wafMatchFieldAndKeyCloud2Local(v SMatchvariable) (cloudprovider.TWafMatchField, string, error) { 123 switch v.Variablename { 124 case "QueryString": 125 return cloudprovider.WafMatchFieldQuery, v.Selector, nil 126 case "RequestMethod": 127 return cloudprovider.WafMatchFieldMethod, "", nil 128 case "RequestUri": 129 return cloudprovider.WafMatchFiledUriPath, "", nil 130 case "RequestHeaders": 131 return cloudprovider.WafMatchFiledHeader, v.Selector, nil 132 case "PostArgs": 133 return cloudprovider.WafMatchFiledPostArgs, v.Selector, nil 134 case "RequestBody": 135 return cloudprovider.WafMatchFieldBody, "", nil 136 case "RequestCookies": 137 return cloudprovider.WafMatchFiledCookie, v.Selector, nil 138 case "RemoteAddr": 139 return cloudprovider.WafMatchFiledHeader, v.Selector, nil 140 default: 141 return "", "", fmt.Errorf("invalid variablename %s", v.Variablename) 142 } 143 } 144 145 func wafStatementLocal2Cloud(opts cloudprovider.SWafStatement) (SMatchcondition, error) { 146 ret := SMatchcondition{} 147 if opts.Transformations != nil { 148 for _, tran := range *opts.Transformations { 149 ret.Transforms = append(ret.Transforms, string(tran)) 150 } 151 } 152 if opts.MatchFieldValues != nil { 153 ret.Matchvalues = *opts.MatchFieldValues 154 } 155 ret.Negationconditon = opts.Negation 156 ret.Operator = string(opts.Operator) 157 var err error 158 switch opts.Type { 159 case cloudprovider.WafStatementTypeIPSet: 160 ret.Operator = "IPMatch" 161 ret.Matchvariables = []SMatchvariable{ 162 SMatchvariable{ 163 Variablename: "RemoteAddr", 164 }, 165 } 166 case cloudprovider.WafStatementTypeGeoMatch: 167 ret.Operator = "GeoMatch" 168 if len(opts.ForwardedIPHeader) == 0 { 169 ret.Matchvariables = []SMatchvariable{ 170 SMatchvariable{ 171 Variablename: "RemoteAddr", 172 }, 173 } 174 } else { 175 ret.Matchvariables = []SMatchvariable{ 176 SMatchvariable{ 177 Variablename: "RequestHeaders", 178 Selector: opts.ForwardedIPHeader, 179 }, 180 } 181 } 182 case cloudprovider.WafStatementTypeSize: 183 switch opts.Operator { 184 case "LT": 185 ret.Operator = "LessThan" 186 case "LE": 187 ret.Operator = "LessThanOrEqual" 188 case "GT": 189 ret.Operator = "GreaterThan" 190 default: 191 return ret, fmt.Errorf("invalid operator %s for %s", opts.Operator, opts.Type) 192 } 193 ret.Matchvariables, err = wafMatchFieldAndKeyLocal2Cloud(opts) 194 if err != nil { 195 return ret, errors.Wrapf(err, "wafMatchFieldAndKeyLocal2Cloud") 196 } 197 case cloudprovider.WafStatementTypeByteMatch: 198 switch opts.Operator { 199 case "Contains", "EndsWith", "Regex": 200 case "StartsWith": 201 ret.Operator = "BeginsWith" 202 case "Exactly": 203 ret.Operator = "Equal" 204 default: 205 return ret, fmt.Errorf("invalid operator %s for %s", opts.Operator, opts.Type) 206 } 207 ret.Matchvariables, err = wafMatchFieldAndKeyLocal2Cloud(opts) 208 if err != nil { 209 return ret, errors.Wrapf(err, "wafMatchFieldAndKeyLocal2Cloud") 210 } 211 } 212 return ret, nil 213 } 214 215 func wafRuleLocal2Cloud(opts *cloudprovider.SWafRule) (*CustomRule, error) { 216 ret := &CustomRule{} 217 ret.Name = opts.Name 218 ret.Priority = opts.Priority 219 ret.Ruletype = "MatchRule" 220 ret.Matchconditions = []SMatchcondition{} 221 for _, s := range opts.Statements { 222 cds, err := wafStatementLocal2Cloud(s) 223 if err != nil { 224 return nil, errors.Wrapf(err, "wafStatementLocal2Cloud") 225 } 226 ret.Matchconditions = append(ret.Matchconditions, cds) 227 } 228 ret.Action = "Block" 229 if opts.Action != nil { 230 ret.Action = string(opts.Action.Action) 231 } 232 return ret, nil 233 } 234 235 func (self *CustomRule) Update(opts *cloudprovider.SWafRule) error { 236 rules := []CustomRule{} 237 for _, rule := range self.waf.Properties.Customrules { 238 if rule.Name != self.Name { 239 rules = append(rules, rule) 240 } else { 241 rule, err := wafRuleLocal2Cloud(opts) 242 if err != nil { 243 return errors.Wrapf(err, "wafRuleLocal2Cloud") 244 } 245 rules = append(rules, *rule) 246 } 247 } 248 self.waf.Properties.Customrules = rules 249 return self.waf.region.update(jsonutils.Marshal(self.waf), nil) 250 } 251 252 func (self *CustomRule) GetAction() *cloudprovider.DefaultAction { 253 return &cloudprovider.DefaultAction{ 254 Action: cloudprovider.TWafAction(self.Action), 255 } 256 } 257 258 func (self *CustomRule) GetStatementCondition() cloudprovider.TWafStatementCondition { 259 return cloudprovider.WafStatementConditionAnd 260 } 261 262 func (self *CustomRule) GetStatements() ([]cloudprovider.SWafStatement, error) { 263 ret := []cloudprovider.SWafStatement{} 264 for _, condition := range self.Matchconditions { 265 trans := cloudprovider.TextTransformations{} 266 for _, tran := range condition.Transforms { 267 trans = append(trans, cloudprovider.TWafTextTransformation(tran)) 268 } 269 values := cloudprovider.TWafMatchFieldValues(condition.Matchvalues) 270 statement := cloudprovider.SWafStatement{ 271 Negation: condition.Negationconditon, 272 Transformations: &trans, 273 MatchFieldValues: &values, 274 } 275 switch condition.Operator { 276 case "IPMatch": 277 statement.Type = cloudprovider.WafStatementTypeIPSet 278 case "GeoMatch": 279 statement.Type = cloudprovider.WafStatementTypeGeoMatch 280 case "LessThan": 281 statement.Type = cloudprovider.WafStatementTypeSize 282 statement.Operator = cloudprovider.WafOperatorLT 283 case "LessThanOrEqual": 284 statement.Type = cloudprovider.WafStatementTypeSize 285 statement.Operator = cloudprovider.WafOperatorLE 286 case "GreaterThan": 287 statement.Type = cloudprovider.WafStatementTypeSize 288 statement.Operator = cloudprovider.WafOperatorGT 289 case "BeginsWith": 290 statement.Type = cloudprovider.WafStatementTypeByteMatch 291 statement.Operator = cloudprovider.WafOperatorStartsWith 292 case "Contains", "EndsWith", "Regex": 293 statement.Type = cloudprovider.WafStatementTypeByteMatch 294 statement.Operator = cloudprovider.TWafOperator(condition.Operator) 295 case "Equal": 296 statement.Type = cloudprovider.WafStatementTypeByteMatch 297 statement.Operator = cloudprovider.WafOperatorExactly 298 default: 299 statement.Type = cloudprovider.WafStatementTypeByteMatch 300 } 301 302 var err error 303 for _, v := range condition.Matchvariables { 304 statement.MatchField, statement.MatchFieldKey, err = wafMatchFieldAndKeyCloud2Local(v) 305 if err != nil { 306 log.Errorf("wafMatchFieldAndKeyCloud2Local %s error: %v", v, err) 307 continue 308 } 309 ret = append(ret, statement) 310 } 311 } 312 return ret, nil 313 } 314 315 type ManagedRule struct { 316 Rulesettype string `json:"ruleSetType"` 317 Rulesetversion string `json:"ruleSetVersion"` 318 } 319 320 type ManagedRules struct { 321 waf *SAppGatewayWaf 322 Managedrulesets []ManagedRule `json:"managedRuleSets"` 323 } 324 325 func (self *ManagedRules) GetName() string { 326 return fmt.Sprintf("%s Managed rules", self.waf.GetName()) 327 } 328 329 func (self *ManagedRules) GetGlobalId() string { 330 return self.waf.GetGlobalId() 331 } 332 333 func (self *ManagedRules) GetDesc() string { 334 return "" 335 } 336 337 func (self *ManagedRules) GetPriority() int { 338 return 0 339 } 340 341 func (self *ManagedRules) GetAction() *cloudprovider.DefaultAction { 342 return nil 343 } 344 345 func (self *ManagedRules) Delete() error { 346 return cloudprovider.ErrNotSupported 347 } 348 349 func (self *ManagedRules) Update(opts *cloudprovider.SWafRule) error { 350 rules := []ManagedRule{} 351 for _, s := range opts.Statements { 352 if len(s.ManagedRuleGroupName) == 0 { 353 return fmt.Errorf("missing managed rule group name") 354 } 355 names := strings.Split(s.ManagedRuleGroupName, "_") 356 if len(names) != 2 { 357 return fmt.Errorf("invalid managed rule group name %s", s.ManagedRuleGroupName) 358 } 359 rules = append(rules, ManagedRule{ 360 Rulesettype: names[0], 361 Rulesetversion: names[1], 362 }) 363 } 364 if len(rules) == 0 { 365 return fmt.Errorf("missing statements") 366 } 367 self.waf.Properties.Managedrules = ManagedRules{ 368 Managedrulesets: rules, 369 } 370 return self.waf.region.update(jsonutils.Marshal(self.waf), nil) 371 } 372 373 func (self *ManagedRules) GetStatementCondition() cloudprovider.TWafStatementCondition { 374 return cloudprovider.WafStatementConditionAnd 375 } 376 377 func (self *ManagedRules) GetStatements() ([]cloudprovider.SWafStatement, error) { 378 ret := []cloudprovider.SWafStatement{} 379 for i := range self.Managedrulesets { 380 ruleGroupName := fmt.Sprintf("%s_%s", self.Managedrulesets[i].Rulesettype, self.Managedrulesets[i].Rulesetversion) 381 ret = append(ret, cloudprovider.SWafStatement{ 382 ManagedRuleGroupName: ruleGroupName, 383 Type: cloudprovider.WafStatementTypeManagedRuleGroup, 384 RuleGroupId: ruleGroupName, 385 }) 386 } 387 return ret, nil 388 } 389 390 type SAppGatewayWaf struct { 391 multicloud.SResourceBase 392 AzureTags 393 region *SRegion 394 395 Name string `json:"name"` 396 ID string `json:"id"` 397 Type string `json:"type"` 398 Location string `json:"location"` 399 Properties struct { 400 ApplicationGateways []SApplicationGateway 401 HttpListeners []struct { 402 Id string 403 } 404 PathBasedRules []struct { 405 Id string 406 } 407 Resourcestate string `json:"resourceState"` 408 Provisioningstate string `json:"provisioningState"` 409 Policysettings struct { 410 State string `json:"state"` 411 Mode string `json:"mode"` 412 Maxrequestbodysizeinkb int `json:"maxRequestBodySizeInKb"` 413 Fileuploadlimitinmb int `json:"fileUploadLimitInMb"` 414 Requestbodycheck bool `json:"requestBodyCheck"` 415 } `json:"policySettings"` 416 Customrules []CustomRule `json:"customRules"` 417 Managedrules ManagedRules `json:"managedRules"` 418 } `json:"properties"` 419 } 420 421 func (self *SAppGatewayWaf) GetEnabled() bool { 422 return self.Properties.Policysettings.State == "Enabled" 423 } 424 425 func (self *SAppGatewayWaf) GetName() string { 426 return self.Name 427 } 428 429 func (self *SAppGatewayWaf) GetId() string { 430 return self.ID 431 } 432 433 func (self *SAppGatewayWaf) GetGlobalId() string { 434 return strings.ToLower(self.ID) 435 } 436 437 func (self *SAppGatewayWaf) Delete() error { 438 return self.region.del(self.ID) 439 } 440 441 func (self *SAppGatewayWaf) GetWafType() cloudprovider.TWafType { 442 return cloudprovider.WafTypeAppGateway 443 } 444 445 func (self *SAppGatewayWaf) AddRule(opts *cloudprovider.SWafRule) (cloudprovider.ICloudWafRule, error) { 446 rule, err := wafRuleLocal2Cloud(opts) 447 if err != nil { 448 return nil, errors.Wrapf(err, "wafRuleLocal2Cloud") 449 } 450 rule.waf = self 451 self.Properties.Customrules = append(self.Properties.Customrules, *rule) 452 err = self.region.update(jsonutils.Marshal(self), nil) 453 if err != nil { 454 return nil, errors.Wrapf(err, "update") 455 } 456 return rule, nil 457 } 458 459 func (self *SAppGatewayWaf) GetStatus() string { 460 switch self.Properties.Provisioningstate { 461 case "Deleting": 462 return api.WAF_STATUS_DELETING 463 case "Failed": 464 return api.WAF_STATUS_CREATE_FAILED 465 case "Succeeded": 466 return api.WAF_STATUS_AVAILABLE 467 case "Updating": 468 return api.WAF_STATUS_UPDATING 469 default: 470 return self.Properties.Provisioningstate 471 } 472 } 473 474 func (self *SAppGatewayWaf) GetRules() ([]cloudprovider.ICloudWafRule, error) { 475 ret := []cloudprovider.ICloudWafRule{} 476 for i := range self.Properties.Customrules { 477 self.Properties.Customrules[i].waf = self 478 ret = append(ret, &self.Properties.Customrules[i]) 479 } 480 self.Properties.Managedrules.waf = self 481 ret = append(ret, &self.Properties.Managedrules) 482 return ret, nil 483 } 484 485 func (self *SAppGatewayWaf) Refresh() error { 486 waf, err := self.region.GetAppGatewayWaf(self.ID) 487 if err != nil { 488 return errors.Wrapf(err, "GetAppGatewayWa") 489 } 490 return jsonutils.Update(self, waf) 491 } 492 493 func (self *SAppGatewayWaf) GetDefaultAction() *cloudprovider.DefaultAction { 494 return &cloudprovider.DefaultAction{ 495 Action: cloudprovider.TWafAction(self.Properties.Policysettings.Mode), 496 } 497 } 498 499 func (self *SRegion) ListAppWafs() ([]SAppGatewayWaf, error) { 500 ret := []SAppGatewayWaf{} 501 err := self.list("Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies", url.Values{}, &ret) 502 if err != nil { 503 return nil, errors.Wrapf(err, "list") 504 } 505 return ret, nil 506 } 507 508 type SAppWafRuleGroup struct { 509 Name string `json:"name"` 510 ID string `json:"id"` 511 Type string `json:"type"` 512 Properties struct { 513 Provisioningstate string `json:"provisioningState"` 514 Rulesettype string `json:"ruleSetType"` 515 Rulesetversion string `json:"ruleSetVersion"` 516 Rulegroups []struct { 517 Rulegroupname string `json:"ruleGroupName"` 518 Description string `json:"description"` 519 Rules []struct { 520 Ruleid int `json:"ruleId"` 521 Description string `json:"description"` 522 } `json:"rules"` 523 } `json:"ruleGroups"` 524 } `json:"properties"` 525 } 526 527 func (self *SRegion) CreateICloudWafInstance(opts *cloudprovider.WafCreateOptions) (cloudprovider.ICloudWafInstance, error) { 528 switch opts.Type { 529 case cloudprovider.WafTypeAppGateway: 530 return self.CreateAppWafInstance(opts.Name, opts.DefaultAction) 531 default: 532 return nil, errors.Wrapf(cloudprovider.ErrNoSuchProvder, "invalid waf type %s", opts.Type) 533 } 534 } 535 536 func (self *SRegion) GetICloudWafInstanceById(id string) (cloudprovider.ICloudWafInstance, error) { 537 if strings.Contains(id, "microsoft.network/applicationgatewaywebapplicationfirewallpolicies") { 538 return self.GetAppGatewayWaf(id) 539 } 540 return nil, errors.Wrapf(cloudprovider.ErrNotSupported, id) 541 } 542 543 func (self *SRegion) CreateAppWafInstance(name string, action *cloudprovider.DefaultAction) (*SAppGatewayWaf, error) { 544 mode := cloudprovider.WafActionDetection 545 if action != nil { 546 switch action.Action { 547 case cloudprovider.WafActionDetection, cloudprovider.WafActionPrevention: 548 mode = action.Action 549 default: 550 return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "invalid action %s", action.Action) 551 } 552 } 553 params := map[string]interface{}{ 554 "Type": "Microsoft.Network/applicationGatewayWebApplicationFirewallPolicies", 555 "Name": name, 556 "Location": self.Name, 557 "properties": map[string]interface{}{ 558 "customRules": []string{}, 559 "policySettings": map[string]interface{}{ 560 "fileUploadLimitInMb": 100, 561 "maxRequestBodySizeInKb": 128, 562 "mode": mode, 563 "requestBodyCheck": true, 564 "state": "Enabled", 565 }, 566 "managedRules": map[string]interface{}{ 567 "exclusions": []string{}, 568 "managedRuleSets": []map[string]interface{}{ 569 map[string]interface{}{ 570 "ruleSetType": "OWASP", 571 "ruleSetVersion": "3.1", 572 "ruleGroupOverrides": []string{}, 573 }, 574 }, 575 }, 576 }, 577 } 578 ret := &SAppGatewayWaf{region: self} 579 err := self.create("", jsonutils.Marshal(params), ret) 580 if err != nil { 581 return nil, err 582 } 583 return ret, nil 584 } 585 586 func (self *SRegion) GetAppGatewayWaf(id string) (*SAppGatewayWaf, error) { 587 res := &SAppGatewayWaf{region: self} 588 return res, self.get(id, nil, &res) 589 } 590 591 func (self *SRegion) ListAppWafManagedRuleGroup() ([]SAppWafRuleGroup, error) { 592 ret := []SAppWafRuleGroup{} 593 err := self.list("Microsoft.Network/applicationGatewayAvailableWafRuleSets", url.Values{}, &ret) 594 if err != nil { 595 return nil, errors.Wrapf(err, "list") 596 } 597 return ret, nil 598 } 599 600 func (self *SRegion) GetICloudWafInstances() ([]cloudprovider.ICloudWafInstance, error) { 601 wafs, err := self.ListAppWafs() 602 if err != nil { 603 return nil, errors.Wrapf(err, "ListAppWafs") 604 } 605 ret := []cloudprovider.ICloudWafInstance{} 606 for i := range wafs { 607 wafs[i].region = self 608 ret = append(ret, &wafs[i]) 609 } 610 return ret, nil 611 } 612 613 func (self *SAppGatewayWaf) GetCloudResources() ([]cloudprovider.SCloudResource, error) { 614 ret := []cloudprovider.SCloudResource{} 615 for _, ag := range self.Properties.ApplicationGateways { 616 ret = append(ret, cloudprovider.SCloudResource{ 617 Id: ag.Id, 618 Name: ag.Id[strings.LastIndex(ag.Id, "/")+1:], 619 Type: "Application Gateway", 620 CanDissociate: true, 621 }) 622 } 623 for _, lis := range self.Properties.HttpListeners { 624 ret = append(ret, cloudprovider.SCloudResource{ 625 Id: lis.Id, 626 Name: lis.Id[strings.LastIndex(lis.Id, "/")+1:], 627 Type: "HTTP Listener", 628 CanDissociate: true, 629 }) 630 } 631 for _, route := range self.Properties.PathBasedRules { 632 ret = append(ret, cloudprovider.SCloudResource{ 633 Id: route.Id, 634 Name: route.Id[strings.LastIndex(route.Id, "/")+1:], 635 Type: "Route Path", 636 CanDissociate: true, 637 }) 638 639 } 640 return ret, nil 641 } 642 643 func (self *SAppGatewayWaf) SetTags(tags map[string]string, replace bool) error { 644 if !replace { 645 for k, v := range self.Tags { 646 if _, ok := tags[k]; !ok { 647 tags[k] = v 648 } 649 } 650 } 651 _, err := self.region.client.SetTags(self.ID, tags) 652 if err != nil { 653 return errors.Wrapf(err, "SetTags") 654 } 655 return nil 656 }