From 76199fe7f9f80c508e86393afbdbe754ecb07efb Mon Sep 17 00:00:00 2001 From: ChengXiangdong Date: Thu, 8 Jul 2021 15:05:12 +0800 Subject: [PATCH] Add WAF policy management support #1257 --- docs/resources/waf_policy.md | 90 +++++++ huaweicloud/provider.go | 2 + .../resource_huaweicloud_waf_domain_test.go | 52 ++++ .../resource_huaweicloud_waf_policy_test.go | 148 +++++++++++ .../waf/resource_huaweicloud_waf_policy.go | 230 ++++++++++++++++++ 5 files changed, 522 insertions(+) create mode 100644 docs/resources/waf_policy.md create mode 100644 huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_policy_test.go create mode 100644 huaweicloud/services/waf/resource_huaweicloud_waf_policy.go diff --git a/docs/resources/waf_policy.md b/docs/resources/waf_policy.md new file mode 100644 index 00000000000..9329b4896eb --- /dev/null +++ b/docs/resources/waf_policy.md @@ -0,0 +1,90 @@ +--- +subcategory: "Web Application Firewall (WAF)" +--- + +# huaweicloud_waf_policy + +Manages a WAF policy resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_waf_policy" "policy_1" { + name = "policy_1" + protection_mode = "log" + level = 2 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) The region in which to create the WAF policy resource. + If omitted, the provider-level region will be used. + Changing this setting will push a new certificate. + +* `name` - (Required, String) Specifies the policy name. The maximum length is 256 characters. Only digits, letters, + underscores(_), and hyphens(-) are allowed. + +* `protection_mode` - (Optional, String) Specifies the protective action after a rule is matched. Defaults to `log`. + Valid values are: + * `block`: WAF blocks and logs detected attacks. + * `log`: WAF logs detected attacks only. + +* `level` - (Optional, Int) Specifies the protection level. Defaults to 2. Valid values are: + * `1`: low + * `2`: medium + * `3`: high + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The policy ID in UUID format. + +* `full_detection` - The detection mode in Precise Protection. + * `true`: full detection, Full detection finishes all threat detections before blocking requests that meet Precise + Protection specified conditions. + * `false`: instant detection. Instant detection immediately ends threat detection after blocking a request that meets + Precise Protection specified conditions. + +* `options` - The protection switches. The options object structure is documented below. + +The `options` block supports: + +* `basic_web_protection` - Indicates whether Basic Web Protection is enabled. + +* `general_check` - Indicates whether General Check in Basic Web Protection is enabled. + +* `crawler` - Indicates whether the master crawler detection switch in Basic Web Protection is enabled. + +* `crawler_engine` - Indicates whether the Search Engine switch in Basic Web Protection is enabled. + +* `crawler_scanner` - Indicates whether the Scanner switch in Basic Web Protection is enabled. + +* `crawler_script` - Indicates whether the Script Tool switch in Basic Web Protection is enabled. + +* `crawler_other` - Indicates whether detection of other crawlers in Basic Web Protection is enabled. + +* `webshell` - Indicates whether webshell detection in Basic Web Protection is enabled. + +* `cc_attack_protection` - Indicates whether CC Attack Protection is enabled. + +* `precise_protection` - Indicates whether Precise Protection is enabled. + +* `blacklist` - Indicates whether Blacklist and Whitelist is enabled. + +* `data_masking` - Indicates whether Data Masking is enabled. + +* `false_alarm_masking` - Indicates whether False Alarm Masking is enabled. + +* `web_tamper_protection` - Indicates whether Web Tamper Protection is enabled. + +## Import + +Policies can be imported using the `id`, e.g. + +```sh +terraform import huaweicloud_waf_policy.policy_2 25e1df831bea4022a6e22bebe678915a +``` diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 1b8f27434c1..626d0bcc285 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -506,6 +506,8 @@ func Provider() terraform.ResourceProvider { "huaweicloud_scm_certificate": resourceScmCertificateV3(), "huaweicloud_waf_certificate": waf.ResourceWafCertificateV1(), "huaweicloud_waf_domain": waf.ResourceWafDomainV1(), + "huaweicloud_waf_policy": waf.ResourceWafPolicyV1(), + // Legacy "huaweicloud_compute_instance_v2": ResourceComputeInstanceV2(), "huaweicloud_compute_interface_attach_v2": ResourceComputeInterfaceAttachV2(), diff --git a/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_domain_test.go b/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_domain_test.go index 7c3c8671060..9675144ec55 100644 --- a/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_domain_test.go +++ b/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_domain_test.go @@ -55,6 +55,33 @@ func TestAccWafDomainV1_basic(t *testing.T) { }) } +func TestAccWafDomainV1_policy(t *testing.T) { + var domain domains.Domain + resourceName := "huaweicloud_waf_domain.domain_1" + randName := acctest.RandString(8) + certificateName := acctest.RandString(8) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t) }, + Providers: acceptance.TestAccProviders, + CheckDestroy: testAccCheckWafDomainV1Destroy, + Steps: []resource.TestStep{ + { + Config: testAccWafDomainV1_policy(certificateName, randName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWafDomainV1Exists(resourceName, &domain), + resource.TestCheckResourceAttr(resourceName, "domain", fmt.Sprintf("www.%s.com", randName)), + resource.TestCheckResourceAttr(resourceName, "proxy", "true"), + resource.TestCheckResourceAttr(resourceName, "server.0.client_protocol", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "server.0.server_protocol", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "server.0.port", "8080"), + resource.TestCheckResourceAttrSet(resourceName, "policy_id"), + ), + }, + }, + }) +} + func testAccCheckWafDomainV1Destroy(s *terraform.State) error { config := acceptance.TestAccProvider.Meta().(*config.Config) wafClient, err := config.WafV1Client(acceptance.HW_REGION_NAME) @@ -150,3 +177,28 @@ resource "huaweicloud_waf_domain" "domain_1" { } `, testAccWafCertificateV1_conf(name), name) } + +func testAccWafDomainV1_policy(certificateName string, name string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_waf_policy" "policy_1" { + name = "policy_%s" +} + +resource "huaweicloud_waf_domain" "domain_1" { + domain = "www.%s.com" + certificate_id = huaweicloud_waf_certificate.certificate_1.id + certificate_name = huaweicloud_waf_certificate.certificate_1.name + policy_id = huaweicloud_waf_policy.policy_1.id + proxy = true + + server { + client_protocol = "HTTPS" + server_protocol = "HTTP" + address = "119.8.0.14" + port = 8080 + } +} +`, testAccWafCertificateV1_conf(certificateName), name, name) +} diff --git a/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_policy_test.go b/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_policy_test.go new file mode 100644 index 00000000000..c296912900f --- /dev/null +++ b/huaweicloud/services/acceptance/waf/resource_huaweicloud_waf_policy_test.go @@ -0,0 +1,148 @@ +package waf + +import ( + "fmt" + "testing" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + + "github.com/huaweicloud/golangsdk/openstack/waf_hw/v1/policies" +) + +func TestAccWafPolicyV1_basic(t *testing.T) { + var policy policies.Policy + randName := acctest.RandString(5) + resourceName := "huaweicloud_waf_policy.policy_1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t) }, + Providers: acceptance.TestAccProviders, + CheckDestroy: testAccCheckWafPolicyV1Destroy, + Steps: []resource.TestStep{ + { + Config: testAccWafPolicyV1_basic(randName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWafPolicyV1Exists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("policy-%s", randName)), + resource.TestCheckResourceAttr(resourceName, "protection_mode", "log"), + resource.TestCheckResourceAttr(resourceName, "level", "2"), + resource.TestCheckResourceAttr(resourceName, "full_detection", "false"), + ), + }, + }, + }) +} + +func TestAccWafPolicyV1_update(t *testing.T) { + var policy policies.Policy + randName := acctest.RandString(5) + resourceName := "huaweicloud_waf_policy.policy_1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t) }, + Providers: acceptance.TestAccProviders, + CheckDestroy: testAccCheckWafPolicyV1Destroy, + Steps: []resource.TestStep{ + { + Config: testAccWafPolicyV1_basic(randName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWafPolicyV1Exists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("policy-%s", randName)), + resource.TestCheckResourceAttr(resourceName, "protection_mode", "log"), + resource.TestCheckResourceAttr(resourceName, "level", "2"), + resource.TestCheckResourceAttr(resourceName, "full_detection", "false"), + ), + }, + { + Config: testAccWafPolicyV1_update(randName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWafPolicyV1Exists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("policy_%s_updated", randName)), + resource.TestCheckResourceAttr(resourceName, "protection_mode", "block"), + resource.TestCheckResourceAttr(resourceName, "level", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckWafPolicyV1Destroy(s *terraform.State) error { + config := acceptance.TestAccProvider.Meta().(*config.Config) + wafClient, err := config.WafV1Client(acceptance.HW_REGION_NAME) + if err != nil { + return fmt.Errorf("error creating HuaweiCloud WAF client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_waf_policy" { + continue + } + + _, err := policies.Get(wafClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Waf policy still exists") + } + } + + return nil +} + +func testAccCheckWafPolicyV1Exists(n string, policy *policies.Policy) 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 ID is set") + } + + config := acceptance.TestAccProvider.Meta().(*config.Config) + wafClient, err := config.WafV1Client(acceptance.HW_REGION_NAME) + if err != nil { + return fmt.Errorf("error creating huaweicloud WAF client: %s", err) + } + + found, err := policies.Get(wafClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.Id != rs.Primary.ID { + return fmt.Errorf("Waf policy not found") + } + + *policy = *found + + return nil + } +} + +func testAccWafPolicyV1_basic(name string) string { + return fmt.Sprintf(` +resource "huaweicloud_waf_policy" "policy_1" { + name = "policy-%s" +} +`, name) +} + +func testAccWafPolicyV1_update(name string) string { + return fmt.Sprintf(` +resource "huaweicloud_waf_policy" "policy_1" { + name = "policy_%s_updated" + protection_mode = "block" + level = 1 +} +`, name) +} diff --git a/huaweicloud/services/waf/resource_huaweicloud_waf_policy.go b/huaweicloud/services/waf/resource_huaweicloud_waf_policy.go new file mode 100644 index 00000000000..455d6fdbc59 --- /dev/null +++ b/huaweicloud/services/waf/resource_huaweicloud_waf_policy.go @@ -0,0 +1,230 @@ +package waf + +import ( + "fmt" + "log" + "time" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/common" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/huaweicloud/golangsdk/openstack/waf_hw/v1/policies" +) + +func ResourceWafPolicyV1() *schema.Resource { + return &schema.Resource{ + Create: resourceWafPolicyV1Create, + Read: resourceWafPolicyV1Read, + Update: resourceWafPolicyV1Update, + Delete: resourceWafPolicyV1Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "protection_mode": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "log", "block", + }, false), + }, + "level": { + Type: schema.TypeInt, + Computed: true, + Optional: true, + ValidateFunc: validation.IntBetween(0, 3), + }, + "options": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "basic_web_protection": { + Type: schema.TypeBool, + Computed: true, + }, + "general_check": { + Type: schema.TypeBool, + Computed: true, + }, + "crawler": { + Type: schema.TypeBool, + Computed: true, + }, + "crawler_engine": { + Type: schema.TypeBool, + Computed: true, + }, + "crawler_scanner": { + Type: schema.TypeBool, + Computed: true, + }, + "crawler_script": { + Type: schema.TypeBool, + Computed: true, + }, + "crawler_other": { + Type: schema.TypeBool, + Computed: true, + }, + "webshell": { + Type: schema.TypeBool, + Computed: true, + }, + "cc_attack_protection": { + Type: schema.TypeBool, + Computed: true, + }, + "precise_protection": { + Type: schema.TypeBool, + Computed: true, + }, + "blacklist": { + Type: schema.TypeBool, + Computed: true, + }, + "data_masking": { + Type: schema.TypeBool, + Computed: true, + }, + "false_alarm_masking": { + Type: schema.TypeBool, + Computed: true, + }, + "web_tamper_protection": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "full_detection": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func resourceWafPolicyV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + wafClient, err := config.WafV1Client(config.GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud WAF client: %s", err) + } + + createOpts := policies.CreateOpts{ + Name: d.Get("name").(string), + } + policy, err := policies.Create(wafClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating waf policy: %s", err) + } + + log.Printf("[DEBUG] Waf policy created: %#v", policy) + d.SetId(policy.Id) + d.Set("name", policy.Name) + + return resourceWafPolicyV1Read(d, meta) +} + +func resourceWafPolicyV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + wafClient, err := config.WafV1Client(config.GetRegion(d)) + if err != nil { + return fmt.Errorf("error creating HuaweiCloud WAF client: %s", err) + } + + n, err := policies.Get(wafClient, d.Id()).Extract() + if err != nil { + return common.CheckDeleted(d, err, "Waf Policy") + } + + d.Set("region", config.GetRegion(d)) + d.Set("name", n.Name) + d.Set("level", n.Level) + d.Set("protection_mode", n.Action.Category) + d.Set("full_detection", n.FullDetection) + + options := []map[string]interface{}{ + { + "basic_web_protection": n.Options.Webattack, + "general_check": n.Options.Common, + "crawler": n.Options.Crawler, + "crawler_engine": n.Options.CrawlerEngine, + "crawler_scanner": n.Options.CrawlerScanner, + "crawler_script": n.Options.CrawlerScript, + "crawler_other": n.Options.CrawlerOther, + "webshell": n.Options.Webshell, + "cc_attack_protection": n.Options.Cc, + "precise_protection": n.Options.Custom, + "blacklist": n.Options.Whiteblackip, + "false_alarm_masking": n.Options.Ignore, + "data_masking": n.Options.Privacy, + "web_tamper_protection": n.Options.Antitamper, + }, + } + d.Set("options", options) + return nil +} + +func resourceWafPolicyV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + wafClient, err := config.WafV1Client(config.GetRegion(d)) + if err != nil { + return fmt.Errorf("error creating HuaweiCloud WAF Client: %s", err) + } + + if d.HasChanges("name", "level", "protection_mode") { + updateOpts := policies.UpdateOpts{ + Name: d.Get("name").(string), + Level: d.Get("level").(int), + Action: &policies.Action{ + Category: d.Get("protection_mode").(string), + }, + } + + log.Printf("[DEBUG] updateOpts: %#v", updateOpts) + _, err = policies.Update(wafClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("error updating WAF Policy: %s", err) + } + } + + return resourceWafPolicyV1Read(d, meta) +} + +func resourceWafPolicyV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + wafClient, err := config.WafV1Client(config.GetRegion(d)) + if err != nil { + return fmt.Errorf("error creating HuaweiCloud WAF client: %s", err) + } + + err = policies.Delete(wafClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("error deleting WAF Policy: %s", err) + } + + d.SetId("") + return nil +}