-
Notifications
You must be signed in to change notification settings - Fork 626
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add data source for WAF Rules (#525)
Add data source for WAF Rules
- Loading branch information
1 parent
f60eff7
commit 66cfb4f
Showing
5 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |