Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rate limit options for compute resource security policy rules #5413

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
63bf9c0
Add rate limit options for compute resource security policy rules
kylejohnson514 Nov 2, 2021
3aa3dde
Update website docs to reflect support for rate_limit_options and cor…
kylejohnson514 Nov 3, 2021
f3fc4b5
cleanup
kylejohnson514 Nov 3, 2021
17bb57a
Address PR feedback and require rate_limit_threshold's count and inte…
kylejohnson514 Nov 16, 2021
9210305
Resolve compile errors
kylejohnson514 Nov 17, 2021
9590f05
Require both count and interval_sec values to be provided in rate_lim…
kylejohnson514 Nov 24, 2021
71ac76c
If ban_threshold is included, we require both count and interval_sec …
kylejohnson514 Nov 24, 2021
ac12608
Change rateLimitOptions to rate_limit_options in flatten function
kylejohnson514 Dec 7, 2021
f11f78c
Update acceptable values for enforceOnKey and update unit test to pro…
kylejohnson514 Dec 10, 2021
2a8a5dd
Merge branch 'resource_compute_security_policy/rateLimitOptions' of g…
kylejohnson514 Dec 10, 2021
278ac03
Merge branch 'master' of github.com:premisedata/magic-modules into re…
kylejohnson514 Jan 13, 2022
0f464ef
Fix resource definition in unit test. Add rate_based_ban and threshol…
kylejohnson514 Jan 13, 2022
bfa88bc
Update docs to add rate_based_ban and threshold as possible values fo…
kylejohnson514 Jan 13, 2022
97c46e2
Fixed typo
kylejohnson514 Jan 19, 2022
dac57c2
Fix valid field values for exceed_action and conform_action. Add supp…
kylejohnson514 Jan 20, 2022
9b7baad
Add default security policy rule
kylejohnson514 Jan 20, 2022
9402321
Fix exceed and conform action descriptions
kylejohnson514 Jan 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)`,
},

Expand Down Expand Up @@ -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": {
rileykarson marked this conversation as resolved.
Show resolved Hide resolved
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.`,
Expand Down Expand Up @@ -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"},
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<project>[^/]+)/global/securityPolicies/(?P<name>[^/]+)", "(?P<project>[^/]+)/(?P<name>[^/]+)", "(?P<name>[^/]+)"}, d, config); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 -%>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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).

<a name="nested_match"></a>The `match` block supports:

* `config` - (Optional) The configuration options available when specifying `versioned_expr`.
Expand All @@ -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.

<a name="nested_rate_limit_options"></a>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).

<a name="nested_threshold"></a>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.

<a name="nested_adaptive_protection_config"></a>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).
Expand Down