diff --git a/README.md b/README.md index f87c6ce..c420ae9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ module security_polcy { name = my-test-ca-policy description = "Test Cloud Armor security policy with preconfigured rules, security rules and custom rules" default_rule_action = "deny(403)" + recaptcha_redirect_site_key = google_recaptcha_enterprise_key.primary.name pre_configured_rules = {} security_rules = {} custom_rules = {} @@ -27,8 +28,10 @@ module security_polcy { } ``` +Rule details and Sample Code for each type of rule is available [here](#Rules) + ## Usage -There are examples included in the [examples](https://github.com/terraform-google-modules/terraform-google-cloud-armor/tree/master/examples) folder but simple usage is as follows: +There are examples included in the [examples](https://github.com/GoogleCloudPlatform/terraform-google-cloud-armor/tree/main/examples) folder but simple usage is as follows: ``` module "security_policy" { @@ -38,6 +41,7 @@ module "security_policy" { project_id = var.project_id name = "my-test-security-policy" description = "Test Security Policy" + recaptcha_redirect_site_key = google_recaptcha_enterprise_key.primary.name default_rule_action = "allow" type = "CLOUD_ARMOR" layer_7_ddos_defense_enable = true @@ -185,6 +189,28 @@ module "security_policy" { } } + allow_path_token_header = { + action = "allow" + priority = 25 + description = "Allow path and token match with addition of header" + + expression = <<-EOT + request.path.matches('/login.html') && token.recaptcha_session.score < 0.2 + EOT + + header_action = [ + { + header_name = "reCAPTCHA-Warning" + header_value = "high" + }, + { + header_name = "X-Resource" + header_value = "test" + } + ] + + } + deny_java_level3_with_exclude = { action = "deny(502)" priority = 100 @@ -206,16 +232,16 @@ module "security_policy" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| 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)
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)
}),
{})
}))
| `{}` | 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)
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 | | 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 | | layer\_7\_ddos\_defense\_enable | (Optional) If set to true, enables CAAP for L7 DDoS detection | `bool` | `false` | no | | layer\_7\_ddos\_defense\_rule\_visibility | (Optional) Rule visibility can be one of the following: STANDARD - opaque rules. PREMIUM - transparent rules | `string` | `"STANDARD"` | 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)
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)
}),
{})
}))
| `{}` | no | +| 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)
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 | | 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)
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)
}),
{})
}))
| `{}` | 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)
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 | | threat\_intelligence\_rules | Map of Threat Intelligence Feed rules | `map(any)` | `{}` | no | | type | Type indicates the intended use of the security policy. Possible values are CLOUD\_ARMOR and CLOUD\_ARMOR\_EDGE | `string` | `"CLOUD_ARMOR"` | no | @@ -229,8 +255,9 @@ module "security_policy" { -### Rules -`pre_configured_rules`, `security_rules`, `custom_rules` and `threat_intelligence_rules` are maps of rules. Each rule is a map which provides details about the rule. For example: +## Rules + +[Pre-Configured Rules](#pre_configured_rules), [Security Rules](#security_rules), [Custom Rules](#custom_rules) and [Threat Intelligence Rules](#threat_intelligence_rules) are maps of rules. Each rule is a map which provides details about the rule. Here is an example of `pre_configured_rules`: ``` "my_rule" = { @@ -239,17 +266,19 @@ module "security_policy" { 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 = [] } ``` -`action, priority, description, preview, rate_limit_options` are common in all the rule types. +`action, priority, description, preview, rate_limit_options, header_action, redirect_type and redirect_target` are common in all the rule types. Some of then are optional and some have default value see [Input](#Inputs). -### Rate limit +## Rate limit `rate_limit_options` is needed for the rules where action is set to `throttle` or `rate_based_ban`. `rate_limit_options` is a map of strings with following key pairs. You can find more details about rate limit [here](https://cloud.google.com/armor/docs/rate-limiting-overview) ``` @@ -264,10 +293,10 @@ rate_limit_options = { } ``` -## pre_configured_rules +## pre_configured_rules List of preconfigured rules are available [here](https://cloud.google.com/armor/docs/waf-rules). Following is the key value pairs for setting up pre configured rules. `include_target_rule_ids` and `exclude_target_rule_ids` are mutually exclusive. If `include_target_rule_ids` is provided, sensitivity_level is automatically set to 0 by the module as it is a [requirement for opt in rule signature](https://cloud.google.com/armor/docs/rule-tuning#opt_in_rule_signatures). `exclude_target_rule_ids` is ignored when `include_target_rule_ids` is provided. -### Format: +### Format: ``` "sqli_sensitivity_level_4" = { @@ -276,16 +305,18 @@ List of preconfigured rules are available [here](https://cloud.google.com/armor/ 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 = [] } ``` -### Sample: +### Sample: ``` pre_configured_rules = { @@ -313,10 +344,10 @@ pre_configured_rules = { ``` -## security_rules: +## security_rules: Set of IP addresses or ranges (IPV4 or IPV6) in CIDR notation to match against inbound traffic. There is a limit of 10 IP ranges per rule. -### Format: +### Format: Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. ``` @@ -327,11 +358,13 @@ Each rule is key value pair where key is a unique name of the rule and value is src_ip_ranges = ["A..B.C.D", "W.X.Y.Z",] preview = false redirect_type = null + redirect_target = null rate_limit_options = {} + header_action = [] } ``` -### Sample: +### Sample: ``` security_rules = { @@ -359,10 +392,10 @@ security_rules = { } ``` -## custom_rules: +## custom_rules: Add Custom Rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference) -### Format: +### Format: Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. ``` @@ -375,11 +408,13 @@ allow_specific_regions = { '[US,AU,BE]'.contains(origin.region_code) EOT redirect_type = null + redirect_target = null rate_limit_options = {} + header_action = [] } ``` -### Sample: +### Sample: ``` custom_rules = { @@ -407,10 +442,10 @@ custom_rules = { } ``` -## 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. -### Format: +### Format: Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. ``` @@ -422,22 +457,24 @@ threat_intelligence_rules = { preview = false feed = "iplist-search-engines-crawlers" redirect_type = null + redirect_target = null rate_limit_options = {} + header_action = [] } } ``` -### Sample: +### Sample: ``` threat_intelligence_rules = { - deny_crawlers_ip = { + deny_malicious_ips = { action = "deny(502)" priority = 31 - description = "Deny IP addresses of search engine crawlers" + description = "Deny IP addresses known to attack web applications" preview = true - feed = "iplist-search-engines-crawlers" + feed = "iplist-known-malicious-ips" } } diff --git a/examples/simple-example/main.tf b/examples/simple-example/main.tf index 94e5c72..f005f4c 100644 --- a/examples/simple-example/main.tf +++ b/examples/simple-example/main.tf @@ -98,6 +98,7 @@ module "cloud_armor" { priority = 13 description = "Rate based ban for address from project dropten as soon as they cross rate limit threshold" src_ip_ranges = ["190.217.68.213/32", "45.116.227.70", ] + rate_limit_options = { exceed_action = "deny(502)" rate_limit_http_request_count = 10 @@ -105,6 +106,7 @@ module "cloud_armor" { ban_duration_sec = 120 enforce_on_key = "ALL" } + } "rate_ban_project_dropthirty" = { @@ -112,6 +114,7 @@ module "cloud_armor" { priority = 14 description = "Rate based ban for address from project dropthirty only if they cross banned threshold" src_ip_ranges = ["190.217.68.213", "45.116.227.70", ] + rate_limit_options = { exceed_action = "deny(502)" rate_limit_http_request_count = 10 @@ -121,6 +124,7 @@ module "cloud_armor" { ban_http_request_interval_sec = 300 enforce_on_key = "ALL" } + } "throttle_project_droptwenty" = { @@ -128,11 +132,13 @@ module "cloud_armor" { priority = 15 description = "Throttle IP addresses from project droptwenty" src_ip_ranges = ["190.217.68.214", "45.116.227.71", ] + rate_limit_options = { exceed_action = "deny(502)" rate_limit_http_request_count = 10 rate_limit_http_request_interval_sec = 60 } + } } @@ -143,40 +149,49 @@ module "cloud_armor" { action = "deny(502)" priority = 21 description = "Deny specific Regions" - expression = <<-EOT + + expression = <<-EOT '[AU,BE]'.contains(origin.region_code) EOT + } deny_specific_ip = { action = "deny(502)" priority = 22 description = "Deny Specific IP address" - expression = <<-EOT + + expression = <<-EOT inIpRange(origin.ip, '47.185.201.155/32') EOT + } throttle_specific_ip = { action = "throttle" priority = 23 description = "Throttle specific IP address in US Region" - expression = <<-EOT + + expression = <<-EOT origin.region_code == "US" && inIpRange(origin.ip, '47.185.201.159/32') EOT + rate_limit_options = { exceed_action = "deny(502)" rate_limit_http_request_count = 10 rate_limit_http_request_interval_sec = 60 } + } rate_ban_specific_ip = { - action = "rate_based_ban" - priority = 24 + action = "rate_based_ban" + priority = 24 + expression = <<-EOT inIpRange(origin.ip, '47.185.201.160/32') EOT + rate_limit_options = { exceed_action = "deny(502)" rate_limit_http_request_count = 10 @@ -186,16 +201,41 @@ module "cloud_armor" { ban_http_request_interval_sec = 600 enforce_on_key = "ALL" } + + } + + allow_path_token_header = { + action = "allow" + priority = 25 + description = "Allow path and token match with addition of header" + + expression = <<-EOT + request.path.matches('/login.html') && token.recaptcha_session.score < 0.2 + EOT + + header_action = [ + { + header_name = "reCAPTCHA-Warning" + header_value = "high" + }, + { + header_name = "X-Resource" + header_value = "test" + } + ] + } - deny_xss_level3_with_exclude = { + deny_java_level3_with_exclude = { action = "deny(502)" priority = 100 - description = "test Sensitivity level policies" + description = "Deny pre-configured rule java-v33-stable at sensitivity level 3" preview = true - expression = <<-EOT + + expression = <<-EOT evaluatePreconfiguredWaf('java-v33-stable', {'sensitivity': 3, 'opt_out_rule_ids': ['owasp-crs-v030301-id944240-java', 'owasp-crs-v030301-id944120-java']}) EOT + } } diff --git a/main.tf b/main.tf index ebc9336..f5f3788 100644 --- a/main.tf +++ b/main.tf @@ -76,6 +76,7 @@ resource "google_compute_security_policy" "policy" { } ##### Preconfigured Rules Sensitivity level + dynamic "rule" { for_each = var.pre_configured_rules content { @@ -90,6 +91,20 @@ resource "google_compute_security_policy" "policy" { } } + # Header Action Block. Only if header_action is provided + dynamic "header_action" { + for_each = length(rule.value["header_action"]) == 0 ? [] : ["header_action"] + content { + dynamic "request_headers_to_adds" { + for_each = { for x in rule.value["header_action"] : x.header_name => x } + content { + header_name = request_headers_to_adds.value.header_name + header_value = request_headers_to_adds.value.header_value + } + } + } + } + ### Redirect option dynamic "redirect_options" { for_each = rule.value["action"] == "redirect" ? ["redirect"] : [] @@ -134,6 +149,7 @@ resource "google_compute_security_policy" "policy" { ##### Security Rules IP + dynamic "rule" { for_each = var.security_rules content { @@ -148,6 +164,20 @@ resource "google_compute_security_policy" "policy" { } } + # Header Action Block. Only if header_action is provided + dynamic "header_action" { + for_each = length(rule.value["header_action"]) == 0 ? [] : ["header_action"] + content { + dynamic "request_headers_to_adds" { + for_each = { for x in rule.value["header_action"] : x.header_name => x } + content { + header_name = request_headers_to_adds.value.header_name + header_value = request_headers_to_adds.value.header_value + } + } + } + } + ### Redirect option. Execute only if Action is "redirect" dynamic "redirect_options" { for_each = rule.value["action"] == "redirect" ? ["redirect"] : [] @@ -206,6 +236,21 @@ resource "google_compute_security_policy" "policy" { } } + # Header Action Block. Only if header_action is provided + dynamic "header_action" { + for_each = length(rule.value["header_action"]) == 0 ? [] : ["header_action"] + content { + dynamic "request_headers_to_adds" { + for_each = { for x in rule.value["header_action"] : x.header_name => x } + content { + header_name = request_headers_to_adds.value.header_name + header_value = request_headers_to_adds.value.header_value + } + } + } + } + + # Redirect option block dynamic "redirect_options" { for_each = rule.value["action"] == "redirect" ? ["redirect"] : [] content { @@ -263,6 +308,20 @@ resource "google_compute_security_policy" "policy" { } } + # Header Action Block. Only if header_action is provided + dynamic "header_action" { + for_each = length(rule.value["header_action"]) == 0 ? [] : ["header_action"] + content { + dynamic "request_headers_to_adds" { + for_each = { for x in rule.value["header_action"] : x.header_name => x } + content { + header_name = request_headers_to_adds.value.header_name + header_value = request_headers_to_adds.value.header_value + } + } + } + } + ### Rate limit. Execute only if Action is "rate_based_ban" or "throttle" dynamic "rate_limit_options" { for_each = rule.value["action"] == "rate_based_ban" || rule.value["action"] == "throttle" ? ["rate_limits"] : [] diff --git a/variables.tf b/variables.tf index 503e743..e98afd5 100644 --- a/variables.tf +++ b/variables.tf @@ -65,6 +65,10 @@ variable "pre_configured_rules" { ban_http_request_interval_sec = optional(number) }), {}) + header_action = optional(list(object({ + header_name = optional(string) + header_value = optional(string) + })), []) })) default = {} } @@ -89,6 +93,10 @@ variable "security_rules" { ban_http_request_interval_sec = optional(number) }), {}) + header_action = optional(list(object({ + header_name = optional(string) + header_value = optional(string) + })), []) })) default = {} } @@ -113,6 +121,10 @@ variable "custom_rules" { ban_http_request_interval_sec = optional(number) }), {}) + header_action = optional(list(object({ + header_name = optional(string) + header_value = optional(string) + })), []) })) default = {} }