diff --git a/mmv1/third_party/terraform/resources/resource_compute_security_policy.go.erb b/mmv1/third_party/terraform/resources/resource_compute_security_policy.go.erb index 93889331f027..e4c04ccb73c9 100644 --- a/mmv1/third_party/terraform/resources/resource_compute_security_policy.go.erb +++ b/mmv1/third_party/terraform/resources/resource_compute_security_policy.go.erb @@ -67,7 +67,7 @@ func resourceComputeSecurityPolicy() *schema.Resource { "action": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{"allow", "deny(403)", "deny(404)", "deny(502)"}, false), + ValidateFunc: validation.StringInSlice([]string{"allow", "deny(403)", "deny(404)", "deny(502)", "rate_based_ban", "throttle"}, false), Description: `Action to take when match matches the request. Valid values: "allow" : allow access to target, "deny(status)" : deny access to target, returns the HTTP response code specified (valid values are 403, 404 and 502)`, }, @@ -154,6 +154,119 @@ func resourceComputeSecurityPolicy() *schema.Resource { Computed: true, Description: `When set to true, the action specified above is not enforced. Stackdriver logs for requests that trigger a preview action are annotated as such.`, }, + + <% unless version == 'ga' -%> + "rate_limit_options": { + Type: schema.TypeList, + Optional: true, + Description: `Rate limit threshold for this security policy. Must be specified if the action is "rate_based_ban" or "throttle". Cannot be specified for any other actions.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rate_limit_threshold": { + Type: schema.TypeList, + Required: true, + Description: `Threshold at which to begin ratelimiting.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + Description: `Number of HTTP(S) requests for calculating the threshold.`, + }, + + "interval_sec": { + Type: schema.TypeInt, + Required: true, + Description: `Interval over which the threshold is computed.`, + }, + }, + }, + }, + + "conform_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"allow"}, false), + Description: `Action to take for requests that are under the configured rate limit threshold. Valid option is "allow" only.`, + }, + + "exceed_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"redirect", "deny(403)", "deny(404)", "deny(429)", "deny(502)"}, false), + Description: `Action to take for requests that are above the configured rate limit threshold, to either deny with a specified HTTP response code, or redirect to a different endpoint. Valid options are "deny()" where valid values for status are 403, 404, 429, and 502, and "redirect" where the redirect parameters come from exceedRedirectOptions below.`, + }, + + "enforce_on_key": { + Type: schema.TypeString, + Optional: true, + Default: "ALL", + ValidateFunc: validation.StringInSlice([]string{"ALL", "IP", "HTTP_HEADER", "XFF_IP"}, false), + Description: `Determines the key to enforce the rateLimitThreshold on. Possible values are: "ALL" -- A single rate limit threshold is applied to all the requests matching this rule. This is the default value if this field 'enforceOnKey' is not configured. "IP" -- The source IP address of the request is the key. Each IP has this limit enforced separately. "HTTP_HEADER" -- The value of the HTTP Header whose name is configured under "enforceOnKeyName". The key value is truncated to the first 128 bytes of the Header value. If no such header is present in the request, the key type defaults to "ALL". "XFF_IP" -- The first IP address (i.e. the originating client IP address) specified in the list of IPs under X-Forwarded-For HTTP Header. If no such header is present or the value is not a valid IP, the key type defaults to "ALL".`, + }, + + "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.`, + }, + + "ban_threshold": { + Type: schema.TypeList, + Optional: true, + Description: `Can only be specified if the action for the rule is "rate_based_ban". If specified, the key will be banned for the configured 'banDurationSec' when the number of requests that exceed the 'rateLimitThreshold' also exceed this 'banThreshold'.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + Description: `Number of HTTP(S) requests for calculating the threshold.`, + }, + + "interval_sec": { + Type: schema.TypeInt, + Required: true, + Description: `Interval over which the threshold is computed.`, + }, + }, + }, + }, + + "ban_duration_sec": { + Type: schema.TypeInt, + Optional: true, + Description: `Can only be specified if the action for the rule is "rate_based_ban". If specified, determines the time (in seconds) the traffic will continue to be banned by the rate limit after the rate falls below the threshold.`, + }, + + "exceed_redirect_options": { + Type: schema.TypeList, + Optional: true, + Description: `Parameters defining the redirect action that is used as the exceed action. Cannot be specified if the exceed action is not redirect.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: `Type of the redirect action.`, + ValidateFunc: validation.StringInSlice([]string{"EXTERNAL_302", "GOOGLE_RECAPTCHA"}, false), + }, + + "target": { + Type: schema.TypeString, + Optional: true, + Description: `Target for the redirect action. This is required if the type is EXTERNAL_302 and cannot be specified for GOOGLE_RECAPTCHA.`, + }, + }, + }, + }, + }, + }, + }, + <% end -%> }, }, Description: `The set of rules that belong to this policy. There must always be a default rule (rule with priority 2147483647 and match "*"). If no rules are provided when creating a security policy, a default rule with action "allow" will be added.`, @@ -497,12 +610,15 @@ func expandSecurityPolicyRules(configured []interface{}) []*compute.SecurityPoli func expandSecurityPolicyRule(raw interface{}) *compute.SecurityPolicyRule { data := raw.(map[string]interface{}) return &compute.SecurityPolicyRule{ - Description: data["description"].(string), - Priority: int64(data["priority"].(int)), - Action: data["action"].(string), - Preview: data["preview"].(bool), - Match: expandSecurityPolicyMatch(data["match"].([]interface{})), - ForceSendFields: []string{"Description", "Preview"}, + Description: data["description"].(string), + Priority: int64(data["priority"].(int)), + Action: data["action"].(string), + Preview: data["preview"].(bool), + Match: expandSecurityPolicyMatch(data["match"].([]interface{})), + <% unless version == 'ga' -%> + RateLimitOptions: expandSecurityPolicyRuleRateLimitOptions(data["rate_limit_options"].([]interface{})), + <% end -%> + ForceSendFields: []string{"Description", "Preview"}, } } @@ -549,11 +665,14 @@ func flattenSecurityPolicyRules(rules []*compute.SecurityPolicyRule) []map[strin rulesSchema := make([]map[string]interface{}, 0, len(rules)) for _, rule := range rules { data := map[string]interface{}{ - "description": rule.Description, - "priority": rule.Priority, - "action": rule.Action, - "preview": rule.Preview, - "match": flattenMatch(rule.Match), + "description": rule.Description, + "priority": rule.Priority, + "action": rule.Action, + "preview": rule.Preview, + "match": flattenMatch(rule.Match), + <% unless version == 'ga' -%> + "rate_limit_options": flattenSecurityPolicyRuleRateLimitOptions(rule.RateLimitOptions), + <% end -%> } rulesSchema = append(rulesSchema, data) @@ -653,6 +772,68 @@ func flattenLayer7DdosDefenseConfig(conf *compute.SecurityPolicyAdaptiveProtecti } <% end -%> +<% unless version == 'ga' -%> +func expandSecurityPolicyRuleRateLimitOptions(configured []interface{}) *compute.SecurityPolicyRuleRateLimitOptions { + if len(configured) == 0 || configured[0] == nil { + return nil + } + + data := configured[0].(map[string]interface{}) + return &compute.SecurityPolicyRuleRateLimitOptions{ + BanThreshold: expandThreshold(data["ban_threshold"].([]interface{})), + RateLimitThreshold: expandThreshold(data["rate_limit_threshold"].([]interface{})), + ExceedAction: data["exceed_action"].(string), + ConformAction: data["conform_action"].(string), + EnforceOnKey: data["enforce_on_key"].(string), + EnforceOnKeyName: data["enforce_on_key_name"].(string), + BanDurationSec: int64(data["ban_duration_sec"].(int)), + } +} + +func expandThreshold(configured []interface{}) *compute.SecurityPolicyRuleRateLimitOptionsThreshold { + if len(configured) == 0 || configured[0] == nil { + return nil + } + + data := configured[0].(map[string]interface{}) + return &compute.SecurityPolicyRuleRateLimitOptionsThreshold{ + Count: int64(data["count"].(int)), + IntervalSec: int64(data["interval_sec"].(int)), + } +} + +func flattenSecurityPolicyRuleRateLimitOptions(conf *compute.SecurityPolicyRuleRateLimitOptions) []map[string]interface{} { + if conf == nil { + return nil + } + + data := map[string]interface{}{ + "ban_threshold": flattenThreshold(conf.BanThreshold), + "rate_limit_threshold": flattenThreshold(conf.RateLimitThreshold), + "exceed_action": conf.ExceedAction, + "conform_action": conf.ConformAction, + "enforce_on_key": conf.EnforceOnKey, + "enforce_on_key_name": conf.EnforceOnKeyName, + "ban_duration_sec": conf.BanDurationSec, + } + + return []map[string]interface{}{data} +} + +func flattenThreshold(conf *compute.SecurityPolicyRuleRateLimitOptionsThreshold) []map[string]interface{} { + if conf == nil { + return nil + } + + data := map[string]interface{}{ + "count": conf.Count, + "interval_sec": conf.IntervalSec, + } + + return []map[string]interface{}{data} +} +<% end -%> + func resourceSecurityPolicyStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { config := meta.(*Config) if err := parseImportId([]string{"projects/(?P[^/]+)/global/securityPolicies/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { diff --git a/mmv1/third_party/terraform/tests/resource_compute_security_policy_test.go.erb b/mmv1/third_party/terraform/tests/resource_compute_security_policy_test.go.erb index b2f10eab7b31..822013cbc9b7 100644 --- a/mmv1/third_party/terraform/tests/resource_compute_security_policy_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_compute_security_policy_test.go.erb @@ -147,6 +147,30 @@ func TestAccComputeSecurityPolicy_withAdaptiveProtection(t *testing.T) { } <% end -%> +<% unless version == 'ga' -%> +func TestAccComputeSecurityPolicy_withRateLimitOptions(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSecurityPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeSecurityPolicy_withRateLimitOptions(spName), + }, + { + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} +<% end -%> + func testAccCheckComputeSecurityPolicyDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := googleProviderConfig(t) @@ -356,3 +380,48 @@ resource "google_compute_security_policy" "policy" { `, spName) } <% end -%> + +<% unless version == 'ga' -%> +func testAccComputeSecurityPolicy_withRateLimitOptions(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "updated description" + + rule { + action = "allow" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule" + } + + rule { + action = "throttle" + priority = 100 + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = [ + "0.0.0.0/32", + ] + } + } + rate_limit_options { + conform_action = "allow" + exceed_action = "deny(403)" + enforce_on_key = "IP" + rate_limit_threshold { + count = 100 + interval_sec = 60 + } + } + } +} +`, spName) +} +<% end -%> \ No newline at end of file diff --git a/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown index ce6a1d2623c7..6f039d16f2e5 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_security_policy.html.markdown @@ -71,6 +71,8 @@ The following arguments are supported: * `action` - (Required) Action to take when `match` matches the request. Valid values: * "allow" : allow access to target * "deny(status)" : deny access to target, returns the HTTP response code specified (valid values are 403, 404 and 502) + * "rate_based_ban" : limit client traffic to the configured threshold and ban the client if the traffic exceeds the threshold. Configure parameters for this action in RateLimitOptions. Requires rateLimitOptions to be set. + * "threshold" : limit client traffic to the configured threshold. Configure parameters for this action in rateLimitOptions. Requires rateLimitOptions to be set for this. * `priority` - (Required) An unique positive integer indicating the priority of evaluation for a rule. Rules are evaluated from highest priority (lowest numerically) to lowest priority (highest numerically) in order. @@ -83,6 +85,9 @@ The following arguments are supported: * `preview` - (Optional) When set to true, the `action` specified above is not enforced. Stackdriver logs for requests that trigger a preview action are annotated as such. +* `rate_limit_options` - (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) + Must be specified if the `action` is "rate_based_bad" or "throttle". Cannot be specified for other actions. Structure is [documented below](#nested_rate_limit_options). + The `match` block supports: * `config` - (Optional) The configuration options available when specifying `versioned_expr`. @@ -108,6 +113,33 @@ The following arguments are supported: * `expression` - (Required) Textual representation of an expression in Common Expression Language syntax. The application context of the containing message determines which well-known feature set of CEL is supported. +The `rate_limit_options` block supports: + +* `ban_duration_sec` - (Optional) Can only be specified if the `action` for the rule is "rate_based_ban". + If specified, determines the time (in seconds) the traffic will continue to be banned by the rate limit after the rate falls below the threshold. + +* `ban_threshold` - (Optional) Can only be specified if the `action` for the rule is "rate_based_ban". + If specified, the key will be banned for the configured 'ban_duration_sec' when the number of requests that exceed the 'rate_limit_threshold' also + exceed this 'ban_threshold'. Structure is [documented below](#nested_threshold). + +* `conform_action` - (Optional) Action to take for requests that are under the configured rate limit threshold. Valid option is "allow" only. + +* `enforce_on_key` - (Optional) Determines the key to enforce the rate_limit_threshold on. + Possible values incude "ALL", "ALL_IPS", "HTTP_HEADER", "IP", "XFF_IP". If not specified, defaults to "ALL". + +* `enforce_on_key_name` - (Optional) Rate limit key name applicable only for HTTP_HEADER key types. Name of the HTTP header whose value is taken as the key value. + +* `exceed_action` - (Optional) When a request is denied, returns the HTTP response code specified. + Valid options are "deny()" where valid values for status are 403, 404, 429, and 502. + +* `rate_limit_threshold` - (Optional) Threshold at which to begin ratelimiting. Structure is [documented below](#nested_threshold). + +The `{ban/rate_limit}_threshold` block supports: + +* `count` - (Optional) Number of HTTP(S) requests for calculating the threshold. + +* `interval_sec` - (Optional) Interval over which the threshold is computed. + The `adaptive_protection_config` block supports: * `layer_7_ddos_defense_config` - (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) Configuration for [Google Cloud Armor Adaptive Protection Layer 7 DDoS Defense](https://cloud.google.com/armor/docs/adaptive-protection-overview?hl=en). Structure is [documented below](#nested_layer_7_ddos_defense_config).