From 2817df5dcaa942d75f7ef894e16b18c26aa7534b Mon Sep 17 00:00:00 2001 From: Danniel Magno Date: Thu, 29 Jun 2017 13:39:51 -0300 Subject: [PATCH 1/2] Add support for resource_aws_wafregional_sql_injection_match_set --- aws/provider.go | 1 + ...aws_wafregional_sql_injection_match_set.go | 172 ++++++++++++++ ...afregional_sql_injection_match_set_test.go | 221 ++++++++++++++++++ website/aws.erb | 4 + ...onal_sql_injection_match_set.html.markdown | 40 ++++ 5 files changed, 438 insertions(+) create mode 100644 aws/resource_aws_wafregional_sql_injection_match_set.go create mode 100644 aws/resource_aws_wafregional_sql_injection_match_set_test.go create mode 100644 website/docs/r/wafregional_sql_injection_match_set.html.markdown diff --git a/aws/provider.go b/aws/provider.go index edc26a51f80..966eec408b8 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -555,6 +555,7 @@ func Provider() terraform.ResourceProvider { "aws_waf_sql_injection_match_set": resourceAwsWafSqlInjectionMatchSet(), "aws_wafregional_byte_match_set": resourceAwsWafRegionalByteMatchSet(), "aws_wafregional_ipset": resourceAwsWafRegionalIPSet(), + "aws_wafregional_sql_injection_match_set": resourceAwsWafRegionalSqlInjectionMatchSet(), "aws_wafregional_xss_match_set": resourceAwsWafRegionalXssMatchSet(), "aws_batch_compute_environment": resourceAwsBatchComputeEnvironment(), "aws_batch_job_definition": resourceAwsBatchJobDefinition(), diff --git a/aws/resource_aws_wafregional_sql_injection_match_set.go b/aws/resource_aws_wafregional_sql_injection_match_set.go new file mode 100644 index 00000000000..1f394786b64 --- /dev/null +++ b/aws/resource_aws_wafregional_sql_injection_match_set.go @@ -0,0 +1,172 @@ +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 resourceAwsWafRegionalSqlInjectionMatchSet() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsWafRegionalSqlInjectionMatchSetCreate, + Read: resourceAwsWafRegionalSqlInjectionMatchSetRead, + Update: resourceAwsWafRegionalSqlInjectionMatchSetUpdate, + Delete: resourceAwsWafRegionalSqlInjectionMatchSetDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "sql_injection_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, + }, + }, + }, + }, + "text_transformation": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceAwsWafRegionalSqlInjectionMatchSetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + region := meta.(*AWSClient).region + + log.Printf("[INFO] Creating SqlInjectionMatchSet: %s", d.Get("name").(string)) + + wr := newWafRegionalRetryer(conn, region) + out, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + params := &waf.CreateSqlInjectionMatchSetInput{ + ChangeToken: token, + Name: aws.String(d.Get("name").(string)), + } + + return conn.CreateSqlInjectionMatchSet(params) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error creating SqlInjectionMatchSet: {{err}}", err) + } + resp := out.(*waf.CreateSqlInjectionMatchSetOutput) + d.SetId(*resp.SqlInjectionMatchSet.SqlInjectionMatchSetId) + + return resourceAwsWafRegionalSqlInjectionMatchSetUpdate(d, meta) +} + +func resourceAwsWafRegionalSqlInjectionMatchSetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + log.Printf("[INFO] Reading SqlInjectionMatchSet: %s", d.Get("name").(string)) + params := &waf.GetSqlInjectionMatchSetInput{ + SqlInjectionMatchSetId: aws.String(d.Id()), + } + + resp, err := conn.GetSqlInjectionMatchSet(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.SqlInjectionMatchSet.Name) + + return nil +} + +func resourceAwsWafRegionalSqlInjectionMatchSetUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Updating SqlInjectionMatchSet: %s", d.Get("name").(string)) + err := updateSqlInjectionMatchSetResourceWR(d, meta, waf.ChangeActionInsert) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + } + return resourceAwsWafRegionalSqlInjectionMatchSetRead(d, meta) +} + +func resourceAwsWafRegionalSqlInjectionMatchSetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).wafregionalconn + region := meta.(*AWSClient).region + + log.Printf("[INFO] Deleting SqlInjectionMatchSet: %s", d.Get("name").(string)) + err := updateSqlInjectionMatchSetResourceWR(d, meta, waf.ChangeActionDelete) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + } + + wr := newWafRegionalRetryer(conn, region) + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.DeleteSqlInjectionMatchSetInput{ + ChangeToken: token, + SqlInjectionMatchSetId: aws.String(d.Id()), + } + + return conn.DeleteSqlInjectionMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + } + + return nil +} + +func updateSqlInjectionMatchSetResourceWR(d *schema.ResourceData, meta interface{}, ChangeAction string) error { + conn := meta.(*AWSClient).wafregionalconn + region := meta.(*AWSClient).region + + wr := newWafRegionalRetryer(conn, region) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateSqlInjectionMatchSetInput{ + ChangeToken: token, + SqlInjectionMatchSetId: aws.String(d.Id()), + } + + sqlInjectionMatchTuples := d.Get("sql_injection_match_tuples").(*schema.Set) + for _, sqlInjectionMatchTuple := range sqlInjectionMatchTuples.List() { + simt := sqlInjectionMatchTuple.(map[string]interface{}) + sizeConstraintUpdate := &waf.SqlInjectionMatchSetUpdate{ + Action: aws.String(ChangeAction), + SqlInjectionMatchTuple: &waf.SqlInjectionMatchTuple{ + FieldToMatch: expandFieldToMatch(simt["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), + TextTransformation: aws.String(simt["text_transformation"].(string)), + }, + } + req.Updates = append(req.Updates, sizeConstraintUpdate) + } + + return conn.UpdateSqlInjectionMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + } + + return nil +} diff --git a/aws/resource_aws_wafregional_sql_injection_match_set_test.go b/aws/resource_aws_wafregional_sql_injection_match_set_test.go new file mode 100644 index 00000000000..fca562386b3 --- /dev/null +++ b/aws/resource_aws_wafregional_sql_injection_match_set_test.go @@ -0,0 +1,221 @@ +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 TestAccAWSWafRegionalSqlInjectionMatchSet_basic(t *testing.T) { + var v waf.SqlInjectionMatchSet + sqlInjectionMatchSet := fmt.Sprintf("sqlInjectionMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig(sqlInjectionMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &v), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSet), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalSqlInjectionMatchSet_changeNameForceNew(t *testing.T) { + var before, after waf.SqlInjectionMatchSet + sqlInjectionMatchSet := fmt.Sprintf("sqlInjectionMatchSet-%s", acctest.RandString(5)) + sqlInjectionMatchSetNewName := fmt.Sprintf("sqlInjectionMatchSetNewName-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig(sqlInjectionMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &before), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSet), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + ), + }, + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfigChangeName(sqlInjectionMatchSetNewName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &after), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSetNewName), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalSqlInjectionMatchSet_disappears(t *testing.T) { + var v waf.SqlInjectionMatchSet + sqlInjectionMatchSet := fmt.Sprintf("sqlInjectionMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig(sqlInjectionMatchSet), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &v), + testAccCheckAWSWafRegionalSqlInjectionMatchSetDisappears(&v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSWafRegionalSqlInjectionMatchSetDisappears(v *waf.SqlInjectionMatchSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + region := testAccProvider.Meta().(*AWSClient).region + + wr := newWafRegionalRetryer(conn, region) + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { + req := &waf.UpdateSqlInjectionMatchSetInput{ + ChangeToken: token, + SqlInjectionMatchSetId: v.SqlInjectionMatchSetId, + } + + for _, sqlInjectionMatchTuple := range v.SqlInjectionMatchTuples { + sqlInjectionMatchTupleUpdate := &waf.SqlInjectionMatchSetUpdate{ + Action: aws.String("DELETE"), + SqlInjectionMatchTuple: &waf.SqlInjectionMatchTuple{ + FieldToMatch: sqlInjectionMatchTuple.FieldToMatch, + TextTransformation: sqlInjectionMatchTuple.TextTransformation, + }, + } + req.Updates = append(req.Updates, sqlInjectionMatchTupleUpdate) + } + return conn.UpdateSqlInjectionMatchSet(req) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + } + + _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + opts := &waf.DeleteSqlInjectionMatchSetInput{ + ChangeToken: token, + SqlInjectionMatchSetId: v.SqlInjectionMatchSetId, + } + return conn.DeleteSqlInjectionMatchSet(opts) + }) + if err != nil { + return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + } + return nil + } +} + +func testAccCheckAWSWafRegionalSqlInjectionMatchSetExists(n string, v *waf.SqlInjectionMatchSet) 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 SqlInjectionMatchSet ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).wafregionalconn + resp, err := conn.GetSqlInjectionMatchSet(&waf.GetSqlInjectionMatchSetInput{ + SqlInjectionMatchSetId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if *resp.SqlInjectionMatchSet.SqlInjectionMatchSetId == rs.Primary.ID { + *v = *resp.SqlInjectionMatchSet + return nil + } + + return fmt.Errorf("WAF SqlInjectionMatchSet (%s) not found", rs.Primary.ID) + } +} + +func testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy(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.GetSqlInjectionMatchSet( + &waf.GetSqlInjectionMatchSetInput{ + SqlInjectionMatchSetId: aws.String(rs.Primary.ID), + }) + + if err == nil { + if *resp.SqlInjectionMatchSet.SqlInjectionMatchSetId == rs.Primary.ID { + return fmt.Errorf("WAF SqlInjectionMatchSet %s still exists", rs.Primary.ID) + } + } + + // Return nil if the SqlInjectionMatchSet is already destroyed + if awsErr, ok := err.(awserr.Error); ok { + if awsErr.Code() == "WAFNonexistentItemException" { + return nil + } + } + + return err + } + + return nil +} + +func testAccAWSWafRegionalSqlInjectionMatchSetConfig(name string) string { + return fmt.Sprintf(` +resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { + name = "%s" + sql_injection_match_tuples { + text_transformation = "URL_DECODE" + field_to_match { + type = "QUERY_STRING" + } + } +}`, name) +} + +func testAccAWSWafRegionalSqlInjectionMatchSetConfigChangeName(name string) string { + return fmt.Sprintf(` +resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { + name = "%s" + sql_injection_match_tuples { + text_transformation = "URL_DECODE" + field_to_match { + type = "QUERY_STRING" + } + } +}`, name) +} diff --git a/website/aws.erb b/website/aws.erb index d4822a87659..f372acace12 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1511,6 +1511,10 @@ aws_wafregional_ipset + > + aws_wafregional_sql_injection_match_set + + > aws_wafregional_xss_match_set diff --git a/website/docs/r/wafregional_sql_injection_match_set.html.markdown b/website/docs/r/wafregional_sql_injection_match_set.html.markdown new file mode 100644 index 00000000000..18eb04365f0 --- /dev/null +++ b/website/docs/r/wafregional_sql_injection_match_set.html.markdown @@ -0,0 +1,40 @@ +--- +layout: "aws" +page_title: "AWS: wafregional_sql_injection_match_set" +sidebar_current: "docs-aws-resource-wafregional-sql-injection-match-set" +description: |- + Provides a AWS WAF Regional SqlInjectionMatchSet resource for use with ALB. +--- + +# aws\_wafregional\_sql\_injection\_match\_set + +Provides a WAF Regional SQL Injection Match Set Resource for use with Application Load Balancer. + +## Example Usage + +``` +resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { + name = "tf-sql_injection_match_set" + sql_injection_match_tuples { + text_transformation = "URL_DECODE" + field_to_match { + type = "QUERY_STRING" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name or description of the SizeConstraintSet. +* `sql_injection_match_tuples` - The parts of web requests that you want AWS WAF to inspect for malicious SQL code and, if you want AWS WAF to inspect a header, the name of the header. + +## Remarks + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the WAF SqlInjectionMatchSet. From f5ee8a92e6e60334ef7eee32900e663efe27dd48 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 14 Mar 2018 09:32:36 +0000 Subject: [PATCH 2/2] Implement feedback and clean up code --- ...aws_wafregional_sql_injection_match_set.go | 141 +++++++++++++----- ...afregional_sql_injection_match_set_test.go | 127 +++++++++++++--- ...onal_sql_injection_match_set.html.markdown | 28 +++- 3 files changed, 229 insertions(+), 67 deletions(-) diff --git a/aws/resource_aws_wafregional_sql_injection_match_set.go b/aws/resource_aws_wafregional_sql_injection_match_set.go index 1f394786b64..f7d23c9c2a6 100644 --- a/aws/resource_aws_wafregional_sql_injection_match_set.go +++ b/aws/resource_aws_wafregional_sql_injection_match_set.go @@ -1,12 +1,15 @@ package aws import ( + "bytes" + "fmt" "log" + "strings" "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/aws/aws-sdk-go/service/wafregional" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -18,18 +21,19 @@ func resourceAwsWafRegionalSqlInjectionMatchSet() *schema.Resource { Delete: resourceAwsWafRegionalSqlInjectionMatchSetDelete, Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "sql_injection_match_tuples": &schema.Schema{ + "sql_injection_match_tuple": { Type: schema.TypeSet, Optional: true, + Set: resourceAwsWafRegionalSqlInjectionMatchSetTupleHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "field_to_match": { - Type: schema.TypeSet, + Type: schema.TypeList, Required: true, MaxItems: 1, Elem: &schema.Resource{ @@ -37,6 +41,10 @@ func resourceAwsWafRegionalSqlInjectionMatchSet() *schema.Resource { "data": { Type: schema.TypeString, Optional: true, + StateFunc: func(v interface{}) string { + value := v.(string) + return strings.ToLower(value) + }, }, "type": { Type: schema.TypeString, @@ -45,7 +53,7 @@ func resourceAwsWafRegionalSqlInjectionMatchSet() *schema.Resource { }, }, }, - "text_transformation": &schema.Schema{ + "text_transformation": { Type: schema.TypeString, Required: true, }, @@ -60,7 +68,7 @@ func resourceAwsWafRegionalSqlInjectionMatchSetCreate(d *schema.ResourceData, me conn := meta.(*AWSClient).wafregionalconn region := meta.(*AWSClient).region - log.Printf("[INFO] Creating SqlInjectionMatchSet: %s", d.Get("name").(string)) + log.Printf("[INFO] Creating Regional WAF SQL Injection Match Set: %s", d.Get("name").(string)) wr := newWafRegionalRetryer(conn, region) out, err := wr.RetryWithToken(func(token *string) (interface{}, error) { @@ -72,7 +80,7 @@ func resourceAwsWafRegionalSqlInjectionMatchSetCreate(d *schema.ResourceData, me return conn.CreateSqlInjectionMatchSet(params) }) if err != nil { - return errwrap.Wrapf("[ERROR] Error creating SqlInjectionMatchSet: {{err}}", err) + return fmt.Errorf("Failed creating Regional WAF SQL Injection Match Set: %s", err) } resp := out.(*waf.CreateSqlInjectionMatchSetOutput) d.SetId(*resp.SqlInjectionMatchSet.SqlInjectionMatchSetId) @@ -82,15 +90,15 @@ func resourceAwsWafRegionalSqlInjectionMatchSetCreate(d *schema.ResourceData, me func resourceAwsWafRegionalSqlInjectionMatchSetRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).wafregionalconn - log.Printf("[INFO] Reading SqlInjectionMatchSet: %s", d.Get("name").(string)) + log.Printf("[INFO] Reading Regional WAF SQL Injection Match Set: %s", d.Get("name").(string)) params := &waf.GetSqlInjectionMatchSetInput{ SqlInjectionMatchSetId: aws.String(d.Id()), } resp, err := conn.GetSqlInjectionMatchSet(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()) + if isAWSErr(err, wafregional.ErrCodeWAFNonexistentItemException, "") { + log.Printf("[WARN] Regional WAF SQL Injection Match Set (%s) not found, error code (404)", d.Id()) d.SetId("") return nil } @@ -99,16 +107,25 @@ func resourceAwsWafRegionalSqlInjectionMatchSetRead(d *schema.ResourceData, meta } d.Set("name", resp.SqlInjectionMatchSet.Name) + d.Set("sql_injection_match_tuple", flattenWafSqlInjectionMatchTuples(resp.SqlInjectionMatchSet.SqlInjectionMatchTuples)) return nil } func resourceAwsWafRegionalSqlInjectionMatchSetUpdate(d *schema.ResourceData, meta interface{}) error { - log.Printf("[INFO] Updating SqlInjectionMatchSet: %s", d.Get("name").(string)) - err := updateSqlInjectionMatchSetResourceWR(d, meta, waf.ChangeActionInsert) - if err != nil { - return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + conn := meta.(*AWSClient).wafregionalconn + region := meta.(*AWSClient).region + + if d.HasChange("sql_injection_match_tuple") { + o, n := d.GetChange("sql_injection_match_tuple") + oldT, newT := o.(*schema.Set).List(), n.(*schema.Set).List() + + err := updateSqlInjectionMatchSetResourceWR(d.Id(), oldT, newT, conn, region) + if err != nil { + return fmt.Errorf("[ERROR] Error updating Regional WAF SQL Injection Match Set: %s", err) + } } + return resourceAwsWafRegionalSqlInjectionMatchSetRead(d, meta) } @@ -116,14 +133,18 @@ func resourceAwsWafRegionalSqlInjectionMatchSetDelete(d *schema.ResourceData, me conn := meta.(*AWSClient).wafregionalconn region := meta.(*AWSClient).region - log.Printf("[INFO] Deleting SqlInjectionMatchSet: %s", d.Get("name").(string)) - err := updateSqlInjectionMatchSetResourceWR(d, meta, waf.ChangeActionDelete) - if err != nil { - return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + oldTuples := d.Get("sql_injection_match_tuple").(*schema.Set).List() + + if len(oldTuples) > 0 { + noTuples := []interface{}{} + err := updateSqlInjectionMatchSetResourceWR(d.Id(), oldTuples, noTuples, conn, region) + if err != nil { + return fmt.Errorf("[ERROR] Error deleting Regional WAF SQL Injection Match Set: %s", err) + } } wr := newWafRegionalRetryer(conn, region) - _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { + _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.DeleteSqlInjectionMatchSetInput{ ChangeToken: token, SqlInjectionMatchSetId: aws.String(d.Id()), @@ -132,41 +153,81 @@ func resourceAwsWafRegionalSqlInjectionMatchSetDelete(d *schema.ResourceData, me return conn.DeleteSqlInjectionMatchSet(req) }) if err != nil { - return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + return fmt.Errorf("Failed deleting Regional WAF SQL Injection Match Set: %s", err) } return nil } -func updateSqlInjectionMatchSetResourceWR(d *schema.ResourceData, meta interface{}, ChangeAction string) error { - conn := meta.(*AWSClient).wafregionalconn - region := meta.(*AWSClient).region - +func updateSqlInjectionMatchSetResourceWR(id string, oldT, newT []interface{}, conn *wafregional.WAFRegional, region string) error { wr := newWafRegionalRetryer(conn, region) _, err := wr.RetryWithToken(func(token *string) (interface{}, error) { req := &waf.UpdateSqlInjectionMatchSetInput{ ChangeToken: token, - SqlInjectionMatchSetId: aws.String(d.Id()), - } - - sqlInjectionMatchTuples := d.Get("sql_injection_match_tuples").(*schema.Set) - for _, sqlInjectionMatchTuple := range sqlInjectionMatchTuples.List() { - simt := sqlInjectionMatchTuple.(map[string]interface{}) - sizeConstraintUpdate := &waf.SqlInjectionMatchSetUpdate{ - Action: aws.String(ChangeAction), - SqlInjectionMatchTuple: &waf.SqlInjectionMatchTuple{ - FieldToMatch: expandFieldToMatch(simt["field_to_match"].(*schema.Set).List()[0].(map[string]interface{})), - TextTransformation: aws.String(simt["text_transformation"].(string)), - }, - } - req.Updates = append(req.Updates, sizeConstraintUpdate) + SqlInjectionMatchSetId: aws.String(id), + Updates: diffWafSqlInjectionMatchTuplesWR(oldT, newT), } + log.Printf("[INFO] Updating Regional WAF SQL Injection Match Set: %s", req) return conn.UpdateSqlInjectionMatchSet(req) }) if err != nil { - return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + return fmt.Errorf("Failed updating Regional WAF SQL Injection Match Set: %s", err) } return nil } + +func diffWafSqlInjectionMatchTuplesWR(oldT, newT []interface{}) []*waf.SqlInjectionMatchSetUpdate { + updates := make([]*waf.SqlInjectionMatchSetUpdate, 0) + + for _, od := range oldT { + tuple := od.(map[string]interface{}) + + if idx, contains := sliceContainsMap(newT, tuple); contains { + newT = append(newT[:idx], newT[idx+1:]...) + continue + } + + ftm := tuple["field_to_match"].([]interface{}) + + updates = append(updates, &waf.SqlInjectionMatchSetUpdate{ + Action: aws.String(waf.ChangeActionDelete), + SqlInjectionMatchTuple: &waf.SqlInjectionMatchTuple{ + FieldToMatch: expandFieldToMatch(ftm[0].(map[string]interface{})), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + + for _, nd := range newT { + tuple := nd.(map[string]interface{}) + ftm := tuple["field_to_match"].([]interface{}) + + updates = append(updates, &waf.SqlInjectionMatchSetUpdate{ + Action: aws.String(waf.ChangeActionInsert), + SqlInjectionMatchTuple: &waf.SqlInjectionMatchTuple{ + FieldToMatch: expandFieldToMatch(ftm[0].(map[string]interface{})), + TextTransformation: aws.String(tuple["text_transformation"].(string)), + }, + }) + } + return updates +} + +func resourceAwsWafRegionalSqlInjectionMatchSetTupleHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if v, ok := m["field_to_match"]; ok { + ftms := v.([]interface{}) + ftm := ftms[0].(map[string]interface{}) + + if v, ok := ftm["data"]; ok { + buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(v.(string)))) + } + buf.WriteString(fmt.Sprintf("%s-", ftm["type"].(string))) + } + buf.WriteString(fmt.Sprintf("%s-", m["text_transformation"].(string))) + + return hashcode.String(buf.String()) +} diff --git a/aws/resource_aws_wafregional_sql_injection_match_set_test.go b/aws/resource_aws_wafregional_sql_injection_match_set_test.go index fca562386b3..068ab04cf2d 100644 --- a/aws/resource_aws_wafregional_sql_injection_match_set_test.go +++ b/aws/resource_aws_wafregional_sql_injection_match_set_test.go @@ -4,14 +4,12 @@ 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/aws/aws-sdk-go/service/wafregional" "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) func TestAccAWSWafRegionalSqlInjectionMatchSet_basic(t *testing.T) { @@ -30,7 +28,15 @@ func TestAccAWSWafRegionalSqlInjectionMatchSet_basic(t *testing.T) { resource.TestCheckResourceAttr( "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSet), resource.TestCheckResourceAttr( - "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.0.data", ""), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.0.type", "QUERY_STRING"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.text_transformation", "URL_DECODE"), ), }, }, @@ -54,17 +60,17 @@ func TestAccAWSWafRegionalSqlInjectionMatchSet_changeNameForceNew(t *testing.T) resource.TestCheckResourceAttr( "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSet), resource.TestCheckResourceAttr( - "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "1"), ), }, { - Config: testAccAWSWafRegionalSqlInjectionMatchSetConfigChangeName(sqlInjectionMatchSetNewName), + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig(sqlInjectionMatchSetNewName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &after), resource.TestCheckResourceAttr( "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", sqlInjectionMatchSetNewName), resource.TestCheckResourceAttr( - "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuples.#", "1"), + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "1"), ), }, }, @@ -92,6 +98,78 @@ func TestAccAWSWafRegionalSqlInjectionMatchSet_disappears(t *testing.T) { }) } +func TestAccAWSWafRegionalSqlInjectionMatchSet_changeTuples(t *testing.T) { + var before, after waf.SqlInjectionMatchSet + setName := fmt.Sprintf("sqlInjectionMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &before), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.0.data", ""), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.field_to_match.0.type", "QUERY_STRING"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.1913782288.text_transformation", "URL_DECODE"), + ), + }, + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig_changeTuples(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &after), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.3961339938.field_to_match.#", "1"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.3961339938.field_to_match.0.data", "user-agent"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.3961339938.field_to_match.0.type", "HEADER"), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.3961339938.text_transformation", "NONE"), + ), + }, + }, + }) +} + +func TestAccAWSWafRegionalSqlInjectionMatchSet_noTuples(t *testing.T) { + var ipset waf.SqlInjectionMatchSet + setName := fmt.Sprintf("sqlInjectionMatchSet-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafRegionalSqlInjectionMatchSetConfig_noTuples(setName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSWafRegionalSqlInjectionMatchSetExists("aws_wafregional_sql_injection_match_set.sql_injection_match_set", &ipset), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "name", setName), + resource.TestCheckResourceAttr( + "aws_wafregional_sql_injection_match_set.sql_injection_match_set", "sql_injection_match_tuple.#", "0"), + ), + }, + }, + }) +} + func testAccCheckAWSWafRegionalSqlInjectionMatchSetDisappears(v *waf.SqlInjectionMatchSet) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).wafregionalconn @@ -117,7 +195,7 @@ func testAccCheckAWSWafRegionalSqlInjectionMatchSetDisappears(v *waf.SqlInjectio return conn.UpdateSqlInjectionMatchSet(req) }) if err != nil { - return errwrap.Wrapf("[ERROR] Error updating SqlInjectionMatchSet: {{err}}", err) + return fmt.Errorf("Failed updating SQL Injection Match Set: %s", err) } _, err = wr.RetryWithToken(func(token *string) (interface{}, error) { @@ -128,7 +206,7 @@ func testAccCheckAWSWafRegionalSqlInjectionMatchSetDisappears(v *waf.SqlInjectio return conn.DeleteSqlInjectionMatchSet(opts) }) if err != nil { - return errwrap.Wrapf("[ERROR] Error deleting SqlInjectionMatchSet: {{err}}", err) + return fmt.Errorf("Failed deleting SQL Injection Match Set: %s", err) } return nil } @@ -149,7 +227,6 @@ func testAccCheckAWSWafRegionalSqlInjectionMatchSetExists(n string, v *waf.SqlIn resp, err := conn.GetSqlInjectionMatchSet(&waf.GetSqlInjectionMatchSetInput{ SqlInjectionMatchSetId: aws.String(rs.Primary.ID), }) - if err != nil { return err } @@ -165,7 +242,7 @@ func testAccCheckAWSWafRegionalSqlInjectionMatchSetExists(n string, v *waf.SqlIn func testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_wafregional_byte_match_set" { + if rs.Type != "aws_wafregional_sql_injection_match_set" { continue } @@ -182,10 +259,8 @@ func testAccCheckAWSWafRegionalSqlInjectionMatchSetDestroy(s *terraform.State) e } // Return nil if the SqlInjectionMatchSet is already destroyed - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == "WAFNonexistentItemException" { - return nil - } + if isAWSErr(err, wafregional.ErrCodeWAFNonexistentItemException, "") { + return nil } return err @@ -198,7 +273,7 @@ func testAccAWSWafRegionalSqlInjectionMatchSetConfig(name string) string { return fmt.Sprintf(` resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { name = "%s" - sql_injection_match_tuples { + sql_injection_match_tuple { text_transformation = "URL_DECODE" field_to_match { type = "QUERY_STRING" @@ -207,15 +282,23 @@ resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { }`, name) } -func testAccAWSWafRegionalSqlInjectionMatchSetConfigChangeName(name string) string { +func testAccAWSWafRegionalSqlInjectionMatchSetConfig_changeTuples(name string) string { return fmt.Sprintf(` resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { name = "%s" - sql_injection_match_tuples { - text_transformation = "URL_DECODE" + sql_injection_match_tuple { + text_transformation = "NONE" field_to_match { - type = "QUERY_STRING" + type = "HEADER" + data = "User-Agent" } } }`, name) } + +func testAccAWSWafRegionalSqlInjectionMatchSetConfig_noTuples(name string) string { + return fmt.Sprintf(` +resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { + name = "%s" +}`, name) +} diff --git a/website/docs/r/wafregional_sql_injection_match_set.html.markdown b/website/docs/r/wafregional_sql_injection_match_set.html.markdown index 18eb04365f0..abb46dcf0c4 100644 --- a/website/docs/r/wafregional_sql_injection_match_set.html.markdown +++ b/website/docs/r/wafregional_sql_injection_match_set.html.markdown @@ -6,16 +6,16 @@ description: |- Provides a AWS WAF Regional SqlInjectionMatchSet resource for use with ALB. --- -# aws\_wafregional\_sql\_injection\_match\_set +# aws_wafregional_sql_injection_match_set Provides a WAF Regional SQL Injection Match Set Resource for use with Application Load Balancer. ## Example Usage -``` +```hcl resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { name = "tf-sql_injection_match_set" - sql_injection_match_tuples { + sql_injection_match_tuple { text_transformation = "URL_DECODE" field_to_match { type = "QUERY_STRING" @@ -29,9 +29,27 @@ resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { The following arguments are supported: * `name` - (Required) The name or description of the SizeConstraintSet. -* `sql_injection_match_tuples` - The parts of web requests that you want AWS WAF to inspect for malicious SQL code and, if you want AWS WAF to inspect a header, the name of the header. +* `sql_injection_match_tuple` - (Optional) The parts of web requests that you want AWS WAF to inspect for malicious SQL code and, if you want AWS WAF to inspect a header, the name of the header. + +### Nested fields + +### `sql_injection_match_tuple` + +* `field_to_match` - (Required) Specifies where in a web request to look for snippets of malicious SQL code. +* `text_transformation` - (Required) Text transformations used to eliminate unusual formatting that attackers use in web requests in an effort to bypass AWS WAF. + If you specify a transformation, AWS WAF performs the transformation on `field_to_match` before inspecting a request for a match. + e.g. `CMD_LINE`, `HTML_ENTITY_DECODE` or `NONE`. + See [docs](https://docs.aws.amazon.com/waf/latest/APIReference/API_regional_SqlInjectionMatchTuple.html#WAF-Type-regional_SqlInjectionMatchTuple-TextTransformation) + for all supported values. + +### `field_to_match` -## Remarks +* `data` - (Optional) When `type` is `HEADER`, enter the name of the header that you want to search, e.g. `User-Agent` or `Referer`. + If `type` is any other value, omit this field. +* `type` - (Required) The part of the web request that you want AWS WAF to search for a specified string. + e.g. `HEADER`, `METHOD` or `BODY`. + See [docs](https://docs.aws.amazon.com/waf/latest/APIReference/API_regional_FieldToMatch.html) + for all supported values. ## Attributes Reference