From f927c23e9838e44eb11ac1390f97c9ea1e8d89e6 Mon Sep 17 00:00:00 2001 From: Argishti Rostamian <1332785+WhileLoop@users.noreply.github.com> Date: Mon, 11 Feb 2019 12:19:43 -0800 Subject: [PATCH 1/2] add waf web acl logging config --- aws/resource_aws_waf_web_acl.go | 195 +++++++++++++++++++++++ aws/resource_aws_waf_web_acl_test.go | 163 +++++++++++++++++++ website/docs/r/waf_web_acl.html.markdown | 22 +++ 3 files changed, 380 insertions(+) diff --git a/aws/resource_aws_waf_web_acl.go b/aws/resource_aws_waf_web_acl.go index 03016238607..e41fd1b0892 100644 --- a/aws/resource_aws_waf_web_acl.go +++ b/aws/resource_aws_waf_web_acl.go @@ -5,6 +5,7 @@ import ( "log" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/waf" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -21,6 +22,10 @@ func resourceAwsWafWebAcl() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "name": { Type: schema.TypeString, Required: true, @@ -45,6 +50,44 @@ func resourceAwsWafWebAcl() *schema.Resource { ForceNew: true, ValidateFunc: validateWafMetricName, }, + "logging_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_destination": { + Type: schema.TypeString, + Required: true, + }, + "redacted_fields": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_to_match": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "data": { + Type: schema.TypeString, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, "rules": { Type: schema.TypeSet, Optional: true, @@ -120,6 +163,16 @@ func resourceAwsWafWebAclCreate(d *schema.ResourceData, meta interface{}) error } resp := out.(*waf.CreateWebACLOutput) d.SetId(*resp.WebACL.WebACLId) + + arn := arn.ARN{ + Partition: meta.(*AWSClient).partition, + Service: "waf", + AccountID: meta.(*AWSClient).accountid, + Resource: fmt.Sprintf("webacl/%s", d.Id()), + }.String() + + // Set for update + d.Set("arn", arn) return resourceAwsWafWebAclUpdate(d, meta) } @@ -146,6 +199,14 @@ func resourceAwsWafWebAclRead(d *schema.ResourceData, meta interface{}) error { return nil } + arn := arn.ARN{ + Partition: meta.(*AWSClient).partition, + Service: "waf", + AccountID: meta.(*AWSClient).accountid, + Resource: fmt.Sprintf("webacl/%s", d.Id()), + }.String() + d.Set("arn", arn) + if err := d.Set("default_action", flattenWafAction(resp.WebACL.DefaultAction)); err != nil { return fmt.Errorf("error setting default_action: %s", err) } @@ -155,6 +216,26 @@ func resourceAwsWafWebAclRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting rules: %s", err) } + getLoggingConfigurationInput := &waf.GetLoggingConfigurationInput{ + ResourceArn: aws.String(d.Get("arn").(string)), + } + loggingConfiguration := []interface{}{} + + log.Printf("[DEBUG] Getting WAF Web ACL (%s) Logging Configuration: %s", d.Id(), getLoggingConfigurationInput) + getLoggingConfigurationOutput, err := conn.GetLoggingConfiguration(getLoggingConfigurationInput) + + if err != nil && !isAWSErr(err, waf.ErrCodeNonexistentItemException, "") { + return fmt.Errorf("error getting WAF Web ACL (%s) Logging Configuration: %s", d.Id(), err) + } + + if getLoggingConfigurationOutput != nil { + loggingConfiguration = flattenWAFLoggingConfiguration(getLoggingConfigurationOutput.LoggingConfiguration) + } + + if err := d.Set("logging_configuration", loggingConfiguration); err != nil { + return fmt.Errorf("error setting logging_configuration: %s", err) + } + return nil } @@ -180,6 +261,31 @@ func resourceAwsWafWebAclUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("logging_configuration") { + loggingConfiguration := d.Get("logging_configuration").([]interface{}) + + if len(loggingConfiguration) == 1 { + input := &waf.PutLoggingConfigurationInput{ + LoggingConfiguration: expandWAFLoggingConfiguration(loggingConfiguration, d.Get("arn").(string)), + } + + log.Printf("[DEBUG] Updating WAF Web ACL (%s) Logging Configuration: %s", d.Id(), input) + if _, err := conn.PutLoggingConfiguration(input); err != nil { + return fmt.Errorf("error updating WAF Web ACL (%s) Logging Configuration: %s", d.Id(), err) + } + } else { + input := &waf.DeleteLoggingConfigurationInput{ + ResourceArn: aws.String(d.Get("arn").(string)), + } + + log.Printf("[DEBUG] Deleting WAF Web ACL (%s) Logging Configuration: %s", d.Id(), input) + if _, err := conn.DeleteLoggingConfiguration(input); err != nil { + return fmt.Errorf("error deleting WAF Web ACL (%s) Logging Configuration: %s", d.Id(), err) + } + } + + } + return resourceAwsWafWebAclRead(d, meta) } @@ -219,3 +325,92 @@ func resourceAwsWafWebAclDelete(d *schema.ResourceData, meta interface{}) error } return nil } + +func expandWAFLoggingConfiguration(l []interface{}, resourceARN string) *waf.LoggingConfiguration { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + loggingConfiguration := &waf.LoggingConfiguration{ + LogDestinationConfigs: []*string{ + aws.String(m["log_destination"].(string)), + }, + RedactedFields: expandWAFRedactedFields(m["redacted_fields"].([]interface{})), + ResourceArn: aws.String(resourceARN), + } + + return loggingConfiguration +} + +func expandWAFRedactedFields(l []interface{}) []*waf.FieldToMatch { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + if m["field_to_match"] == nil { + return nil + } + + redactedFields := make([]*waf.FieldToMatch, 0) + + for _, fieldToMatch := range m["field_to_match"].(*schema.Set).List() { + if fieldToMatch == nil { + continue + } + + redactedFields = append(redactedFields, expandFieldToMatch(fieldToMatch.(map[string]interface{}))) + } + + return redactedFields +} + +func flattenWAFLoggingConfiguration(loggingConfiguration *waf.LoggingConfiguration) []interface{} { + if loggingConfiguration == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "log_destination": "", + "redacted_fields": flattenWAFRedactedFields(loggingConfiguration.RedactedFields), + } + + if len(loggingConfiguration.LogDestinationConfigs) > 0 { + m["log_destination"] = aws.StringValue(loggingConfiguration.LogDestinationConfigs[0]) + } + + return []interface{}{m} +} + +func flattenWAFRedactedFields(fieldToMatches []*waf.FieldToMatch) []interface{} { + if len(fieldToMatches) == 0 { + return []interface{}{} + } + + fieldToMatchResource := &schema.Resource{ + Schema: map[string]*schema.Schema{ + "data": { + Type: schema.TypeString, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + }, + }, + } + l := make([]interface{}, len(fieldToMatches)) + + for i, fieldToMatch := range fieldToMatches { + l[i] = flattenFieldToMatch(fieldToMatch)[0] + } + + m := map[string]interface{}{ + "field_to_match": schema.NewSet(schema.HashResource(fieldToMatchResource), l), + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_waf_web_acl_test.go b/aws/resource_aws_waf_web_acl_test.go index 6db996e2981..21103e09a21 100644 --- a/aws/resource_aws_waf_web_acl_test.go +++ b/aws/resource_aws_waf_web_acl_test.go @@ -162,6 +162,52 @@ func TestAccAWSWafWebAcl_Rules(t *testing.T) { }) } +func TestAccAWSWafWebAcl_LoggingConfiguration(t *testing.T) { + var webACL waf.WebACL + rName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) + resourceName := "aws_waf_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSWafWebAclDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSWafWebAclConfig_Logging(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafWebAclExists(resourceName, &webACL), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.#", "1"), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.0.field_to_match.#", "4"), + ), + }, + // Test resource import + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + // Test logging configuration update + { + Config: testAccAWSWafWebAclConfig_LoggingUpdate(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafWebAclExists(resourceName, &webACL), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.#", "0"), + ), + }, + // Test logging configuration removal + { + Config: testAccAWSWafWebAclConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSWafWebAclExists(resourceName, &webACL), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "0"), + ), + }, + }, + }) +} + func TestAccAWSWafWebAcl_disappears(t *testing.T) { var webACL waf.WebACL rName := fmt.Sprintf("wafacl%s", acctest.RandString(5)) @@ -414,3 +460,120 @@ resource "aws_waf_web_acl" "test" { } `, rName, rName, rName, rName, rName, rName, rName) } + +func testAccAWSWafWebAclConfig_Logging(rName string) string { + return fmt.Sprintf(` +resource "aws_waf_web_acl" "waf_acl" { + name = %[1]q + metric_name = %[1]q + + default_action { + type = "ALLOW" + } + + logging_configuration { + + log_destination = "${aws_kinesis_firehose_delivery_stream.test_stream.arn}" + + redacted_fields { + field_to_match { + type = "URI" + } + field_to_match { + data = "referer" + type = "HEADER" + } + } + + } +} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + acl = "private" +} + +resource "aws_iam_role" "test" { + name = %[1]q + assume_role_policy = < *NOTE:* The Kinesis Firehose Delivery Stream name must begin with `aws-waf-logs-`. See the [AWS WAF Developer Guide](https://docs.aws.amazon.com/waf/latest/developerguide/logging.html) for more information about enabling WAF logging. + +```hcl +resource "aws_waf_web_acl" "example" { + # ... other configuration ... + logging_configuration { + log_destination = "${aws_kinesis_firehose_delivery_stream.example.arn}" + redacted_fields { + field_to_match { + type = "URI" + } + field_to_match { + data = "referer" + type = "HEADER" + } + } + } +} +``` + ## Argument Reference The following arguments are supported: From 8c08d063bceecfbc8ef482b365e0e3844bfd8ad6 Mon Sep 17 00:00:00 2001 From: Argishti Rostamian Date: Tue, 12 Feb 2019 13:19:17 -0800 Subject: [PATCH 2/2] fix tests --- aws/resource_aws_waf_web_acl_test.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_waf_web_acl_test.go b/aws/resource_aws_waf_web_acl_test.go index 21103e09a21..2769f2c266c 100644 --- a/aws/resource_aws_waf_web_acl_test.go +++ b/aws/resource_aws_waf_web_acl_test.go @@ -178,7 +178,7 @@ func TestAccAWSWafWebAcl_LoggingConfiguration(t *testing.T) { testAccCheckAWSWafWebAclExists(resourceName, &webACL), resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.#", "1"), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.0.field_to_match.#", "4"), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.redacted_fields.0.field_to_match.#", "2"), ), }, // Test resource import @@ -463,7 +463,7 @@ resource "aws_waf_web_acl" "test" { func testAccAWSWafWebAclConfig_Logging(rName string) string { return fmt.Sprintf(` -resource "aws_waf_web_acl" "waf_acl" { +resource "aws_waf_web_acl" "test" { name = %[1]q metric_name = %[1]q @@ -473,12 +473,14 @@ resource "aws_waf_web_acl" "waf_acl" { logging_configuration { - log_destination = "${aws_kinesis_firehose_delivery_stream.test_stream.arn}" + log_destination = "${aws_kinesis_firehose_delivery_stream.test.arn}" redacted_fields { + field_to_match { type = "URI" } + field_to_match { data = "referer" type = "HEADER" @@ -536,15 +538,16 @@ resource "aws_waf_web_acl" "test" { } logging_configuration { - log_destination = "${aws_kinesis_firehose_delivery_stream.test_stream.arn}" + log_destination = "${aws_kinesis_firehose_delivery_stream.test.arn}" } } -resource "aws_s3_bucket" "bucket" { + +resource "aws_s3_bucket" "test" { bucket = %q acl = "private" } -resource "aws_iam_role" "firehose_role" { +resource "aws_iam_role" "test" { name = %q assume_role_policy = <