diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 17105d2598bc..5fc3a5c2d92f 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -64,6 +64,7 @@ import ( "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/sts" "github.com/aws/aws-sdk-go/service/waf" + "github.com/aws/aws-sdk-go/service/wafregional" "github.com/davecgh/go-spew/spew" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-cleanhttp" @@ -159,6 +160,7 @@ type AWSClient struct { sfnconn *sfn.SFN ssmconn *ssm.SSM wafconn *waf.WAF + wafregionalconn *wafregional.WAFRegional } func (c *AWSClient) S3() *s3.S3 { @@ -339,6 +341,7 @@ func (c *Config) Client() (interface{}, error) { client.sqsconn = sqs.New(sess) client.ssmconn = ssm.New(sess) client.wafconn = waf.New(sess) + client.wafregionalconn = wafregional.New(sess) return &client, nil } diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index b1f9c2bf4b1f..ae638c1df1c7 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -445,6 +445,8 @@ func Provider() terraform.ResourceProvider { "aws_waf_web_acl": resourceAwsWafWebAcl(), "aws_waf_xss_match_set": resourceAwsWafXssMatchSet(), "aws_waf_sql_injection_match_set": resourceAwsWafSqlInjectionMatchSet(), + "aws_wafregional_byte_match_set": resourceAwsWafRegionalByteMatchSet(), + "aws_wafregional_web_acl": resourceAwsWafRegionalWebAcl(), }, ConfigureFunc: providerConfigure, } diff --git a/builtin/providers/aws/resource_aws_wafregional_byte_match_set.go b/builtin/providers/aws/resource_aws_wafregional_byte_match_set.go new file mode 100644 index 000000000000..8dbe93f3730e --- /dev/null +++ b/builtin/providers/aws/resource_aws_wafregional_byte_match_set.go @@ -0,0 +1,197 @@ +package aws + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsWafRegionalByteMatchSet() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsWafRegionalByteMatchSetCreate, + Read: resourceAwsWafRegionalByteMatchSetRead, + Update: resourceAwsWafRegionalByteMatchSetUpdate, + Delete: resourceAwsWafRegionalByteMatchSetDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "byte_match_tuples": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_to_match": { + Type: schema.TypeSet, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "data": { + Type: schema.TypeString, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "positional_constraint": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "target_string": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "text_transformation": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceAwsWafRegionalByteMatchSetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + log.Printf("[INFO] Creating ByteMatchSet: %s", d.Get("name").(string)) + + wr := newWafRegionalRetryer(conn) + out, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + params := &waf.CreateByteMatchSetInput{ + ChangeToken: token, + Name: aws.String(d.Get("name").(string)), + } + return conn.CreateByteMatchSet(params) + }) + + if err != nil { + return errwrap.Wrapf("[ERROR] Error creating ByteMatchSet: {{err}}", err) + } + resp := out.(*waf.CreateByteMatchSetOutput) + + d.SetId(*resp.ByteMatchSet.ByteMatchSetId) + + return resourceAwsWafRegionalByteMatchSetUpdate(d, meta) +} + +func resourceAwsWafRegionalByteMatchSetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + log.Printf("[INFO] Reading ByteMatchSet: %s", d.Get("name").(string)) + + params := &waf.GetByteMatchSetInput{ + ByteMatchSetId: aws.String(d.Id()), + } + + resp, err := conn.GetByteMatchSet(params) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "WAFNonexistentItemException" { + log.Printf("[WARN] WAF IPSet (%s) not found, error code (404)", d.Id()) + d.SetId("") + return nil + } + + return err + } + + d.Set("name", resp.ByteMatchSet.Name) + + return nil +} + +func resourceAwsWafRegionalByteMatchSetUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Updating ByteMatchSet: %s", d.Get("name").(string)) + + err := updateByteMatchSetResourceWR(d, meta, waf.ChangeActionInsert) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating ByteMatchSet: {{err}}", err) + } + return resourceAwsWafRegionalByteMatchSetRead(d, meta) +} + +func resourceAwsWafRegionalByteMatchSetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + log.Printf("[INFO] Deleting ByteMatchSet: %s", d.Get("name").(string)) + + err := updateByteMatchSetResourceWR(d, meta, waf.ChangeActionDelete) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting ByteMatchSet: {{err}}", err) + } + + wr := newWafRegionalRetryer(conn) + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.DeleteByteMatchSetInput{ + ChangeToken: token, + ByteMatchSetId: aws.String(d.Id()), + } + return conn.DeleteByteMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting ByteMatchSet: {{err}}", err) + } + + return nil +} + +func updateByteMatchSetResourceWR(d *schema.ResourceData, meta interface{}, ChangeAction string) error { + conn := meta.(*AWSClient).wafregionalconn + + wr := newWafRegionalRetryer(conn) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateByteMatchSetInput{ + ChangeToken: token, + ByteMatchSetId: aws.String(d.Id()), + } + + ByteMatchTuples := d.Get("byte_match_tuples").(*schema.Set) + for _, ByteMatchTuple := range ByteMatchTuples.List() { + ByteMatch := ByteMatchTuple.(map[string]interface{}) + ByteMatchUpdate := &waf.ByteMatchSetUpdate{ + Action: aws.String(ChangeAction), + ByteMatchTuple: &waf.ByteMatchTuple{ + FieldToMatch: expandFieldToMatch(ByteMatch["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + PositionalConstraint: aws.String(ByteMatch["positional_constraint"].(string)), + TargetString: []byte(ByteMatch["target_string"].(string)), + TextTransformation: aws.String(ByteMatch["text_transformation"].(string)), + }, + } + req.Updates = append(req.Updates, ByteMatchUpdate) + } + + return conn.UpdateByteMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating ByteMatchSet: {{err}}", err) + } + + return nil +} + +func expandFieldToMatchWR(d map[string]interface{}) *waf.FieldToMatch { + return &waf.FieldToMatch{ + Type: aws.String(d["type"].(string)), + Data: aws.String(d["data"].(string)), + } +} + +func flattenFieldToMatchWR(fm *waf.FieldToMatch) map[string]interface{} { + m := make(map[string]interface{}) + m["data"] = *fm.Data + m["type"] = *fm.Type + return m +} diff --git a/builtin/providers/aws/resource_aws_wafregional_byte_match_set_test.go b/builtin/providers/aws/resource_aws_wafregional_byte_match_set_test.go new file mode 100644 index 000000000000..6ec02813d72d --- /dev/null +++ b/builtin/providers/aws/resource_aws_wafregional_byte_match_set_test.go @@ -0,0 +1,250 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/acctest" +) + +func TestAccAWSWafRegionalByteMatchSet_basic(t *testing.T) { + var v waf.ByteMatchSet + byteMatchSet := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalByteMatchSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSWafRegionalByteMatchSetConfig(byteMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalByteMatchSetExists("aws_wafregional_byte_match_set.byte_set", &v), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "name", byteMatchSet), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalByteMatchSet_changeNameForceNew(t *testing.T) { + var before, after waf.ByteMatchSet + byteMatchSet := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + byteMatchSetNewName := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalByteMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalByteMatchSetConfig(byteMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalByteMatchSetExists("aws_wafregional_byte_match_set.byte_set", &before), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "name", byteMatchSet), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + ), + }, + { + Config: testAccAWSWafRegionalByteMatchSetConfigChangeName(byteMatchSetNewName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalByteMatchSetExists("aws_wafregional_byte_match_set.byte_set", &after), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "name", byteMatchSetNewName), + resource.TestCheckResourceAttr( + "aws_wafregional_byte_match_set.byte_set", "byte_match_tuples.#", "2"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalByteMatchSet_disappears(t *testing.T) { + var v waf.ByteMatchSet + byteMatchSet := fmt.Sprintf("byteMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalByteMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalByteMatchSetConfig(byteMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalByteMatchSetExists("aws_wafregional_byte_match_set.byte_set", &v), + testAccCheckAWSWafRegionalByteMatchSetDisappears(&v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSWafRegionalByteMatchSetDisappears(v *waf.ByteMatchSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + + wr := newWafRegionalRetryer(conn) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateByteMatchSetInput{ + ChangeToken: token, + ByteMatchSetId: v.ByteMatchSetId, + } + + for _, ByteMatchTuple := range v.ByteMatchTuples { + ByteMatchUpdate := &waf.ByteMatchSetUpdate{ + Action: aws.String("DELETE"), + ByteMatchTuple: &waf.ByteMatchTuple{ + FieldToMatch: ByteMatchTuple.FieldToMatch, + PositionalConstraint: ByteMatchTuple.PositionalConstraint, + TargetString: ByteMatchTuple.TargetString, + TextTransformation: ByteMatchTuple.TextTransformation, + }, + } + req.Updates = append(req.Updates, ByteMatchUpdate) + } + + return conn.UpdateByteMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating ByteMatchSet: {{err}}", err) + } + + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + opts := &waf.DeleteByteMatchSetInput{ + ChangeToken: token, + ByteMatchSetId: v.ByteMatchSetId, + } + return conn.DeleteByteMatchSet(opts) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting ByteMatchSet: {{err}}", err) + } + + return nil + } +} + +func testAccCheckAWSWafRegionalByteMatchSetExists(n string, v *waf.ByteMatchSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No WAF ByteMatchSet ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.GetByteMatchSet(&waf.GetByteMatchSetInput{ + ByteMatchSetId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if *resp.ByteMatchSet.ByteMatchSetId == rs.Primary.ID { + *v = *resp.ByteMatchSet + return nil + } + + return fmt.Errorf("WAF ByteMatchSet (%s) not found", rs.Primary.ID) + } +} + +func testAccCheckAWSWafRegionalByteMatchSetDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_wafregional_byte_match_set" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.GetByteMatchSet( + &waf.GetByteMatchSetInput{ + ByteMatchSetId: aws.String(rs.Primary.ID), + }) + + if err == nil { + if *resp.ByteMatchSet.ByteMatchSetId == rs.Primary.ID { + return fmt.Errorf("WAF ByteMatchSet %s still exists", rs.Primary.ID) + } + } + + // Return nil if the ByteMatchSet is already destroyed + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "WAFNonexistentItemException" { + return nil + } + } + + return err + } + + return nil +} + +func testAccAWSWafRegionalByteMatchSetConfig(name string) string { + return fmt.Sprintf(` +resource "aws_wafregional_byte_match_set" "byte_set" { + name = "%s" + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer1" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } + + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer2" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } +}`, name) +} + +func testAccAWSWafRegionalByteMatchSetConfigChangeName(name string) string { + return fmt.Sprintf(` +resource "aws_wafregional_byte_match_set" "byte_set" { + name = "%s" + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer1" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } + + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer2" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } +}`, name) +} diff --git a/builtin/providers/aws/resource_aws_wafregional_web_acl.go b/builtin/providers/aws/resource_aws_wafregional_web_acl.go new file mode 100644 index 000000000000..95498f585711 --- /dev/null +++ b/builtin/providers/aws/resource_aws_wafregional_web_acl.go @@ -0,0 +1,226 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsWafRegionalWebAcl() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsWafRegionalWebAclCreate, + Read: resourceAwsWafRegionalWebAclRead, + Update: resourceAwsWafRegionalWebAclUpdate, + Delete: resourceAwsWafRegionalWebAclDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "default_action": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "metric_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "rules": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "priority": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "rule_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceAwsWafRegionalWebAclCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + + wr := newWafRegionalRetryer(conn) + out, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + params := &waf.CreateWebACLInput{ + ChangeToken: token, + DefaultAction: expandDefaultAction(d), + MetricName: aws.String(d.Get("metric_name").(string)), + Name: aws.String(d.Get("name").(string)), + } + + return conn.CreateWebACL(params) + }) + if err != nil { + return err + } + resp := out.(*waf.CreateWebACLOutput) + d.SetId(*resp.WebACL.WebACLId) + return resourceAwsWafRegionalWebAclUpdate(d, meta) +} + +func resourceAwsWafRegionalWebAclRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + params := &waf.GetWebACLInput{ + WebACLId: aws.String(d.Id()), + } + + resp, err := conn.GetWebACL(params) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "WAFNonexistentItemException" { + log.Printf("[WARN] WAF ACL (%s) not found, error code (404)", d.Id()) + d.SetId("") + return nil + } + + return err + } + + defaultAction := flattenDefaultActionWR(resp.WebACL.DefaultAction) + if defaultAction != nil { + if err := d.Set("default_action", defaultAction); err != nil { + return fmt.Errorf("error setting default_action: %s", err) + } + } + d.Set("name", resp.WebACL.Name) + d.Set("metric_name", resp.WebACL.MetricName) + + return nil +} + +func resourceAwsWafRegionalWebAclUpdate(d *schema.ResourceData, meta interface{}) error { + err := updateWebAclResourceWR(d, meta, waf.ChangeActionInsert) + if err != nil { + return fmt.Errorf("Error Updating WAF ACL: %s", err) + } + return resourceAwsWafRegionalWebAclRead(d, meta) +} + +func resourceAwsWafRegionalWebAclDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + err := updateWebAclResourceWR(d, meta, waf.ChangeActionDelete) + if err != nil { + return fmt.Errorf("Error Removing WAF ACL Rules: %s", err) + } + + wr := newWafRegionalRetryer(conn) + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.DeleteWebACLInput{ + ChangeToken: token, + WebACLId: aws.String(d.Id()), + } + + log.Printf("[INFO] Deleting WAF ACL") + return conn.DeleteWebACL(req) + }) + if err != nil { + return fmt.Errorf("Error Deleting WAF ACL: %s", err) + } + return nil +} + +func updateWebAclResourceWR(d *schema.ResourceData, meta interface{}, ChangeAction string) error { + conn := meta.(*AWSClient).wafregionalconn + wr := newWafRegionalRetryer(conn) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateWebACLInput{ + ChangeToken: token, + WebACLId: aws.String(d.Id()), + } + + if d.HasChange("default_action") { + req.DefaultAction = expandDefaultAction(d) + } + + rules := d.Get("rules").(*schema.Set) + for _, rule := range rules.List() { + aclRule := rule.(map[string]interface{}) + action := aclRule["action"].(*schema.Set).List()[0].(map[string]interface{}) + aclRuleUpdate := &waf.WebACLUpdate{ + Action: aws.String(ChangeAction), + ActivatedRule: &waf.ActivatedRule{ + Priority: aws.Int64(int64(aclRule["priority"].(int))), + RuleId: aws.String(aclRule["rule_id"].(string)), + Action: &waf.WafAction{Type: aws.String(action["type"].(string))}, + }, + } + req.Updates = append(req.Updates, aclRuleUpdate) + } + return conn.UpdateWebACL(req) + }) + if err != nil { + return fmt.Errorf("Error Updating WAF ACL: %s", err) + } + return nil +} + +func expandDefaultActionWR(d *schema.ResourceData) *waf.WafAction { + set, ok := d.GetOk("default_action") + if !ok { + return nil + } + + s := set.(*schema.Set).List() + if s == nil || len(s) == 0 { + return nil + } + + if s[0] == nil { + log.Printf("[ERR] First element of Default Action is set to nil") + return nil + } + + dA := s[0].(map[string]interface{}) + + return &waf.WafAction{ + Type: aws.String(dA["type"].(string)), + } +} + +func flattenDefaultActionWR(n *waf.WafAction) []map[string]interface{} { + if n == nil { + return nil + } + + m := setMap(make(map[string]interface{})) + + m.SetString("type", n.Type) + return m.MapList() +} diff --git a/builtin/providers/aws/resource_aws_wafregional_web_acl_test.go b/builtin/providers/aws/resource_aws_wafregional_web_acl_test.go new file mode 100644 index 000000000000..671cd87ff002 --- /dev/null +++ b/builtin/providers/aws/resource_aws_wafregional_web_acl_test.go @@ -0,0 +1,367 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/hashicorp/terraform/helper/acctest" +) + +func TestAccAWSWafRegionalWebAcl_basic(t *testing.T) { + var v waf.WebACL + wafAclName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalWebAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSWafRegionalWebAclConfig(wafAclName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &v), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.4234791575.type", "ALLOW"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "name", wafAclName), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "rules.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "metric_name", wafAclName), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalWebAcl_changeNameForceNew(t *testing.T) { + var before, after waf.WebACL + wafAclName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + wafAclNewName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalWebAclDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalWebAclConfig(wafAclName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &before), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.4234791575.type", "ALLOW"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "name", wafAclName), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "rules.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "metric_name", wafAclName), + ), + }, + { + Config: testAccAWSWafRegionalWebAclConfigChangeName(wafAclNewName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &after), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.4234791575.type", "ALLOW"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "name", wafAclNewName), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "rules.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "metric_name", wafAclNewName), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalWebAcl_changeDefaultAction(t *testing.T) { + var before, after waf.WebACL + wafAclName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + wafAclNewName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalWebAclDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalWebAclConfig(wafAclName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &before), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.4234791575.type", "ALLOW"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "name", wafAclName), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "rules.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "metric_name", wafAclName), + ), + }, + { + Config: testAccAWSWafRegionalWebAclConfigDefaultAction(wafAclNewName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &after), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "default_action.2267395054.type", "BLOCK"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "name", wafAclNewName), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "rules.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_web_acl.waf_acl", "metric_name", wafAclNewName), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalWebAcl_disappears(t *testing.T) { + var v waf.WebACL + wafAclName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalWebAclDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalWebAclConfig(wafAclName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalWebAclExists("aws_wafregional_web_acl.waf_acl", &v), + testAccCheckAWSWafRegionalWebAclDisappears(&v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSWafRegionalWebAclDisappears(v *waf.WebACL) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + + wr := newWafRegionalRetryer(conn) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateWebACLInput{ + ChangeToken: token, + WebACLId: v.WebACLId, + } + + for _, ActivatedRule := range v.Rules { + WebACLUpdate := &waf.WebACLUpdate{ + Action: aws.String("DELETE"), + ActivatedRule: &waf.ActivatedRule{ + Priority: ActivatedRule.Priority, + RuleId: ActivatedRule.RuleId, + Action: ActivatedRule.Action, + }, + } + req.Updates = append(req.Updates, WebACLUpdate) + } + + return conn.UpdateWebACL(req) + }) + if err != nil { + return fmt.Errorf("Error getting change token for waf ACL: %s", err) + } + + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + opts := &waf.DeleteWebACLInput{ + ChangeToken: token, + WebACLId: v.WebACLId, + } + return conn.DeleteWebACL(opts) + }) + if err != nil { + return fmt.Errorf("Error Deleting WAF ACL: %s", err) + } + return nil + } +} + +func testAccCheckAWSWafRegionalWebAclDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_wafregional_web_acl" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.GetWebACL( + &waf.GetWebACLInput{ + WebACLId: aws.String(rs.Primary.ID), + }) + + if err == nil { + if *resp.WebACL.WebACLId == rs.Primary.ID { + return fmt.Errorf("WebACL %s still exists", rs.Primary.ID) + } + } + + // Return nil if the WebACL is already destroyed + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "WAFNonexistentItemException" { + return nil + } + } + + return err + } + + return nil +} + +func testAccCheckAWSWafRegionalWebAclExists(n string, v *waf.WebACL) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No WebACL ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.GetWebACL(&waf.GetWebACLInput{ + WebACLId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if *resp.WebACL.WebACLId == rs.Primary.ID { + *v = *resp.WebACL + return nil + } + + return fmt.Errorf("WebACL (%s) not found", rs.Primary.ID) + } +} + +func testAccAWSWafRegionalWebAclConfig(name string) string { + return fmt.Sprintf(`resource "aws_wafregional_ipset" "ipset" { + name = "%s" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "wafrule" { + depends_on = ["aws_wafregional_ipset.ipset"] + name = "%s" + metric_name = "%s" + predicates { + data_id = "${aws_wafregional_ipset.ipset.id}" + negated = false + type = "IPMatch" + } +} +resource "aws_wafregional_web_acl" "waf_acl" { + depends_on = ["aws_wafregional_ipset.ipset", "aws_wafregional_rule.wafrule"] + name = "%s" + metric_name = "%s" + default_action { + type = "ALLOW" + } + rules { + action { + type = "BLOCK" + } + priority = 1 + rule_id = "${aws_wafregional_rule.wafrule.id}" + } +}`, name, name, name, name, name) +} + +func testAccAWSWafRegionalWebAclConfigChangeName(name string) string { + return fmt.Sprintf(`resource "aws_wafregional_ipset" "ipset" { + name = "%s" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "wafrule" { + depends_on = ["aws_wafregional_ipset.ipset"] + name = "%s" + metric_name = "%s" + predicates { + data_id = "${aws_wafregional_ipset.ipset.id}" + negated = false + type = "IPMatch" + } +} +resource "aws_wafregional_web_acl" "waf_acl" { + depends_on = ["aws_wafregional_ipset.ipset", "aws_wafregional_rule.wafrule"] + name = "%s" + metric_name = "%s" + default_action { + type = "ALLOW" + } + rules { + action { + type = "BLOCK" + } + priority = 1 + rule_id = "${aws_wafregional_rule.wafrule.id}" + } +}`, name, name, name, name, name) +} + +func testAccAWSWafRegionalWebAclConfigDefaultAction(name string) string { + return fmt.Sprintf(`resource "aws_wafregional_ipset" "ipset" { + name = "%s" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "wafrule" { + depends_on = ["aws_wafregional_ipset.ipset"] + name = "%s" + metric_name = "%s" + predicates { + data_id = "${aws_wafregional_ipset.ipset.id}" + negated = false + type = "IPMatch" + } +} +resource "aws_wafregional_web_acl" "waf_acl" { + depends_on = ["aws_wafregional_ipset.ipset", "aws_wafregional_rule.wafrule"] + name = "%s" + metric_name = "%s" + default_action { + type = "BLOCK" + } + rules { + action { + type = "BLOCK" + } + priority = 1 + rule_id = "${aws_wafregional_rule.wafrule.id}" + } +}`, name, name, name, name, name) +} diff --git a/builtin/providers/aws/wafregionl_token_handlers.go b/builtin/providers/aws/wafregionl_token_handlers.go new file mode 100644 index 000000000000..6a08e12f3598 --- /dev/null +++ b/builtin/providers/aws/wafregionl_token_handlers.go @@ -0,0 +1,50 @@ +package aws + +import ( + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/aws/aws-sdk-go/service/wafregional" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/resource" +) + +type WafRegionalRetryer struct { + Connection *wafregional.WAFRegional + Region string +} + +type withRegionalTokenFunc func(token *string) (interface{}, error) + +func (t *WafRegionalRetryer) RetryWithToken(f withRegionalTokenFunc) (interface{}, error) { + awsMutexKV.Lock(t.Region) + defer awsMutexKV.Unlock(t.Region) + + var out interface{} + err := resource.Retry(15*time.Minute, func() *resource.RetryError { + var err error + var tokenOut *waf.GetChangeTokenOutput + + tokenOut, err = t.Connection.GetChangeToken(&waf.GetChangeTokenInput{}) + if err != nil { + return resource.NonRetryableError(errwrap.Wrapf("Failed to acquire change token: {{err}}", err)) + } + + out, err = f(tokenOut.ChangeToken) + if err != nil { + awsErr, ok := err.(awserr.Error) + if ok && awsErr.Code() == "WAFStaleDataException" { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + + return out, err +} + +func newWafRegionalRetryer(conn *wafregional.WAFRegional) *WafRegionalRetryer { + return &WafRegionalRetryer{Connection: conn} +} diff --git a/website/source/docs/providers/aws/r/wafregional_byte_match_set.html.markdown b/website/source/docs/providers/aws/r/wafregional_byte_match_set.html.markdown new file mode 100644 index 000000000000..7607bca3945b --- /dev/null +++ b/website/source/docs/providers/aws/r/wafregional_byte_match_set.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "aws" +page_title: "AWS: wafregional_byte_match_set" +sidebar_current: "docs-aws-resource-wafregional-bytematchset" +description: |- + Provides a AWS WAF Regional ByteMatchSet resource for use with ALB. +--- + +# aws\_wafregional\_byte\_match\_set + +Provides a WAF Regional Byte Match Set Resource for use with Application Load Balancer. + +## Example Usage + +``` +resource "aws_wafregional_byte_match_set" "byte_set" { + name = "tf_waf_byte_match_set" + byte_match_tuples { + text_transformation = "NONE" + target_string = "badrefer1" + positional_constraint = "CONTAINS" + field_to_match { + type = "HEADER" + data = "referer" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name or description of the ByteMatchSet. +* `byte_match_tuples` - Settings for the ByteMatchSet, such as the bytes (typically a string that corresponds with ASCII characters) that you want AWS WAF to search for in web requests. + +## Remarks + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the WAF ByteMatchSet. diff --git a/website/source/docs/providers/aws/r/wafregional_web_acl.html.markdown b/website/source/docs/providers/aws/r/wafregional_web_acl.html.markdown new file mode 100644 index 000000000000..96b896fe207d --- /dev/null +++ b/website/source/docs/providers/aws/r/wafregional_web_acl.html.markdown @@ -0,0 +1,66 @@ +--- +layout: "aws" +page_title: "AWS: aws_wafregional_web_acl" +sidebar_current: "docs-aws-resource-wafregional-web-acl" +description: |- + Provides a AWS WAF Regional web access control group (ACL) resource for use with ALB. +--- + +# aws\_wafregional\_web\_acl + +Provides a WAF Regional Web ACL Resource for use with Application Load Balancer. + +## Example Usage + +``` +resource "aws_wafregional_ipset" "ipset" { + name = "tfIPSet" + ip_set_descriptors { + type = "IPV4" + value = "192.0.7.0/24" + } +} + +resource "aws_wafregional_rule" "wafrule" { + depends_on = ["aws_wafregional_ipset.ipset"] + name = "tfWAFRule" + metric_name = "tfWAFRule" + predicates { + data_id = "${aws_wafregional_ipset.ipset.id}" + negated = false + type = "IPMatch" + } +} + +resource "aws_wafregional_web_acl" "wafacl" { + depends_on = ["aws_wafregional_ipset.ipset", "aws_wafregional_rule.wafrule"] + name = "tfWebACL" + metric_name = "tfWebACL" + default_action { + type = "ALLOW" + } + rules { + action { + type = "BLOCK" + } + priority = 1 + rule_id = "${aws_wafregional_rule.wafrule.id}" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `default_action` - (Required) The action that you want AWS WAF to take when a request doesn't match the criteria in any of the rules that are associated with the web ACL. +* `metric_name` - (Required) The name or description for the Amazon CloudWatch metric of this web ACL. +* `name` - (Required) The name or description of the web ACL. +* `rules` - (Required) The rules to associate with the web ACL and the settings for each rule. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the WAF WebACL. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 43d0787bdb37..e5428fb4755e 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -1080,6 +1080,21 @@ +