Skip to content

Commit

Permalink
Add data source for WAF Rules (#525)
Browse files Browse the repository at this point in the history
Add data source for WAF Rules
  • Loading branch information
XaF authored and jacobbednarz committed Nov 3, 2019
1 parent f60eff7 commit 66cfb4f
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 0 deletions.
204 changes: 204 additions & 0 deletions cloudflare/data_source_waf_rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package cloudflare

import (
"fmt"
"log"
"regexp"
"time"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func dataSourceCloudflareWAFRules() *schema.Resource {
return &schema.Resource{
Read: dataSourceCloudflareWAFRulesRead,

Schema: map[string]*schema.Schema{
"zone_id": {
Type: schema.TypeString,
Required: true,
},

"package_id": {
Type: schema.TypeString,
Optional: true,
},

"filter": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"description": {
Type: schema.TypeString,
Optional: true,
},
"mode": {
Type: schema.TypeString,
Optional: true,
},
"group_id": {
Type: schema.TypeString,
Optional: true,
},
},
},
},

"rules": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Optional: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"priority": {
Type: schema.TypeString,
Optional: true,
},
"mode": {
Type: schema.TypeString,
Optional: true,
},
"group_id": {
Type: schema.TypeString,
Optional: true,
},
"group_name": {
Type: schema.TypeString,
Optional: true,
},
"package_id": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
}
}

func dataSourceCloudflareWAFRulesRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
zoneID := d.Get("zone_id").(string)

// Prepare the filters to be applied to the search
filter, err := expandFilterWAFRules(d.Get("filter"))
if err != nil {
return err
}

// If no package ID is given, we will consider all for the zone
packageID := d.Get("package_id").(string)
var pkgList []cloudflare.WAFPackage
if packageID == "" {
var err error
log.Printf("[DEBUG] Reading WAF Packages")
pkgList, err = client.ListWAFPackages(zoneID)
if err != nil {
return err
}
} else {
pkgList = append(pkgList, cloudflare.WAFPackage{ID: packageID})
}

log.Printf("[DEBUG] Reading WAF Rules")
ruleDetails := make([]interface{}, 0)
for _, pkg := range pkgList {
ruleList, err := client.ListWAFRules(zoneID, pkg.ID)
if err != nil {
return err
}

foundGroup := false
for _, rule := range ruleList {
if filter.GroupID != "" {
if filter.GroupID != rule.Group.ID {
continue
}

// Allows to stop querying the API faster
foundGroup = true
}

if filter.Description != nil && !filter.Description.Match([]byte(rule.Description)) {
continue
}

if filter.Mode != "" && filter.Mode != rule.Mode {
continue
}

ruleDetails = append(ruleDetails, map[string]interface{}{
"id": rule.ID,
"description": rule.Description,
"priority": rule.Priority,
"mode": rule.Mode,
"group_id": rule.Group.ID,
"group_name": rule.Group.Name,
"package_id": pkg.ID,
})
}

if foundGroup {
// We can stop looking further as a group is only part of a unique
// package, meaning that if we found the group, no need to go look
// at other packages
break
}
}

err = d.Set("rules", ruleDetails)
if err != nil {
return fmt.Errorf("Error setting WAF rules: %s", err)
}

d.SetId(fmt.Sprintf("WAFRules_%s", time.Now().UTC().String()))
return nil
}

func expandFilterWAFRules(d interface{}) (*searchFilterWAFRules, error) {
cfg := d.([]interface{})
filter := &searchFilterWAFRules{}
if len(cfg) == 0 || cfg[0] == nil {
return filter, nil
}

m := cfg[0].(map[string]interface{})
description, ok := m["description"]
if ok {
match, err := regexp.Compile(description.(string))
if err != nil {
return nil, err
}

filter.Description = match
}

mode, ok := m["mode"]
if ok {
filter.Mode = mode.(string)
}

groupID, ok := m["group_id"]
if ok {
filter.GroupID = groupID.(string)
}

return filter, nil
}

type searchFilterWAFRules struct {
Description *regexp.Regexp
Mode string
GroupID string
}
104 changes: 104 additions & 0 deletions cloudflare/data_source_waf_rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cloudflare

import (
"fmt"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccCloudflareWAFRules_NoFilter(t *testing.T) {
t.Parallel()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
rnd := generateRandomResourceName()
name := fmt.Sprintf("data.cloudflare_waf_rules.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudflareWAFRulesConfig(zoneID, map[string]string{}, rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWAFRulesDataSourceID(name),
resource.TestCheckResourceAttr(name, "rules.#", "40"),
),
},
},
})
}

func TestAccCloudflareWAFRules_MatchDescription(t *testing.T) {
t.Parallel()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
rnd := generateRandomResourceName()
name := fmt.Sprintf("data.cloudflare_waf_rules.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudflareWAFRulesConfig(zoneID, map[string]string{"description": "^SLR: .*"}, rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWAFRulesDataSourceID(name),
resource.TestCheckResourceAttr(name, "rules.#", "20"),
),
},
},
})
}

