From 8ab03062fe22b0f94d256252c0b187308b748ffe Mon Sep 17 00:00:00 2001 From: Imran Nayer Date: Tue, 5 Mar 2024 19:18:32 +0000 Subject: [PATCH] feat: add preconfigured_waf_config_exclusions to allow multiple exclusions --- README.md | 285 ++++++++++++------ docs/upgrading_to_v2.1.md | 8 + examples/simple-example/main.tf | 97 +++--- main.tf | 87 ++++++ .../simple-example/simple_example_test.go | 43 ++- variables.tf | 48 ++- versions.tf | 4 +- 7 files changed, 397 insertions(+), 175 deletions(-) create mode 100644 docs/upgrading_to_v2.1.md diff --git a/README.md b/README.md index 62e4498..3e91e10 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Cloud Armor Terraform Module -This module makes it easy to setup [Cloud Armor Security Policy](https://cloud.google.com/armor/docs/cloud-armor-overview#security_policies) with Security rules. There are five type of rules you can create in each policy: -- [Pre-Configured Rules](#pre_configured_rules): These are based on [pre-configured waf rules](https://cloud.google.com/armor/docs/waf-rules). -- [Security Rules](#security_rules): Allow or Deny traffic from list of IP addresses or IP adress ranges. -- [Custom Rules](#custom_rules): You can create your own rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference). -- [Threat Intelligence Rules](#threat_intelligence_rules): Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). [Managed protection plus](https://cloud.google.com/armor/docs/managed-protection-overview) subscription is needed to use this feature. -- [Automatically deploy Adaptive Protection suggested rules](#adaptive_protection_auto_deploy); When enable module will create a rule for automatically deploying the suggested rules that [Adaptive Protection generates](https://cloud.google.com/armor/docs/adaptive-protection-auto-deploy). +This module makes it easy to setup [Cloud Armor Security Policy](https://cloud.google.com/armor/docs/cloud-armor-overview#security_policies) with Security rules. There are `five` type of rules you can create in each policy: +1) [Pre-Configured Rules](#pre_configured_rules): These are based on [pre-configured waf rules](https://cloud.google.com/armor/docs/waf-rules). +2) [Security Rules](#security_rules): Allow or Deny traffic from list of IP addresses or IP adress ranges. +3) [Custom Rules](#custom_rules): You can create your own rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference). +4) [Threat Intelligence Rules](#threat_intelligence_rules): Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). [Managed protection plus](https://cloud.google.com/armor/docs/managed-protection-overview) subscription is needed to use this feature. +5) [Automatically deploy Adaptive Protection Suggested Rules](#adaptive_protection_auto_deploy); When enable module will create a rule for automatically deploying the suggested rules that [Adaptive Protection generates](https://cloud.google.com/armor/docs/adaptive-protection-auto-deploy). ## Compatibility @@ -17,6 +17,7 @@ Current version is 2.X. Upgrade guides: - [0.X -> 1.0.](/docs/upgrading_to_v1.0.md) - [1.X -> 2.0.](/docs/upgrading_to_v2.0.md) +- [2.X -> 2.1.](/docs/upgrading_to_v2.1.md) ## Module Format @@ -39,6 +40,7 @@ module security_policy { security_rules = {} custom_rules = {} threat_intelligence_rules = {} + adaptive_protection_auto_deploy = {} } ``` @@ -69,6 +71,58 @@ module "security_policy" { action = "deny(502)" priority = 1 target_rule_set = "sqli-v33-stable" + + sensitivity_level = 4 + description = "sqli-v33-stable Sensitivity Level 4 and 2 preconfigured_waf_config_exclusions" + + preconfigured_waf_config_exclusions = { + exclusion_1 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942120-sqli", "owasp-crs-v030301-id942130-sqli"] + request_cookie = [ + { + operator = "STARTS_WITH" + value = "abc" + } + ] + request_header = [ + { + operator = "STARTS_WITH" + value = "xyz" + }, + { + operator = "STARTS_WITH" + value = "uvw" + } + ] + } + + exclusion_2 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942150-sqli", "owasp-crs-v030301-id942180-sqli"] + request_header = [ + { + operator = "STARTS_WITH" + value = "lmn" + }, + { + operator = "ENDS_WITH" + value = "opq" + } + ] + request_uri = [ + { + operator = "CONTAINS" + value = "https://hashicorp.com" + }, + { + operator = "CONTAINS" + value = "https://xyz.com" + }, + ] + } + + } } "xss-stable_level_2_with_exclude" = { @@ -79,30 +133,6 @@ module "security_policy" { target_rule_set = "xss-v33-stable" sensitivity_level = 2 exclude_target_rule_ids = ["owasp-crs-v030301-id941380-xss", "owasp-crs-v030301-id941280-xss"] - preconfigured_waf_config_exclusion = { - target_rule_set = "xss-v33-stable" - target_rule_ids = ["owasp-crs-v030301-id941140-xss", "owasp-crs-v030301-id941270-xss"] - request_header = [ - { - operator = "STARTS_WITH" - value = "abc" - }, - { - operator = "ENDS_WITH" - value = "xyz" - } - ] - request_uri = [ - { - operator = "CONTAINS" - value = "https://hashicorp.com" - }, - { - operator = "CONTAINS" - value = "https://xyz.com" - }, - ] - } } "php-stable_level_0_with_include" = { @@ -279,7 +309,7 @@ module "security_policy" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | adaptive\_protection\_auto\_deploy | Configuration for Automatically deploy Cloud Armor Adaptive Protection suggested rules. `priority` and `action` fields are required if `enable` is set to true. Requires `layer_7_ddos_defense_enable` set to `true`. |
object({
enable = bool
priority = optional(number, null)
action = optional(string, null)
preview = optional(bool, false)
description = optional(string, "Adaptive Protection auto-deploy")
load_threshold = optional(number)
confidence_threshold = optional(number)
impacted_baseline_threshold = optional(number)
expiration_sec = optional(number)
redirect_type = optional(string)
redirect_target = optional(string)

rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)

enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))

exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}), {})
})
|
{
"enable": false
}
| no | -| custom\_rules | Custome security rules |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
expression = string
redirect_type = optional(string, null)
redirect_target = optional(string, null)
rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)
enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))
exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}),
{})
header_action = optional(list(object({
header_name = optional(string)
header_value = optional(string)
})), [])

preconfigured_waf_config_exclusion = optional(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
}), { target_rule_set = null })

}))
| `{}` | no | +| custom\_rules | Custome security rules |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
expression = string
redirect_type = optional(string, null)
redirect_target = optional(string, null)
rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)
enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))
exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}),
{})
header_action = optional(list(object({
header_name = optional(string)
header_value = optional(string)
})), [])

preconfigured_waf_config_exclusion = optional(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
}), { target_rule_set = null }) # Obsolete. Use preconfigured_waf_config_exclusions

preconfigured_waf_config_exclusions = optional(map(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
})), null)

}))
| `{}` | no | | default\_rule\_action | default rule that allows/denies all traffic with the lowest priority (2,147,483,647). | `string` | `"allow"` | no | | description | An optional description of this security policy. Max size is 2048. | `string` | `null` | no | | json\_custom\_config\_content\_types | A list of custom Content-Type header values to apply the JSON parsing. Only applicable when json\_parsing is set to STANDARD. Not supported for CLOUD\_ARMOR\_EDGE policy type. | `list(string)` | `[]` | no | @@ -288,7 +318,7 @@ module "security_policy" { | layer\_7\_ddos\_defense\_rule\_visibility | (Optional) Rule visibility can be one of the following: STANDARD - opaque rules. PREMIUM - transparent rules. This field is only supported in Global Security Policies of type CLOUD\_ARMOR. | `string` | `"STANDARD"` | no | | log\_level | Log level to use. Possible values are NORMAL and VERBOSE. Not supported for CLOUD\_ARMOR\_EDGE policy type. | `string` | `"NORMAL"` | no | | name | Name of the security policy. | `string` | n/a | yes | -| pre\_configured\_rules | Map of pre-configured rules Sensitivity levels |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
redirect_type = optional(string, null)
redirect_target = optional(string, null)
target_rule_set = string
sensitivity_level = optional(number, 4)
include_target_rule_ids = optional(list(string), [])
exclude_target_rule_ids = optional(list(string), [])
rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)
enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))
exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}), {})

