Skip to content

Commit

Permalink
Adding WAF Rule Exclusions to WAF Configuration resource (#328)
Browse files Browse the repository at this point in the history
* Adding WAF Exclusions to WAF Configuration resource

During this implementation, I've refactored some of the WAF acceptance
tests to reduce overlapping:
1. TestAccFastlyServiceWAFVersionV1ImportWithRules was merged into
TestAccFastlyServiceWAFVersionV1Import. This tests was also enhanced to
 include WAF exclusions

1. TestAccFastlyServiceWAFVersionV1AddWithRules and TestAccFastlyServiceWAFVersionV1DeleteRules
was merged into TestAccFastlyServiceWAFVersionV1UpdateRules. The update
tests was performing add, update and delete already. TestAccFastlyServiceWAFVersionV1UpdateRules
was renamed to TestAccFastlyServiceWAFVersionV1AddUpdateDeleteRules to
make it clearer

Co-authored-by: Zsolt Balvanyos <[email protected]>

* Changing WAF Exclusion terminology to WAF Rule Exclusion

* Changing go imports to group them by stdlib and external. Pointing go-fastly to v2.0.0-alpha.2. Changing provider to use new struct format (include is now a slice instead of a string). Cleaned up go.sum

Co-authored-by: Zsolt Balvanyos <[email protected]>
  • Loading branch information
matpimenta and ZsoltBalvanyos authored Oct 12, 2020
1 parent 9092a7d commit c336db2
Show file tree
Hide file tree
Showing 13 changed files with 1,158 additions and 149 deletions.
136 changes: 3 additions & 133 deletions fastly/block_fastly_waf_configuration_v1_active_rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,43 +94,7 @@ func TestAccFastlyServiceWAFVersionV1FlattenWAFDeleteByModSecID(t *testing.T) {
}
}

func TestAccFastlyServiceWAFVersionV1AddWithRules(t *testing.T) {
var service gofastly.ServiceDetail
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

rules := []gofastly.WAFActiveRule{
{
ModSecID: 2029718,
Status: "log",
Revision: 1,
},
{
ModSecID: 2037405,
Status: "log",
Revision: 1,
},
}
wafVerInput := testAccFastlyServiceWAFVersionV1BuildConfig(20)
rulesTF := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules)
wafVer := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckServiceV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccFastlyServiceWAFVersionV1(name, wafVer),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceV1Exists(serviceRef, &service),
testAccCheckFastlyServiceWAFVersionV1CheckRules(&service, rules, 1),
),
},
},
})
}

func TestAccFastlyServiceWAFVersionV1UpdateRules(t *testing.T) {
func TestAccFastlyServiceWAFVersionV1AddUpdateDeleteRules(t *testing.T) {
var service gofastly.ServiceDetail
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

Expand Down Expand Up @@ -170,63 +134,10 @@ func TestAccFastlyServiceWAFVersionV1UpdateRules(t *testing.T) {
}
wafVerInput := testAccFastlyServiceWAFVersionV1BuildConfig(20)
rulesTF1 := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules1)
wafVer1 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF1)

rulesTF2 := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules2)
wafVer2 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF2)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckServiceV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccFastlyServiceWAFVersionV1(name, wafVer1),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceV1Exists(serviceRef, &service),
testAccCheckFastlyServiceWAFVersionV1CheckRules(&service, rules1, 1),
),
},
{
Config: testAccFastlyServiceWAFVersionV1(name, wafVer2),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceV1Exists(serviceRef, &service),
testAccCheckFastlyServiceWAFVersionV1CheckRules(&service, rules2, 2),
),
},
},
})
}