func TestAccCloudflareWAFRules_MatchMode(t *testing.T) {
t.Parallel()
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
rnd := generateRandomResourceName()
name := fmt.Sprintf("data.cloudflare_waf_rules.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudflareWAFRulesConfig(zoneID, map[string]string{"mode": "on"}, rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWAFRulesDataSourceID(name),
),
},
},
})
}

func testAccCheckCloudflareWAFRulesDataSourceID(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
all := s.RootModule().Resources
rs, ok := all[n]
if !ok {
return fmt.Errorf("Can't find WAF Rules data source: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("Snapshot WAF Rules source ID not set")
}
return nil
}
}

func testAccCloudflareWAFRulesConfig(zoneID string, filters map[string]string, name string) string {
filters_str := make([]string, 0, len(filters))
for k, v := range filters {
filters_str = append(filters_str, fmt.Sprintf(`%[1]s = "%[2]s"`, k, v))
}

return fmt.Sprintf(`
data "cloudflare_waf_rules" "%[1]s" {
zone_id = "%[2]s"
filter {
%[3]s
}
}`, name, zoneID, strings.Join(filters_str, "\n\t\t"))
}
1 change: 1 addition & 0 deletions cloudflare/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func Provider() terraform.ResourceProvider {
"cloudflare_ip_ranges": dataSourceCloudflareIPRanges(),
"cloudflare_waf_groups": dataSourceCloudflareWAFGroups(),
"cloudflare_waf_packages": dataSourceCloudflareWAFPackages(),
"cloudflare_waf_rules": dataSourceCloudflareWAFRules(),
"cloudflare_zones": dataSourceCloudflareZones(),
},

Expand Down
3 changes: 3 additions & 0 deletions website/cloudflare.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<li<%= sidebar_current("docs-cloudflare-datasource-waf-packages") %>>
<a href="/docs/providers/cloudflare/d/waf_packages.html">cloudflare_waf_packages</a>
</li>
<li<%= sidebar_current("docs-cloudflare-datasource-waf-rules") %>>
<a href="/docs/providers/cloudflare/d/waf_rules.html">cloudflare_waf_rules</a>
</li>
<li<%= sidebar_current("docs-cloudflare-datasource-zones") %>>
<a href="/docs/providers/cloudflare/d/zones.html">cloudflare_zones</a>
</li>
Expand Down
61 changes: 61 additions & 0 deletions website/docs/d/waf_rules.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
layout: "cloudflare"
page_title: "Cloudflare: cloudflare_waf_rules"
sidebar_current: "docs-cloudflare-datasource-waf-rules"
description: |-
List available Cloudflare WAF Rules.
---

# cloudflare_waf_rules

Use this data source to look up [WAF Rules][1].

## Example Usage

The example below matches all WAF Rules that are in the group of ID `de677e5818985db1285d0e80225f06e5`, contain `example` in their description, and are currently `on`. The matched WAF Rules are then returned as output.

```hcl
data "cloudflare_waf_rules" "test" {
zone_id = "ae36f999674d196762efcc5abb06b345"
package_id = "a25a9a7e9c00afc1fb2e0245519d725b"
filter {
description = ".*example.*"
mode = "on"
group_id = "de677e5818985db1285d0e80225f06e5"
}
}
output "waf_rules" {
value = data.cloudflare_waf_rules.test.rules
}
```

## Argument Reference

- `zone_id` - (Required) The ID of the DNS zone in which to search for the WAF Rules.
- `package_id` - (Optional) The ID of the WAF Rule Package in which to search for the WAF Rules.
- `filter` - (Optional) One or more values used to look up WAF Rules. If more than one value is given all
values must match in order to be included, see below for full list.

**filter**

- `description` - (Optional) A regular expression matching the description of the WAF Rules to lookup.
- `mode` - (Optional) Mode of the WAF Rules to lookup. Valid values: `"on"` and `"off"`.
- `group_id` - (Optional) The ID of the WAF Rule Group in which the WAF Rules to lookup have to be.

## Attributes Reference

- `rules` - A map of WAF Rules details. Full list below:

**rules**

- `id` - The WAF Rule ID
- `description` - The WAF Rule description
- `priority` - The WAF Rule priority
- `mode` - The WAF Rule mode
- `group_id` - The ID of the WAF Rule Group that contains the WAF Rule
- `group_name` - The Name of the WAF Rule Group that contains the WAF Rule
- `package_id` - The ID of the WAF Rule Package that contains the WAF Rule

[1]: https://api.cloudflare.com/#waf-rule-groups-properties

0 comments on commit 66cfb4f

Please sign in to comment.