header_action = optional(list(object({
header_name = optional(string)
header_value = optional(string)
})), [])

preconfigured_waf_config_exclusion = optional(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
}), { target_rule_set = null })

}))
| `{}` | no | +| pre\_configured\_rules | Map of pre-configured rules with Sensitivity levels. preconfigured\_waf\_config\_exclusion is obsolete and available for backward compatibility. Use preconfigured\_waf\_config\_exclusions which allows multiple exclusions |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
redirect_type = optional(string, null)
redirect_target = optional(string, null)
target_rule_set = string
sensitivity_level = optional(number, 4)
include_target_rule_ids = optional(list(string), [])
exclude_target_rule_ids = optional(list(string), [])
rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)
enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))
exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}), {})

header_action = optional(list(object({
header_name = optional(string)
header_value = optional(string)
})), [])

preconfigured_waf_config_exclusion = optional(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
}), { target_rule_set = null }) # Obsolete. Use preconfigured_waf_config_exclusions

preconfigured_waf_config_exclusions = optional(map(object({
target_rule_set = string
target_rule_ids = optional(list(string), [])
request_header = optional(list(object({
operator = string
value = optional(string)
})))
request_cookie = optional(list(object({
operator = string
value = optional(string)
})))
request_uri = optional(list(object({
operator = string
value = optional(string)
})))
request_query_param = optional(list(object({
operator = string
value = optional(string)
})))
})), null)

}))
| `{}` | no | | project\_id | The project in which the resource belongs. | `string` | n/a | yes | | recaptcha\_redirect\_site\_key | reCAPTCHA site key to be used for all the rules using the redirect action with the redirect type of GOOGLE\_RECAPTCHA. | `string` | `null` | no | | security\_rules | Map of Security rules with list of IP addresses to block or unblock. |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
redirect_type = optional(string, null)
redirect_target = optional(string, null)
src_ip_ranges = list(string)
rate_limit_options = optional(object({
enforce_on_key = optional(string)
enforce_on_key_name = optional(string)
enforce_on_key_configs = optional(list(object({
enforce_on_key_name = optional(string)
enforce_on_key_type = optional(string)
})))
exceed_action = optional(string)
rate_limit_http_request_count = optional(number)
rate_limit_http_request_interval_sec = optional(number)
ban_duration_sec = optional(number)
ban_http_request_count = optional(number)
ban_http_request_interval_sec = optional(number)
}),
{})
header_action = optional(list(object({
header_name = optional(string)
header_value = optional(string)
})), [])
}))
| `{}` | no | @@ -313,18 +343,18 @@ module "security_policy" { ``` "my_rule" = { action = "deny(502)" - priority = 1 - description = "SQL Sensitivity Level 4" - preview = false - redirect_type = null - redirect_target = null - target_rule_set = "sqli-v33-stable" - sensitivity_level = 4 - include_target_rule_ids = [] - exclude_target_rule_ids = [] - header_action = [] - rate_limit_options = {} - preconfigured_waf_config_exclusion = {} + priority = 1 + description = "SQL Sensitivity Level 4" + preview = false + redirect_type = null + redirect_target = null + target_rule_set = "sqli-v33-stable" + sensitivity_level = 4 + include_target_rule_ids = [] + exclude_target_rule_ids = [] + header_action = [] + rate_limit_options = {} + preconfigured_waf_config_exclusions = {} } ``` @@ -356,32 +386,59 @@ rate_limit_options = { ``` ## Preconfigured WAF Config -`preconfigured_waf_config_exclusion` is needed for custom application that might contain content in request fields (like headers, cookies, query parameters, or URIs) that matches signatures in preconfigured WAF rules, but which you know is legitimate. In this case, you can reduce false positives by excluding those request fields from inspection by associating a list of exclusions for request fields with the security policy rule. You can pass `request_header`, `request_uri`, `request_cookie` and `request_query_param`. It is available in [Pre-Configured Rules](#pre_configured_rules). You can find more details about `preconfigured_waf_config` [here](https://cloud.google.com/armor/docs/rule-tuning#exclude_request_fields_from_inspection) +:bangbang: **NOTE:** `preconfigured_waf_config_exclusion` in `pre_configured_rules` and `custom_rules` is obsolete and available for backward compatibility only. Use `pre_configured_rules.preconfigured_waf_config_exclusions` which allows multiple exclusions. They are mutually exclusive. + +`preconfigured_waf_config_exclusions` is needed for custom application that might contain content in request fields (like headers, cookies, query parameters, or URIs) that matches signatures in preconfigured WAF rules, but which you know is legitimate. In this case, you can reduce false positives by excluding those request fields from inspection by associating a list of exclusions for request fields with the security policy rule. You can pass `request_header`, `request_uri`, `request_cookie` and `request_query_param`. It is available in [Pre-Configured Rules](#pre_configured_rules). You can find more details about `preconfigured_waf_config` [here](https://cloud.google.com/armor/docs/rule-tuning#exclude_request_fields_from_inspection) ``` -preconfigured_waf_config_exclusion = { - target_rule_set = "xss-v33-stable" - target_rule_ids = ["owasp-crs-v030301-id941140-xss", "owasp-crs-v030301-id941270-xss"] - request_header = [ - { - operator = "STARTS_WITH" - value = "abc" - }, - { - operator = "ENDS_WITH" - value = "xyz" - } - ] - request_uri = [ - { - operator = "CONTAINS" - value = "https://hashicorp.com" - }, - { - operator = "CONTAINS" - value = "https://xyz.com" - }, - ] +preconfigured_waf_config_exclusions = { + + exclusion_1 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942120-sqli", "owasp-crs-v030301-id942130-sqli"] + request_cookie = [ + { + operator = "STARTS_WITH" + value = "abc" + } + ] + request_header = [ + { + operator = "STARTS_WITH" + value = "xyz" + }, + { + operator = "STARTS_WITH" + value = "uvw" + } + ] + } + + exclusion_2 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942150-sqli", "owasp-crs-v030301-id942180-sqli"] + request_header = [ + { + operator = "STARTS_WITH" + value = "lmn" + }, + { + operator = "ENDS_WITH" + value = "opq" + } + ] + request_uri = [ + { + operator = "CONTAINS" + value = "https://hashicorp.com" + }, + { + operator = "CONTAINS" + value = "https://xyz.com" + }, + ] + } + } ``` @@ -392,19 +449,19 @@ List of preconfigured rules are available [here](https://cloud.google.com/armor/ ``` "sqli_sensitivity_level_4" = { - action = "deny(502)" - priority = 1 - description = "SQL Sensitivity Level 4" - preview = false - redirect_type = null - redirect_target = null - target_rule_set = "sqli-v33-stable" - sensitivity_level = 4 - include_target_rule_ids = [] - exclude_target_rule_ids = [] - rate_limit_options = {} - header_action = [] - preconfigured_waf_config_exclusion = {} + action = "deny(502)" + priority = 1 + description = "SQL Sensitivity Level 4" + preview = false + redirect_type = null + redirect_target = null + target_rule_set = "sqli-v33-stable" + sensitivity_level = 4 + include_target_rule_ids = [] + exclude_target_rule_ids = [] + rate_limit_options = {} + header_action = [] + preconfigured_waf_config_exclusions = {} } ``` @@ -429,24 +486,56 @@ pre_configured_rules = { target_rule_set = "sqli-v33-stable" sensitivity_level = 4 - preconfigured_waf_config_exclusion = { - target_rule_set = "sqli-v33-stable" - request_cookie = [ - { - operator = "EQUALS_ANY" - }, - { - operator = "STARTS_WITH" - value = "abc" - } - ] - request_header = [ - { - operator = "STARTS_WITH" - value = "xyz" - } - ] + preconfigured_waf_config_exclusions = { + + exclusion_1 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942120-sqli", "owasp-crs-v030301-id942130-sqli"] + request_cookie = [ + { + operator = "STARTS_WITH" + value = "abc" + } + ] + request_header = [ + { + operator = "STARTS_WITH" + value = "xyz" + }, + { + operator = "STARTS_WITH" + value = "uvw" + } + ] + } + + exclusion_2 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942150-sqli", "owasp-crs-v030301-id942180-sqli"] + request_header = [ + { + operator = "STARTS_WITH" + value = "lmn" + }, + { + operator = "ENDS_WITH" + value = "opq" + } + ] + request_uri = [ + { + operator = "CONTAINS" + value = "https://hashicorp.com" + }, + { + operator = "CONTAINS" + value = "https://xyz.com" + }, + ] + } + } + } } diff --git a/docs/upgrading_to_v2.1.md b/docs/upgrading_to_v2.1.md new file mode 100644 index 0000000..cf18fcc --- /dev/null +++ b/docs/upgrading_to_v2.1.md @@ -0,0 +1,8 @@ +# Upgrading to v2.1.0 + +The v2.1 release contains backwards-compatible. `preconfigured_waf_config_exclusion` is obsolete and will be removed in next major version. + +## Preconfigured WAF Config +:bangbang: **NOTE:** `preconfigured_waf_config_exclusion` in `pre_configured_rules` and `custom_rules` is obsolete and available for backward compatibility only. Use `preconfigured_waf_config_exclusions` which allows multiple exclusions. They are mutually exclusive. + +If you are migrating from `preconfigured_waf_config_exclusion` to `preconfigured_waf_config_exclusions` first remove `preconfigured_waf_config_exclusion` and apply the code, then add exclusions using `preconfigured_waf_config_exclusions` diff --git a/examples/simple-example/main.tf b/examples/simple-example/main.tf index 4f9ed9a..132e0a9 100644 --- a/examples/simple-example/main.tf +++ b/examples/simple-example/main.tf @@ -40,66 +40,65 @@ module "cloud_armor" { priority = 1 target_rule_set = "sqli-v33-stable" sensitivity_level = 4 + description = "sqli-v33-stable Sensitivity Level 4 and 2 preconfigured_waf_config_exclusions" + + # 2 exclusions + preconfigured_waf_config_exclusions = { + exclusion_1 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942120-sqli", "owasp-crs-v030301-id942130-sqli"] + request_cookie = [ + { + operator = "STARTS_WITH" + value = "abc" + } + ] + request_header = [ + { + operator = "STARTS_WITH" + value = "xyz" + }, + { + operator = "STARTS_WITH" + value = "uvw" + } + ] + } + exclusion_2 = { + target_rule_set = "sqli-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id942150-sqli", "owasp-crs-v030301-id942180-sqli"] + request_header = [ + { + operator = "STARTS_WITH" + value = "lmn" + }, + { + operator = "ENDS_WITH" + value = "opq" + } + ] + request_uri = [ + { + operator = "CONTAINS" + value = "https://hashicorp.com" + }, + { + operator = "CONTAINS" + value = "https://xyz.com" + }, + ] + } - preconfigured_waf_config_exclusion = { - target_rule_set = "sqli-v33-stable" - request_cookie = [ - { - operator = "EQUALS_ANY" - }, - { - operator = "STARTS_WITH" - value = "abc" - } - ] - request_header = [ - { - operator = "STARTS_WITH" - value = "xyz" - }, - { - operator = "STARTS_WITH" - value = "uvw" - } - ] } - } "xss-stable_level_2_with_exclude" = { action = "deny(502)" priority = 2 - description = "XSS Sensitivity Level 2 with excluded rules" preview = true target_rule_set = "xss-v33-stable" sensitivity_level = 2 exclude_target_rule_ids = ["owasp-crs-v030301-id941380-xss", "owasp-crs-v030301-id941280-xss"] - - preconfigured_waf_config_exclusion = { - target_rule_set = "xss-v33-stable" - target_rule_ids = ["owasp-crs-v030301-id941140-xss", "owasp-crs-v030301-id941270-xss"] - request_header = [ - { - operator = "STARTS_WITH" - value = "abc" - }, - { - operator = "ENDS_WITH" - value = "xyz" - } - ] - request_uri = [ - { - operator = "CONTAINS" - value = "https://hashicorp.com" - }, - { - operator = "CONTAINS" - value = "https://xyz.com" - }, - ] - } - } "php-stable_level_0_with_include" = { diff --git a/main.tf b/main.tf index fceb813..8f6f253 100644 --- a/main.tf +++ b/main.tf @@ -206,6 +206,50 @@ resource "google_compute_security_policy" "policy" { } } } + + # Optional preconfigured_waf_config Block if preconfigured_waf_config_exclusion is provided + dynamic "preconfigured_waf_config" { + for_each = rule.value.preconfigured_waf_config_exclusions == null ? [] : ["preconfigured_waf_config_exclusions"] #rule.value.preconfigured_waf_config_exclusions + content { + dynamic "exclusion" { + for_each = rule.value.preconfigured_waf_config_exclusions + content { + target_rule_set = exclusion.value.target_rule_set + target_rule_ids = exclusion.value.target_rule_ids + dynamic "request_header" { + for_each = exclusion.value.request_header == null ? {} : { for x in exclusion.value.request_header : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_header.value.operator + value = request_header.value.operator == "EQUALS_ANY" ? null : request_header.value.value + } + } + dynamic "request_cookie" { + for_each = exclusion.value.request_cookie == null ? {} : { for x in exclusion.value.request_cookie : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_cookie.value.operator + value = request_cookie.value.operator == "EQUALS_ANY" ? null : request_cookie.value.value + } + } + dynamic "request_uri" { + for_each = exclusion.value.request_uri == null ? {} : { for x in exclusion.value.request_uri : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_uri.value.operator + value = request_uri.value.operator == "EQUALS_ANY" ? null : request_uri.value.value + } + } + dynamic "request_query_param" { + for_each = exclusion.value.request_query_param == null ? {} : { for x in exclusion.value.request_query_param : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_query_param.value.operator + value = request_query_param.value.operator == "EQUALS_ANY" ? null : request_query_param.value.value + } + } + } + } + + } + } + } } @@ -406,6 +450,49 @@ resource "google_compute_security_policy" "policy" { } } + # Optional preconfigured_waf_config Block if preconfigured_waf_config_exclusion is provided + dynamic "preconfigured_waf_config" { + for_each = rule.value.preconfigured_waf_config_exclusions == null ? [] : ["preconfigured_waf_config_exclusions"] #rule.value.preconfigured_waf_config_exclusions + content { + dynamic "exclusion" { + for_each = rule.value.preconfigured_waf_config_exclusions + content { + target_rule_set = exclusion.value.target_rule_set + target_rule_ids = exclusion.value.target_rule_ids + dynamic "request_header" { + for_each = exclusion.value.request_header == null ? {} : { for x in exclusion.value.request_header : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_header.value.operator + value = request_header.value.operator == "EQUALS_ANY" ? null : request_header.value.value + } + } + dynamic "request_cookie" { + for_each = exclusion.value.request_cookie == null ? {} : { for x in exclusion.value.request_cookie : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_cookie.value.operator + value = request_cookie.value.operator == "EQUALS_ANY" ? null : request_cookie.value.value + } + } + dynamic "request_uri" { + for_each = exclusion.value.request_uri == null ? {} : { for x in exclusion.value.request_uri : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_uri.value.operator + value = request_uri.value.operator == "EQUALS_ANY" ? null : request_uri.value.value + } + } + dynamic "request_query_param" { + for_each = exclusion.value.request_query_param == null ? {} : { for x in exclusion.value.request_query_param : "${x.operator}-${base64encode(coalesce(x.value, "test"))}" => x } + content { + operator = request_query_param.value.operator + value = request_query_param.value.operator == "EQUALS_ANY" ? null : request_query_param.value.value + } + } + } + } + + } + } + } } diff --git a/test/integration/simple-example/simple_example_test.go b/test/integration/simple-example/simple_example_test.go index 3b42c2e..75efe3c 100644 --- a/test/integration/simple-example/simple_example_test.go +++ b/test/integration/simple-example/simple_example_test.go @@ -46,17 +46,26 @@ func TestSimpleExample(t *testing.T) { for _, sp := range spRule1.Array() { assert.Equal("deny(502)", sp.Get("action").String(), "priority 1 rule has expected action") assert.Equal("evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 4})", sp.Get("match.expr.expression").String(), "priority 1 rule has expected rule expression") - assert.Empty(sp.Get("description").String(), "priority 2 rule has expected description") + assert.Equal("sqli-v33-stable Sensitivity Level 4 and 2 preconfigured_waf_config_exclusions", sp.Get("description").String(), "priority 1 rule has expected description") assert.False(sp.Get("preview").Bool(), "priority 1 rule Preview is set to False") - for _, pce := range sp.Get("preconfiguredWafConfig.exclusions").Array() { - assert.Equal("EQUALS_ANY", pce.Get("requestCookiesToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestCookiesToExclude") - assert.Equal("STARTS_WITH", pce.Get("requestCookiesToExclude").Array()[1].Get("op").String(), "priority 1 rule has expected requestCookiesToExclude") - assert.Equal("abc", pce.Get("requestCookiesToExclude").Array()[1].Get("val").String(), "priority 1 rule has expected requestCookiesToExclude") - - assert.Equal("STARTS_WITH", pce.Get("requestHeadersToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") - assert.Equal("STARTS_WITH", pce.Get("requestHeadersToExclude").Array()[1].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") - assert.Equal("xyz", pce.Get("requestHeadersToExclude").Array()[1].Get("val").String(), "priority 1 rule has expected requestHeadersToExclude") - } + + pce := sp.Get("preconfiguredWafConfig.exclusions").Array() + assert.Equal("STARTS_WITH", pce[0].Get("requestCookiesToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestCookiesToExclude") + assert.Equal("abc", pce[0].Get("requestCookiesToExclude").Array()[0].Get("val").String(), "priority 1 rule has expected requestCookiesToExclude") + assert.Equal("STARTS_WITH", pce[0].Get("requestHeadersToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("uvw", pce[0].Get("requestHeadersToExclude").Array()[0].Get("val").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("STARTS_WITH", pce[0].Get("requestHeadersToExclude").Array()[1].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("xyz", pce[0].Get("requestHeadersToExclude").Array()[1].Get("val").String(), "priority 1 rule has expected requestHeadersToExclude") + + assert.Equal("ENDS_WITH", pce[1].Get("requestHeadersToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("opq", pce[1].Get("requestHeadersToExclude").Array()[0].Get("val").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("STARTS_WITH", pce[1].Get("requestHeadersToExclude").Array()[1].Get("op").String(), "priority 1 rule has expected requestHeadersToExclude") + assert.Equal("lmn", pce[1].Get("requestHeadersToExclude").Array()[1].Get("val").String(), "priority 1 rule has expected requestHeadersToExclude") + + assert.Equal("CONTAINS", pce[1].Get("requestUrisToExclude").Array()[0].Get("op").String(), "priority 1 rule has expected requestUrisToExclude") + assert.Equal("https://xyz.com", pce[1].Get("requestUrisToExclude").Array()[0].Get("val").String(), "priority 1 rule has expected requestUrisToExclude") + assert.Equal("CONTAINS", pce[1].Get("requestUrisToExclude").Array()[1].Get("op").String(), "priority 1 rule has expected requestUrisToExclude") + assert.Equal("https://hashicorp.com", pce[1].Get("requestUrisToExclude").Array()[1].Get("val").String(), "priority 1 rule has expected requestUrisToExclude") } // Rule 2 @@ -64,20 +73,8 @@ func TestSimpleExample(t *testing.T) { for _, sp := range spRule2.Array() { assert.Equal("deny(502)", sp.Get("action").String(), "priority 2 rule has expected action") assert.Equal("evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 2, 'opt_out_rule_ids': ['owasp-crs-v030301-id941380-xss','owasp-crs-v030301-id941280-xss']})", sp.Get("match.expr.expression").String(), "priority 2 rule has expected rule expression") - assert.Equal("XSS Sensitivity Level 2 with excluded rules", sp.Get("description").String(), "priority 2 rule has expected description") + assert.Empty(sp.Get("description").String(), "priority 2 rule has expected description") assert.True(sp.Get("preview").Bool(), "priority 2 rule Preview is set to True") - - for _, pce := range sp.Get("preconfiguredWafConfig.exclusions").Array() { - assert.Equal("ENDS_WITH", pce.Get("requestHeadersToExclude").Array()[0].Get("op").String(), "priority 2 rule has expected requestHeadersToExclude") - assert.Equal("STARTS_WITH", pce.Get("requestHeadersToExclude").Array()[1].Get("op").String(), "priority 2 rule has expected requestHeadersToExclude") - assert.Equal("xyz", pce.Get("requestHeadersToExclude").Array()[0].Get("val").String(), "priority 2 rule has expected requestHeadersToExclude") - assert.Equal("abc", pce.Get("requestHeadersToExclude").Array()[1].Get("val").String(), "priority 2 rule has expected requestHeadersToExclude") - - assert.Equal("CONTAINS", pce.Get("requestUrisToExclude").Array()[0].Get("op").String(), "priority 2 rule has expected requestUrisToExclude") - assert.Equal("CONTAINS", pce.Get("requestUrisToExclude").Array()[1].Get("op").String(), "priority 2 rule has expected requestUrisToExclude") - assert.Equal("https://xyz.com", pce.Get("requestUrisToExclude").Array()[0].Get("val").String(), "priority 2 rule has expected requestUrisToExclude") - assert.Equal("https://hashicorp.com", pce.Get("requestUrisToExclude").Array()[1].Get("val").String(), "priority 2 rule has expected requestUrisToExclude") - } } spRule3 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 3 --security-policy=%s --project %s", policyName, projectId)) diff --git a/variables.tf b/variables.tf index d333e88..55f7d21 100644 --- a/variables.tf +++ b/variables.tf @@ -43,7 +43,7 @@ variable "recaptcha_redirect_site_key" { } variable "pre_configured_rules" { - description = "Map of pre-configured rules Sensitivity levels" + description = "Map of pre-configured rules with Sensitivity levels. preconfigured_waf_config_exclusion is obsolete and available for backward compatibility. Use preconfigured_waf_config_exclusions which allows multiple exclusions" type = map(object({ action = string priority = number @@ -94,7 +94,28 @@ variable "pre_configured_rules" { operator = string value = optional(string) }))) - }), { target_rule_set = null }) + }), { target_rule_set = null }) # Obsolete. Use preconfigured_waf_config_exclusions + + preconfigured_waf_config_exclusions = optional(map(object({ + target_rule_set = string + target_rule_ids = optional(list(string), []) + request_header = optional(list(object({ + operator = string + value = optional(string) + }))) + request_cookie = optional(list(object({ + operator = string + value = optional(string) + }))) + request_uri = optional(list(object({ + operator = string + value = optional(string) + }))) + request_query_param = optional(list(object({ + operator = string + value = optional(string) + }))) + })), null) })) @@ -184,7 +205,28 @@ variable "custom_rules" { operator = string value = optional(string) }))) - }), { target_rule_set = null }) + }), { target_rule_set = null }) # Obsolete. Use preconfigured_waf_config_exclusions + + preconfigured_waf_config_exclusions = optional(map(object({ + target_rule_set = string + target_rule_ids = optional(list(string), []) + request_header = optional(list(object({ + operator = string + value = optional(string) + }))) + request_cookie = optional(list(object({ + operator = string + value = optional(string) + }))) + request_uri = optional(list(object({ + operator = string + value = optional(string) + }))) + request_query_param = optional(list(object({ + operator = string + value = optional(string) + }))) + })), null) })) default = {} diff --git a/versions.tf b/versions.tf index 81677ec..3287b72 100644 --- a/versions.tf +++ b/versions.tf @@ -19,11 +19,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.79.0, < 6" + version = ">= 4.79, < 6" } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.79.0, < 6" + version = ">= 4.79, < 6" } } provider_meta "google" {