diff --git a/.changelog/12318.txt b/.changelog/12318.txt new file mode 100644 index 00000000000..140875bf9ef --- /dev/null +++ b/.changelog/12318.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +compute: added `rule.rate_limit_options.enforce_on_key_configs` field to `google_compute_security_policy` resource (GA) +``` \ No newline at end of file diff --git a/google/services/compute/resource_compute_security_policy.go b/google/services/compute/resource_compute_security_policy.go index 7afa8a3f15f..b66ca41d26a 100644 --- a/google/services/compute/resource_compute_security_policy.go +++ b/google/services/compute/resource_compute_security_policy.go @@ -325,6 +325,27 @@ func ResourceComputeSecurityPolicy() *schema.Resource { Description: `Rate limit key name applicable only for the following key types: HTTP_HEADER -- Name of the HTTP header whose value is taken as the key value. HTTP_COOKIE -- Name of the HTTP cookie whose value is taken as the key value.`, }, + "enforce_on_key_configs": { + Type: schema.TypeList, + Description: `Enforce On Key Config of this security policy`, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enforce_on_key_type": { + Type: schema.TypeString, + Optional: true, + Description: `Determines the key to enforce the rate_limit_threshold on`, + ValidateFunc: validation.StringInSlice([]string{"ALL", "IP", "HTTP_HEADER", "XFF_IP", "HTTP_COOKIE", "HTTP_PATH", "SNI", "REGION_CODE", "TLS_JA3_FINGERPRINT", "USER_IP"}, false), + }, + "enforce_on_key_name": { + Type: schema.TypeString, + Optional: true, + Description: `Rate limit key name applicable only for the following key types: HTTP_HEADER -- Name of the HTTP header whose value is taken as the key value. HTTP_COOKIE -- Name of the HTTP cookie whose value is taken as the key value.`, + }, + }, + }, + }, + "ban_threshold": { Type: schema.TypeList, Optional: true, @@ -853,15 +874,22 @@ func resourceComputeSecurityPolicyUpdate(d *schema.ResourceData, meta interface{ oPriorities := map[int64]bool{} nPriorities := map[int64]bool{} + oRules := make(map[int64]map[string]interface{}) + nRules := make(map[int64]map[string]interface{}) + for _, rule := range oSet.List() { oPriorities[int64(rule.(map[string]interface{})["priority"].(int))] = true + oRules[int64(rule.(map[string]interface{})["priority"].(int))] = rule.(map[string]interface{}) } for _, rule := range nSet.List() { + nRules[int64(rule.(map[string]interface{})["priority"].(int))] = rule.(map[string]interface{}) priority := int64(rule.(map[string]interface{})["priority"].(int)) nPriorities[priority] = true + if !oPriorities[priority] { client := config.NewComputeClient(userAgent) + // If the rule is in new and its priority does not exist in old, then add it. op, err := client.SecurityPolicies.AddRule(project, sp, expandSecurityPolicyRule(rule)).Do() @@ -874,10 +902,40 @@ func resourceComputeSecurityPolicyUpdate(d *schema.ResourceData, meta interface{ return err } } else if !oSet.Contains(rule) { + + oMap := make(map[string]interface{}) + nMap := make(map[string]interface{}) + + updateMask := []string{} + + if oRules[priority]["rate_limit_options"] != nil { + for _, oValue := range oRules[priority]["rate_limit_options"].([]interface{}) { + oMap = oValue.(map[string]interface{}) + } + } + + if nRules[priority]["rate_limit_options"] != nil { + for _, nValue := range nRules[priority]["rate_limit_options"].([]interface{}) { + nMap = nValue.(map[string]interface{}) + } + } + + if fmt.Sprintf("%v", oMap["enforce_on_key"]) != fmt.Sprintf("%v", nMap["enforce_on_key"]) { + updateMask = append(updateMask, "rate_limit_options.enforce_on_key") + } + + if fmt.Sprintf("%v", oMap["enforce_on_key_configs"]) != fmt.Sprintf("%v", nMap["enforce_on_key_configs"]) { + updateMask = append(updateMask, "rate_limit_options.enforce_on_key_configs") + } + + if fmt.Sprintf("%v", oMap["enforce_on_key_name"]) != fmt.Sprintf("%v", nMap["enforce_on_key_name"]) { + updateMask = append(updateMask, "rate_limit_options.enforce_on_key_name") + } + client := config.NewComputeClient(userAgent) // If the rule is in new, and its priority is in old, but its hash is different than the one in old, update it. - op, err := client.SecurityPolicies.PatchRule(project, sp, expandSecurityPolicyRule(rule)).Priority(priority).Do() + op, err := client.SecurityPolicies.PatchRule(project, sp, expandSecurityPolicyRule(rule)).Priority(priority).UpdateMask(strings.Join(updateMask, ",")).Do() if err != nil { return errwrap.Wrapf(fmt.Sprintf("Error updating SecurityPolicy %q: {{err}}", sp), err) @@ -1420,6 +1478,7 @@ func expandSecurityPolicyRuleRateLimitOptions(configured []interface{}) *compute ConformAction: data["conform_action"].(string), EnforceOnKey: data["enforce_on_key"].(string), EnforceOnKeyName: data["enforce_on_key_name"].(string), + EnforceOnKeyConfigs: expandSecurityPolicyEnforceOnKeyConfigs(data["enforce_on_key_configs"].([]interface{})), BanDurationSec: int64(data["ban_duration_sec"].(int)), ExceedRedirectOptions: expandSecurityPolicyRuleRedirectOptions(data["exceed_redirect_options"].([]interface{})), ForceSendFields: []string{"EnforceOnKey", "EnforceOnKeyName"}, @@ -1438,6 +1497,25 @@ func expandThreshold(configured []interface{}) *compute.SecurityPolicyRuleRateLi } } +func expandSecurityPolicyEnforceOnKeyConfigs(configured []interface{}) []*compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig { + params := make([]*compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig, 0, len(configured)) + + for _, raw := range configured { + params = append(params, expandSecurityPolicyEnforceOnKeyConfigsFields(raw)) + } + + return params +} + +func expandSecurityPolicyEnforceOnKeyConfigsFields(raw interface{}) *compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig { + data := raw.(map[string]interface{}) + + return &compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig{ + EnforceOnKeyType: data["enforce_on_key_type"].(string), + EnforceOnKeyName: data["enforce_on_key_name"].(string), + } +} + func flattenSecurityPolicyRuleRateLimitOptions(conf *compute.SecurityPolicyRuleRateLimitOptions) []map[string]interface{} { if conf == nil { return nil @@ -1450,6 +1528,7 @@ func flattenSecurityPolicyRuleRateLimitOptions(conf *compute.SecurityPolicyRuleR "conform_action": conf.ConformAction, "enforce_on_key": conf.EnforceOnKey, "enforce_on_key_name": conf.EnforceOnKeyName, + "enforce_on_key_configs": flattenSecurityPolicyEnforceOnKeyConfigs(conf.EnforceOnKeyConfigs), "ban_duration_sec": conf.BanDurationSec, "exceed_redirect_options": flattenSecurityPolicyRedirectOptions(conf.ExceedRedirectOptions), } @@ -1482,6 +1561,29 @@ func expandSecurityPolicyRuleRedirectOptions(configured []interface{}) *compute. } } +func flattenSecurityPolicyEnforceOnKeyConfigs(conf []*compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig) []map[string]interface{} { + if conf == nil || len(conf) == 0 { + return nil + } + + transformed := make([]map[string]interface{}, 0, len(conf)) + for _, raw := range conf { + transformed = append(transformed, flattenSecurityPolicyEnforceOnKeyConfigsFields(raw)) + } + return transformed +} + +func flattenSecurityPolicyEnforceOnKeyConfigsFields(conf *compute.SecurityPolicyRuleRateLimitOptionsEnforceOnKeyConfig) map[string]interface{} { + if conf == nil { + return nil + } + + return map[string]interface{}{ + "enforce_on_key_name": conf.EnforceOnKeyName, + "enforce_on_key_type": conf.EnforceOnKeyType, + } +} + func flattenSecurityPolicyRedirectOptions(conf *compute.SecurityPolicyRuleRedirectOptions) []map[string]interface{} { if conf == nil { return nil diff --git a/google/services/compute/resource_compute_security_policy_test.go b/google/services/compute/resource_compute_security_policy_test.go index 7687f71dd52..ace85a71233 100644 --- a/google/services/compute/resource_compute_security_policy_test.go +++ b/google/services/compute/resource_compute_security_policy_test.go @@ -444,6 +444,120 @@ func TestAccComputeSecurityPolicy_withRateLimitWithRedirectOptions(t *testing.T) }) } +func TestAccComputeSecurityPolicy_withRateLimit_withEnforceOnKeyConfigs(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeSecurityPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyConfigs(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSecurityPolicy_withRateLimitOption_withMultipleEnforceOnKeyConfigs(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeSecurityPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeSecurityPolicy_withRateLimitOption_withMultipleEnforceOnKeyConfigs(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOption_withMultipleEnforceOnKeyConfigs2(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSecurityPolicy_EnforceOnKeyUpdates(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeSecurityPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withoutRateLimitOptions(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyName(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKey(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyConfigs(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKey(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyName(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComputeSecurityPolicy_withRecaptchaOptionsConfig(t *testing.T) { t.Parallel() @@ -1243,7 +1357,7 @@ resource "google_compute_security_policy" "policy" { } log_level = "VERBOSE" user_ip_request_headers = [ - "True-Client-IP", + "True-Client-IP", "x-custom-ip" ] } @@ -1522,6 +1636,236 @@ resource "google_compute_security_policy" "policy" { `, spName) } +func testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKey(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "throttle" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withEnforceOnKey" + + rate_limit_options { + conform_action = "allow" + exceed_action = "redirect" + + enforce_on_key = "IP" + + exceed_redirect_options { + type = "EXTERNAL_302" + target = "https://www.example.com" + } + + rate_limit_threshold { + count = 10 + interval_sec = 60 + } + } + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRateLimitOptions_withoutRateLimitOptions(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "deny(403)" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withoutRateLimitOptions" + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyName(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "throttle" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withEnforceOnKeyName" + + rate_limit_options { + conform_action = "allow" + exceed_action = "redirect" + + enforce_on_key = "HTTP_HEADER" + enforce_on_key_name = "user-agent" + + exceed_redirect_options { + type = "EXTERNAL_302" + target = "https://www.example.com" + } + + rate_limit_threshold { + count = 10 + interval_sec = 60 + } + } + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRateLimitOptions_withEnforceOnKeyConfigs(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "throttle" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withEnforceOnKeyConfigs" + + rate_limit_options { + conform_action = "allow" + exceed_action = "redirect" + + enforce_on_key = "" + + enforce_on_key_configs { + enforce_on_key_type = "IP" + } + exceed_redirect_options { + type = "EXTERNAL_302" + target = "https://www.example.com" + } + + rate_limit_threshold { + count = 10 + interval_sec = 60 + } + } + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRateLimitOption_withMultipleEnforceOnKeyConfigs(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "throttle" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withMultipleEnforceOnKeyConfigs" + + rate_limit_options { + conform_action = "allow" + exceed_action = "deny(429)" + + rate_limit_threshold { + count = 10 + interval_sec = 60 + } + + enforce_on_key = "" + + enforce_on_key_configs { + enforce_on_key_type = "HTTP_PATH" + } + + enforce_on_key_configs { + enforce_on_key_type = "HTTP_HEADER" + enforce_on_key_name = "user-agent" + } + + enforce_on_key_configs { + enforce_on_key_type = "REGION_CODE" + } + } + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRateLimitOption_withMultipleEnforceOnKeyConfigs2(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "throttle rule with enforce_on_key_configs" + + rule { + action = "throttle" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule withMultipleEnforceOnKeyConfigs2" + + rate_limit_options { + conform_action = "allow" + exceed_action = "deny(429)" + + rate_limit_threshold { + count = 10 + interval_sec = 60 + } + + enforce_on_key = "" + + enforce_on_key_configs { + enforce_on_key_type = "REGION_CODE" + } + + enforce_on_key_configs { + enforce_on_key_type = "TLS_JA3_FINGERPRINT" + } + + enforce_on_key_configs { + enforce_on_key_type = "USER_IP" + } + } + } +} +`, spName) +} + func TestAccComputeSecurityPolicy_withRedirectOptionsRecaptcha(t *testing.T) { t.Parallel()