Skip to content

Commit

Permalink
Merge pull request #10563 from flosell/d-waf-subscribed-rule-group
Browse files Browse the repository at this point in the history
Add data sources for Managed Rules for WAF and WAF Regional
  • Loading branch information
zhelding authored Aug 3, 2022
2 parents be93039 + 6787fc1 commit a9aea59
Show file tree
Hide file tree
Showing 10 changed files with 595 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/contributing/maintaining.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi
| `TF_ACC` | Enables Go tests containing `resource.Test()` and `resource.ParallelTest()`. |
| `TF_ACC_ASSUME_ROLE_ARN` | Amazon Resource Name of existing IAM Role to use for limited permissions acceptance testing. |
| `TF_TEST_CLOUDFRONT_RETAIN` | Flag to disable but dangle CloudFront Distributions during testing to reduce feedback time (must be manually destroyed afterwards) |
| `WAF_SUBSCRIBED_RULE_GROUP_NAME` | Subscribed rule group name for WAF testing. Should be set to the name of an existing subscribed rule group within the account. |
| `WAF_SUBSCRIBED_RULE_GROUP_METRIC_NAME` | The name of the metric measured by the subscribed rule group used for WAF testing. Required as the rule group name is not a unique identifier. |

## Label Dictionary

Expand Down
20 changes: 11 additions & 9 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -887,15 +887,17 @@ func New(_ context.Context) (*schema.Provider, error) {

"aws_transfer_server": transfer.DataSourceServer(),

"aws_waf_ipset": waf.DataSourceIPSet(),
"aws_waf_rule": waf.DataSourceRule(),
"aws_waf_rate_based_rule": waf.DataSourceRateBasedRule(),
"aws_waf_web_acl": waf.DataSourceWebACL(),

"aws_wafregional_ipset": wafregional.DataSourceIPSet(),
"aws_wafregional_rule": wafregional.DataSourceRule(),
"aws_wafregional_rate_based_rule": wafregional.DataSourceRateBasedRule(),
"aws_wafregional_web_acl": wafregional.DataSourceWebACL(),
"aws_waf_ipset": waf.DataSourceIPSet(),
"aws_waf_rule": waf.DataSourceRule(),
"aws_waf_rate_based_rule": waf.DataSourceRateBasedRule(),
"aws_waf_subscribed_rule_group": waf.DataSourceSubscribedRuleGroup(),
"aws_waf_web_acl": waf.DataSourceWebACL(),

"aws_wafregional_ipset": wafregional.DataSourceIPSet(),
"aws_wafregional_rule": wafregional.DataSourceRule(),
"aws_wafregional_rate_based_rule": wafregional.DataSourceRateBasedRule(),
"aws_wafregional_subscribed_rule_group": wafregional.DataSourceSubscribedRuleGroup(),
"aws_wafregional_web_acl": wafregional.DataSourceWebACL(),

"aws_wafv2_ip_set": wafv2.DataSourceIPSet(),
"aws_wafv2_regex_pattern_set": wafv2.DataSourceRegexPatternSet(),
Expand Down
74 changes: 74 additions & 0 deletions internal/service/waf/find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package waf

import (
"context"
"errors"
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/waf"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func FindSubscribedRuleGroupByNameOrMetricName(ctx context.Context, conn *waf.WAF, name string, metricName string) (*waf.SubscribedRuleGroupSummary, error) {
hasName := name != ""
hasMetricName := metricName != ""
hasMatch := false

if !hasName && !hasMetricName {
return nil, errors.New("must specify either name or metricName")
}

input := &waf.ListSubscribedRuleGroupsInput{}

matchingRuleGroup := &waf.SubscribedRuleGroupSummary{}

for {
output, err := conn.ListSubscribedRuleGroupsWithContext(ctx, input)

if tfawserr.ErrCodeContains(err, waf.ErrCodeNonexistentItemException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

for _, ruleGroup := range output.RuleGroups {
respName := aws.StringValue(ruleGroup.Name)
respMetricName := aws.StringValue(ruleGroup.MetricName)

if hasName && respName != name {
continue
}
if hasMetricName && respMetricName != metricName {
continue
}
if hasName && hasMetricName && (name != respName || metricName != respMetricName) {
continue
}
// Previous conditionals catch all non-matches
if hasMatch {
return nil, fmt.Errorf("multiple matches found for name %s and metricName %s", name, metricName)
}

matchingRuleGroup = ruleGroup
hasMatch = true
}

if output.NextMarker == nil {
break
}
input.NextMarker = output.NextMarker
}

if !hasMatch {
return nil, fmt.Errorf("no matches found for name %s and metricName %s", name, metricName)
}

return matchingRuleGroup, nil
}
61 changes: 61 additions & 0 deletions internal/service/waf/subscribed_rule_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package waf

import (
"context"
"errors"

"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/names"
)

const (
DSNameSubscribedRuleGroup = "Subscribed Rule Group Data Source"
)

func DataSourceSubscribedRuleGroup() *schema.Resource {
return &schema.Resource{
ReadWithoutTimeout: dataSourceSubscribedRuleGroupRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
},
"metric_name": {
Type: schema.TypeString,
Optional: true,
},
},
}
}

func dataSourceSubscribedRuleGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).WAFConn
name, nameOk := d.Get("name").(string)
metricName, metricNameOk := d.Get("metric_name").(string)

// Error out if string-assertion fails for either name or metricName
if !nameOk || !metricNameOk {
if !nameOk {
name = DSNameSubscribedRuleGroup
}

err := errors.New("unable to read attributes")
return names.DiagError(names.WAF, names.ErrActionReading, DSNameSubscribedRuleGroup, name, err)
}

output, err := FindSubscribedRuleGroupByNameOrMetricName(ctx, conn, name, metricName)

if err != nil {
return names.DiagError(names.WAF, names.ErrActionReading, DSNameSubscribedRuleGroup, name, err)
}

d.SetId(aws.StringValue(output.RuleGroupId))
d.Set("metric_name", output.MetricName)
d.Set("name", output.Name)

return nil
}
106 changes: 106 additions & 0 deletions internal/service/waf/subscribed_rule_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package waf_test

import (
"fmt"
"os"
"regexp"
"testing"

"github.com/aws/aws-sdk-go/service/waf"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
)

func TestAccWAFSubscribedRuleGroupDataSource_basic(t *testing.T) {
if os.Getenv("WAF_SUBSCRIBED_RULE_GROUP_NAME") == "" {
t.Skip("Environment variable WAF_SUBSCRIBED_RULE_GROUP_NAME is not set")
}

ruleGroupName := os.Getenv("WAF_SUBSCRIBED_RULE_GROUP_NAME")

if os.Getenv("WAF_SUBSCRIBED_RULE_GROUP_METRIC_NAME") == "" {
t.Skip("Environment variable WAF_SUBSCRIBED_RULE_GROUP_METRIC_NAME is not set")
}

metricName := os.Getenv("WAF_SUBSCRIBED_RULE_GROUP_METRIC_NAME")

datasourceName := "data.aws_waf_subscribed_rule_group.rulegroup"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(waf.EndpointsID, t) },
ErrorCheck: acctest.ErrorCheck(t, waf.EndpointsID),
CheckDestroy: nil,
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccSubscribedRuleGroupDataSourceConfig_nonexistent,
ExpectError: regexp.MustCompile(`no matches found`),
},
{
Config: testAccSubscribedRuleGroupDataSourceConfig_name(ruleGroupName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "name", ruleGroupName),
resource.TestCheckResourceAttr(datasourceName, "metric_name", metricName),
),
},
{
Config: testAccSubscribedRuleGroupDataSourceConfig_metricName(metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "name", ruleGroupName),
resource.TestCheckResourceAttr(datasourceName, "metric_name", metricName),
),
},
{
Config: testAccSubscribedRuleGroupDataSourceConfig_nameAndMetricName(ruleGroupName, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "name", ruleGroupName),
resource.TestCheckResourceAttr(datasourceName, "metric_name", metricName),
),
},
{
Config: testAccSubscribedRuleGroupDataSourceConfig_nameAndMismatchingMetricName(ruleGroupName),
ExpectError: regexp.MustCompile(`no matches found`),
},
},
})
}

func testAccSubscribedRuleGroupDataSourceConfig_name(name string) string {
return fmt.Sprintf(`
data "aws_waf_subscribed_rule_group" "rulegroup" {
name = %[1]q
}
`, name)
}

func testAccSubscribedRuleGroupDataSourceConfig_metricName(metricName string) string {
return fmt.Sprintf(`
data "aws_waf_subscribed_rule_group" "rulegroup" {
metric_name = %[1]q
}
`, metricName)
}

func testAccSubscribedRuleGroupDataSourceConfig_nameAndMetricName(name string, metricName string) string {
return fmt.Sprintf(`
data "aws_waf_subscribed_rule_group" "rulegroup" {
name = %[1]q
metric_name = %[2]q
}
`, name, metricName)
}

func testAccSubscribedRuleGroupDataSourceConfig_nameAndMismatchingMetricName(name string) string {
return fmt.Sprintf(`
data "aws_waf_subscribed_rule_group" "rulegroup" {
name = %[1]q
metric_name = "tf-acc-test-does-not-exist"
}
`, name)
}

const testAccSubscribedRuleGroupDataSourceConfig_nonexistent = `
data "aws_waf_subscribed_rule_group" "rulegroup" {
name = "tf-acc-test-does-not-exist"
}
`
68 changes: 68 additions & 0 deletions internal/service/wafregional/find.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package wafregional

import (
"context"
"errors"
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/waf"
"github.com/aws/aws-sdk-go/service/wafregional"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func FindRegexMatchSetByID(conn *wafregional.WAFRegional, id string) (*waf.RegexMatchSet, error) {
Expand All @@ -13,3 +19,65 @@ func FindRegexMatchSetByID(conn *wafregional.WAFRegional, id string) (*waf.Regex

return result.RegexMatchSet, err
}

func FindSubscribedRuleGroupByNameOrMetricName(ctx context.Context, conn *wafregional.WAFRegional, name string, metricName string) (*waf.SubscribedRuleGroupSummary, error) {
hasName := name != ""
hasMetricName := metricName != ""
hasMatch := false

if !hasName && !hasMetricName {
return nil, errors.New("must specify either name or metricName")
}

input := &waf.ListSubscribedRuleGroupsInput{}

matchingRuleGroup := &waf.SubscribedRuleGroupSummary{}

for {
output, err := conn.ListSubscribedRuleGroupsWithContext(ctx, input)

if tfawserr.ErrCodeContains(err, waf.ErrCodeNonexistentItemException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

for _, ruleGroup := range output.RuleGroups {
respName := aws.StringValue(ruleGroup.Name)
respMetricName := aws.StringValue(ruleGroup.MetricName)

if hasName && respName != name {
continue
}
if hasMetricName && respMetricName != metricName {
continue
}
if hasName && hasMetricName && (name != respName || metricName != respMetricName) {
continue
}
// Previous conditionals catch all non-matches
if hasMatch {
return nil, fmt.Errorf("multiple matches found for name %s and metricName %s", name, metricName)
}

matchingRuleGroup = ruleGroup
hasMatch = true
}

if output.NextMarker == nil {
break
}
input.NextMarker = output.NextMarker
}

if !hasMatch {
return nil, fmt.Errorf("no matches found for name %s and metricName %s", name, metricName)
}

return matchingRuleGroup, nil
}
Loading

0 comments on commit a9aea59

Please sign in to comment.