func TestAccFastlyServiceWAFVersionV1DeleteRules(t *testing.T) {
var service gofastly.ServiceDetail
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

rules1 := []gofastly.WAFActiveRule{
{
ModSecID: 2029718,
Status: "log",
Revision: 1,
},
{
ModSecID: 2037405,
Status: "log",
Revision: 1,
},
}
rules2 := []gofastly.WAFActiveRule{
{
ModSecID: 2029718,
Status: "block",
Revision: 1,
},
}
wafVerInput := testAccFastlyServiceWAFVersionV1BuildConfig(20)
rulesTF1 := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules1)
wafVer1 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF1)
wafVer1 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF1, "")

rulesTF2 := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules2)
wafVer2 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF2)
wafVer2 := testAccFastlyServiceWAFVersionV1ComposeConfiguration(wafVerInput, rulesTF2, "")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -251,47 +162,6 @@ func TestAccFastlyServiceWAFVersionV1DeleteRules(t *testing.T) {
})
}

func TestAccFastlyServiceWAFVersionV1ImportWithRules(t *testing.T) {

var service gofastly.ServiceDetail
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

rules := []gofastly.WAFActiveRule{
{
ModSecID: 2029718,
Status: "log",
Revision: 1,
},
{
ModSecID: 2037405,
Status: "log",
Revision: 1,
},
}

rulesTF := testAccCheckFastlyServiceWAFVersionV1ComposeWAFRules(rules)
wafVer := testAccFastlyServiceWAFVersionV1ComposeConfiguration(nil, rulesTF)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckServiceV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccFastlyServiceWAFVersionV1(name, wafVer),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceV1Exists(serviceRef, &service),
),
},
{
ResourceName: "fastly_service_waf_configuration.waf",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckFastlyServiceWAFVersionV1CheckRules(service *gofastly.ServiceDetail, expected []gofastly.WAFActiveRule, wafVerNo int) resource.TestCheckFunc {
return func(s *terraform.State) error {

Expand Down
213 changes: 213 additions & 0 deletions fastly/block_fastly_waf_configuration_v1_exclusion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package fastly

import (
"fmt"
"log"
"strconv"

gofastly "github.com/fastly/go-fastly/v2/fastly"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

var wafRuleExclusion = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Name of the exclusion.",
},
"condition": {
Type: schema.TypeString,
Required: true,
Description: "A conditional expression in VCL used to determine if the condition is met.",
},
"exclusion_type": {
Type: schema.TypeString,
Required: true,
Description: "The type of exclusion.",
ValidateFunc: validateExecutionType(),
},
"modsec_rule_ids": {
Type: schema.TypeSet,
Optional: true,
Description: "The modsec rule IDs to exclude.",
Elem: &schema.Schema{Type: schema.TypeInt},
},
"number": {
Type: schema.TypeInt,
Computed: true,
Description: "A sequential ID assigned to the exclusion.",
},
},
},
}

func readWAFRuleExclusions(meta interface{}, d *schema.ResourceData, wafVersionNumber int) error {
conn := meta.(*FastlyClient).conn
wafID := d.Get("waf_id").(string)

resp, e := conn.ListAllWAFRuleExclusions(&gofastly.ListAllWAFRuleExclusionsInput{
WAFID: wafID,
WAFVersionNumber: wafVersionNumber,
Include: []string{"waf_rules"},
})

if e != nil {
return e
}

err := d.Set("rule_exclusion", flattenWAFRuleExclusions(resp.Items))

if err != nil {
log.Printf("[WARN] Error setting WAF rule exclusions for (%s): %s", d.Id(), err)
}

return nil
}

func flattenWAFRuleExclusions(exclusions []*gofastly.WAFRuleExclusion) []map[string]interface{} {
var result []map[string]interface{}

for _, exclusion := range exclusions {

m := make(map[string]interface{})
if exclusion.Name != nil {
m["name"] = *exclusion.Name
}
if exclusion.Number != nil {
m["number"] = *exclusion.Number
}
if exclusion.Condition != nil {
m["condition"] = *exclusion.Condition
}
if exclusion.ExclusionType != nil {
m["exclusion_type"] = *exclusion.ExclusionType
}

var rules []interface{}
for _, rule := range exclusion.Rules {
rules = append(rules, rule.ModSecID)
}
if len(rules) > 0 {
m["modsec_rule_ids"] = schema.NewSet(schema.HashInt, rules)
}
result = append(result, m)
}

return result
}

func updateWAFRuleExclusions(d *schema.ResourceData, meta interface{}, wafID string, wafVersionNumber int) error {

os, ns := d.GetChange("rule_exclusion")

if os == nil {
os = new(schema.Set)
}
if ns == nil {
ns = new(schema.Set)
}

oss := os.(*schema.Set)
nss := ns.(*schema.Set)

add := nss.Difference(oss).List()
remove := oss.Difference(nss).List()

var err error

err = deleteWAFRuleExclusion(remove, meta, wafID, wafVersionNumber)
if err != nil {
return err
}

err = createWAFRuleExclusion(add, meta, wafID, wafVersionNumber)
if err != nil {
return err
}

return nil
}

func deleteWAFRuleExclusion(remove []interface{}, meta interface{}, wafID string, wafVersionNumber int) error {
conn := meta.(*FastlyClient).conn

for _, aRaw := range remove {
a := aRaw.(map[string]interface{})

err := conn.DeleteWAFRuleExclusion(&gofastly.DeleteWAFRuleExclusionInput{
Number: a["number"].(int),
WAFID: wafID,
WAFVersionNumber: wafVersionNumber,
})

if err != nil {
return err
}
}

return nil
}

func createWAFRuleExclusion(add []interface{}, meta interface{}, wafID string, wafVersionNumber int) error {
conn := meta.(*FastlyClient).conn

for _, aRaw := range add {
a := aRaw.(map[string]interface{})

var rules []*gofastly.WAFRule
if a["exclusion_type"] == gofastly.WAFRuleExclusionTypeRule {
for _, ruleId := range a["modsec_rule_ids"].(*schema.Set).List() {
rules = append(rules, &gofastly.WAFRule{
ID: strconv.Itoa(ruleId.(int)),
})
}
} else {
rules = nil
}

_, err := conn.CreateWAFRuleExclusion(&gofastly.CreateWAFRuleExclusionInput{
WAFID: wafID,
WAFVersionNumber: wafVersionNumber,
WAFRuleExclusion: &gofastly.WAFRuleExclusion{
Name: strToPtr(a["name"].(string)),
ExclusionType: strToPtr(a["exclusion_type"].(string)),
Condition: strToPtr(a["condition"].(string)),
Rules: rules,
},
})

if err != nil {
return err
}
}
return nil
}

func validateExecutionType() schema.SchemaValidateFunc {
return validation.StringInSlice(
[]string{
gofastly.WAFRuleExclusionTypeRule,
gofastly.WAFRuleExclusionTypeWAF,
},
false,
)
}

func validateWAFRuleExclusion(d *schema.ResourceDiff) error {
for _, i := range d.Get("rule_exclusion").(*schema.Set).List() {
wafRuleExclusion := i.(map[string]interface{})

if wafRuleExclusion["exclusion_type"] == gofastly.WAFRuleExclusionTypeWAF && len(wafRuleExclusion["modsec_rule_ids"].(*schema.Set).List()) > 0 {
return fmt.Errorf("must not set \"modsec_rule_ids\" with \"waf\" exclusion type in exclusion \"%s\"", wafRuleExclusion["name"])
}
if wafRuleExclusion["exclusion_type"] == gofastly.WAFRuleExclusionTypeRule && len(wafRuleExclusion["modsec_rule_ids"].(*schema.Set).List()) == 0 {
return fmt.Errorf("must set \"modsec_rule_ids\" with \"rule\" exclusion type in exclusion \"%s\"", wafRuleExclusion["name"])
}
}
return nil
}
Loading

0 comments on commit c336db2

Please sign in to comment.