From f9a6dd080df87acababfc2ece301bb69e237095a Mon Sep 17 00:00:00 2001 From: Imran Nayer Date: Thu, 29 Aug 2024 10:31:45 -0500 Subject: [PATCH] feat!: add sub-module for regional backend security policy (#126) --- README.md | 15 +- build/int.cloudbuild.yaml | 107 +++- docs/upgrading_to_v3.0.md | 79 +++ .../README.md | 12 +- .../main.tf | 57 +- .../outputs.tf | 5 - .../README.md | 3 +- .../main.tf | 18 +- .../outputs.tf | 5 - .../README.md | 3 +- .../main.tf | 46 +- .../outputs.tf | 5 - .../README.md | 1 - .../main.tf | 18 +- .../outputs.tf | 5 - .../global-edge-security-policy/README.md | 1 - examples/global-edge-security-policy/main.tf | 2 +- .../global-edge-security-policy/outputs.tf | 5 - .../README.md | 11 +- .../main.tf | 7 +- .../network.tf | 0 .../nlb.tf | 0 .../outputs.tf | 22 +- .../variables.tf | 5 - .../vm.tf | 0 .../README.md | 2 +- .../main.tf | 2 +- .../README.md | 36 ++ .../main.tf | 277 +++++++++ .../outputs.tf | 25 + .../variables.tf | 20 + .../README.md | 5 +- .../main.tf | 4 +- .../outputs.tf | 8 +- main.tf | 80 +-- .../README.md | 21 +- .../variables.tf | 4 +- .../versions.tf | 4 +- .../network-edge-security-policy/README.md | 19 +- modules/network-edge-security-policy/main.tf | 14 +- .../network-edge-security-policy/variables.tf | 16 +- .../network-edge-security-policy/versions.tf | 4 +- .../README.md | 528 ++++++++++++++++++ .../regional-backend-security-policy/main.tf | 336 +++++++++++ .../outputs.tf | 25 + .../variables.tf | 172 ++++++ .../versions.tf | 35 ++ .../security_policy_test.go | 2 +- .../security_policy_enterprise_test.go | 63 +++ .../simple_example_test.go | 6 +- .../security_policy_recaptcha_test.go | 44 ++ .../security_policy_edge_test.go | 4 +- .../regional_network_security_policy_test.go | 53 ++ .../regional_backend_security_policy_test.go | 203 +++++++ test/setup/main.tf | 8 + variables.tf | 44 +- versions.tf | 4 +- 57 files changed, 2208 insertions(+), 292 deletions(-) create mode 100644 docs/upgrading_to_v3.0.md rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/README.md (75%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/main.tf (95%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/network.tf (100%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/nlb.tf (100%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/outputs.tf (78%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/variables.tf (81%) rename examples/{regional-adv-ddos-and-edge-security-policy-complete => regional-adv-ddos-and-network-edge-security-policy-complete}/vm.tf (100%) create mode 100644 examples/regional-backend-security-policy-example/README.md create mode 100644 examples/regional-backend-security-policy-example/main.tf create mode 100644 examples/regional-backend-security-policy-example/outputs.tf create mode 100644 examples/regional-backend-security-policy-example/variables.tf create mode 100644 modules/regional-backend-security-policy/README.md create mode 100644 modules/regional-backend-security-policy/main.tf create mode 100644 modules/regional-backend-security-policy/outputs.tf create mode 100644 modules/regional-backend-security-policy/variables.tf create mode 100644 modules/regional-backend-security-policy/versions.tf create mode 100644 test/integration/global-backend-security-policy-enterprise/security_policy_enterprise_test.go create mode 100644 test/integration/global-backend-security-policy-recaptcha/security_policy_recaptcha_test.go create mode 100644 test/integration/regional-adv-ddos-and-network-edge-security-policy-complete/regional_network_security_policy_test.go create mode 100644 test/integration/regional-backend-security-policy-example/regional_backend_security_policy_test.go diff --git a/README.md b/README.md index 7d26fd7..095563f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Cloud Armor Terraform Module -This module makes it easy to setup [Cloud Armor global Security Policy](https://cloud.google.com/armor/docs/cloud-armor-overview#security_policies) with Security rules. You can attach the global Security Policy policy to backend services exposed by the following load balancer types: +This module makes it easy to setup [Cloud Armor Global Backend Security Policy](https://cloud.google.com/armor/docs/security-policy-overview#expandable-1) with Security rules. You can attach the global Security policy to the backend services exposed by the following load balancer types: - Global external Application Load Balancer (HTTP/HTTPS) - Classic Application Load Balancer (HTTP/HTTPS) - Global external proxy Network Load Balancer (TCP/SSL) @@ -7,13 +7,14 @@ This module makes it easy to setup [Cloud Armor global Security Policy](https:// 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. +2) [Security Rules](#security_rules): Allow or Deny traffic from list of IP addresses or IP address 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). **NOTE:** For `external passthrough Network Load Balancers`, `protocol forwarding` and `VMs with public IP addresses` create [network Edge Security policy](https://cloud.google.com/armor/docs/security-policy-overview#network-edge-policies) using [advanced network DDoS protection](./modules/advanced-network-ddos-protection/) and [network edge security policy](./modules/network-edge-security-policy/) sub-modules. +**NOTE:** For `Regional external Application Load Balancer` and `Regional internal Application Load Balancer` create [Regionl Backend Security policy](https://cloud.google.com/armor/docs/security-policy-overview#expandable-2) using [regional backend serity policy sub-module](./modules/regional-backend-security-policy/). ## Compatibility @@ -26,6 +27,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) +- [2.X -> 3.X.](/docs/upgrading_to_v3.0.md) ## Module Format @@ -59,8 +61,8 @@ There are examples included in the [examples](https://github.com/GoogleCloudPlat ``` module "security_policy" { - source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.2" + source = "GoogleCloudPlatform/cloud-armor/google" + version = "~> 3.0" project_id = var.project_id name = "my-test-security-policy" @@ -225,7 +227,7 @@ resource "google_compute_backend_service" "backend_service" { | 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 }) # 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 | +| 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_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 | @@ -234,7 +236,7 @@ resource "google_compute_backend_service" "backend_service" { | 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 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 | +| pre\_configured\_rules | Map of pre-configured rules with 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_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 | @@ -302,7 +304,6 @@ rate_limit_options = { ``` ## 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 `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) diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index ef25263..0989e66 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -31,53 +31,114 @@ steps: name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run all --stage init --verbose'] -- id: security-policy-all-apply +- id: global-backend-security-policy-complete-apply waitFor: - init-all name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyComplete --stage apply --verbose'] -- id: security-policy-all-verify +- id: global-backend-security-policy-complete-verify waitFor: - - security-policy-all-apply + - global-backend-security-policy-complete-apply name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyComplete --stage verify --verbose'] -- id: security-policy-all-teardown +- id: global-backend-security-policy-complete-teardown waitFor: - - security-policy-all-verify + - global-backend-security-policy-complete-verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyComplete --stage teardown --verbose'] -- id: simple-example-apply +- id: global-backend-security-policy-example-apply waitFor: - - security-policy-all-teardown + - global-backend-security-policy-complete-teardown name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSimpleExample --stage apply --verbose'] -- id: simple-example-verify + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyExample --stage apply --verbose'] +- id: global-backend-security-policy-example-verify waitFor: - - simple-example-apply + - global-backend-security-policy-example-apply name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSimpleExample --stage verify --verbose'] -- id: simple-example-teardown + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyExample --stage verify --verbose'] +- id: global-backend-security-policy-example-teardown waitFor: - - simple-example-verify + - global-backend-security-policy-example-verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSimpleExample --stage teardown --verbose'] + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyExample --stage teardown --verbose'] -- id: security-policy-edge-apply +- id: global-edge-security-policy-apply waitFor: - - simple-example-teardown + - global-backend-security-policy-example-teardown name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSecurityPolicyEdge --stage apply --verbose'] -- id: security-policy-edge-verify + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEdge --stage apply --verbose'] +- id: global-edge-security-policy-verify waitFor: - - security-policy-edge-apply + - global-edge-security-policy-apply name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSecurityPolicyEdge --stage verify --verbose'] -- id: security-policy-edge-teardown + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEdge --stage verify --verbose'] +- id: global-edge-security-policy-teardown waitFor: - - security-policy-edge-verify + - global-edge-security-policy-verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cft test run TestSecurityPolicyEdge --stage teardown --verbose'] + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEdge --stage teardown --verbose'] + +- id: global-backend-security-policy-recaptcha-apply + waitFor: + - global-backend-security-policy-example-teardown + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyRecaptcha --stage apply --verbose'] +- id: global-backend-security-policy-recaptcha-verify + waitFor: + - global-backend-security-policy-recaptcha-apply + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyRecaptcha --stage verify --verbose'] +- id: global-backend-security-policy-recaptcha-teardown + waitFor: + - global-backend-security-policy-recaptcha-verify + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyRecaptcha --stage teardown --verbose'] +- id: global-backend-security-policy-enterprise-apply + waitFor: + - global-backend-security-policy-example-teardown + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEnterprise --stage apply --verbose'] +- id: global-backend-security-policy-enterprise-verify + waitFor: + - global-backend-security-policy-enterprise-apply + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEnterprise --stage verify --verbose'] +- id: global-backend-security-policy-enterprise-teardown + waitFor: + - global-backend-security-policy-enterprise-verify + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestGlobalSecurityPolicyEnterprise --stage teardown --verbose'] +- id: regional-adv-ddos-and-edge-security-policy-complete-apply + waitFor: + - global-backend-security-policy-example-teardown + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalNetworkEdgePolicy --stage apply --verbose'] +- id: regional-adv-ddos-and-edge-security-policy-complete-verify + waitFor: + - regional-adv-ddos-and-edge-security-policy-complete-apply + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalNetworkEdgePolicy --stage verify --verbose'] +- id: regional-adv-ddos-and-edge-security-policy-complete-teardown + waitFor: + - regional-adv-ddos-and-edge-security-policy-complete-verify + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalNetworkEdgePolicy --stage teardown --verbose'] +- id: regional-backend-security-policy-example-apply + waitFor: + - global-backend-security-policy-example-teardown + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalBackendPolicy --stage apply --verbose'] +- id: regional-backend-security-policy-example-verify + waitFor: + - regional-backend-security-policy-example-apply + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalBackendPolicy --stage verify --verbose'] +- id: regional-backend-security-policy-example-teardown + waitFor: + - regional-backend-security-policy-example-verify + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestRegionalBackendPolicy --stage teardown --verbose'] tags: - 'ci' diff --git a/docs/upgrading_to_v3.0.md b/docs/upgrading_to_v3.0.md new file mode 100644 index 0000000..cc6711c --- /dev/null +++ b/docs/upgrading_to_v3.0.md @@ -0,0 +1,79 @@ +# Upgrading to v3.0.0 + +The v3.0 release contains backwards-incompatible changes. + +This update changed max provider version from `5.X` to `6.X`. + +### TPG max version is bumped to 6.x +There is no known breaking change for Cloud Armor in 6.X. + +### Remove preconfigured_waf_config_exclusion +`preconfigured_waf_config_exclusion` was deprecated in [v2.1](./upgrading_to_v2.1.md). It is now removed from the module. Before upgrading to 3.X move `preconfigured_waf_config_exclusion` settings to `preconfigured_waf_config_exclusions`. Here is an example: + + +```tf +"sqli_sensitivity_level_4" = { + 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_exclusion = { +- 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" +- } +- ] +- } +} +``` + +```tf +"sqli_sensitivity_level_4" = { + 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" + + # 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" ++ } ++ ] ++ } ++ ++ } +} +``` + diff --git a/examples/global-backend-security-policy-complete/README.md b/examples/global-backend-security-policy-complete/README.md index e9f3c62..d86d106 100644 --- a/examples/global-backend-security-policy-complete/README.md +++ b/examples/global-backend-security-policy-complete/README.md @@ -1,9 +1,14 @@ -# Cloud Armor Policy with preconfigured rules, custom rules and security rules +# Cloud Armor Policy end to end example This example performs the following: - Network (VPC/Subnets/Firewall-rules/NAT). -- Creates a `global cloud armor security policy`. -- Creates a VM instance behind a `global external application load balancer`. +- A `global cloud armor security policy` with following types of rules. + - Threat Intelligence Rules (Requires [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview). Remove these rules if you dont have Cloud Armor Enterprise enabled for your project) + - Rule for Automatically deploying Adaptive Protection suggested rules (Requires [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview). Remove these rules if you dont have Cloud Armor Enterprise enabled for your project) + - Pre-configured rules + - Custom rules + - Security rules +- A VM instance behind a `global external application load balancer`. - Attaches `security policy` to the backend service by passing security policy link in `security_policy` parameter in `google_compute_backend_service` resource. ## Usage @@ -32,7 +37,6 @@ terraform apply | Name | Description | |------|-------------| | policy\_name | Security Policy name | -| project\_id | The project ID | | security\_policy | Cloud Armor security policy created | diff --git a/examples/global-backend-security-policy-complete/main.tf b/examples/global-backend-security-policy-complete/main.tf index 6e352ea..dbbc455 100644 --- a/examples/global-backend-security-policy-complete/main.tf +++ b/examples/global-backend-security-policy-complete/main.tf @@ -24,7 +24,7 @@ resource "random_id" "suffix" { } module "cloud_armor" { source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id name = "test-casp-policy-${random_id.suffix.hex}" @@ -35,6 +35,30 @@ module "cloud_armor" { layer_7_ddos_defense_rule_visibility = "STANDARD" user_ip_request_headers = ["True-Client-IP", ] + # preconfigured WAF rules + pre_configured_rules = { + + "xss-stable_level_2_with_exclude" = { + action = "deny(502)" + priority = 2 + 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"] + } + + "php-stable_level_0_with_include" = { + action = "deny(502)" + priority = 3 + description = "PHP Sensitivity Level 0 with included rules" + target_rule_set = "php-v33-stable" + include_target_rule_ids = ["owasp-crs-v030301-id933190-php", "owasp-crs-v030301-id933111-php"] + } + + } + + + # Security Rules for blocking IP addresses security_rules = { "allow_whitelisted_ip_ranges" = { action = "allow" @@ -82,6 +106,7 @@ module "cloud_armor" { } + #Custom Rules custom_rules = { allow_specific_regions = { action = "allow" @@ -132,4 +157,34 @@ module "cloud_armor" { } + #adaptive protection auto deploy rules + adaptive_protection_auto_deploy = { + enable = true + priority = 100000 + action = "deny(403)" + load_threshold = 0.3 + confidence_threshold = 0.6 + } + + # Rules based on threat intelligence + threat_intelligence_rules = { + + deny_malicious_ips = { + action = "deny(502)" + priority = 300 + description = "Deny IP addresses known to attack web applications" + preview = false + feed = "iplist-known-malicious-ips" + exclude_ip = "['47.100.100.100', '47.189.12.139']" + } + + deny_tor_exit_ips = { + action = "deny(502)" + priority = 400 + description = "Deny Tor exit nodes IP addresses" + preview = false + feed = "iplist-tor-exit-nodes" + } + } + } diff --git a/examples/global-backend-security-policy-complete/outputs.tf b/examples/global-backend-security-policy-complete/outputs.tf index e666783..75193d7 100644 --- a/examples/global-backend-security-policy-complete/outputs.tf +++ b/examples/global-backend-security-policy-complete/outputs.tf @@ -19,11 +19,6 @@ output "security_policy" { description = "Cloud Armor security policy created" } -output "project_id" { - value = var.project_id - description = "The project ID" -} - output "policy_name" { value = module.cloud_armor.policy.name description = "Security Policy name" diff --git a/examples/global-backend-security-policy-enterprise/README.md b/examples/global-backend-security-policy-enterprise/README.md index 9d19f0c..eb3e9df 100644 --- a/examples/global-backend-security-policy-enterprise/README.md +++ b/examples/global-backend-security-policy-enterprise/README.md @@ -1,6 +1,6 @@ # Cloud Armor Policy with rules supported by [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) -This example configures a single cloud armor policy with following types of rules which are only availalable to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview): +This example configures a single cloud armor policy with following types of rules which are only available to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview): - Threat Intelligence Rules - Rule for Automatically deploying Adaptive Protection suggested rules @@ -32,7 +32,6 @@ terraform apply | Name | Description | |------|-------------| | policy\_name | Security Policy name | -| project\_id | The project ID | | security\_policy | Cloud Armor security policy created | diff --git a/examples/global-backend-security-policy-enterprise/main.tf b/examples/global-backend-security-policy-enterprise/main.tf index da68838..e675492 100644 --- a/examples/global-backend-security-policy-enterprise/main.tf +++ b/examples/global-backend-security-policy-enterprise/main.tf @@ -19,15 +19,15 @@ resource "random_id" "suffix" { } module "cloud_armor" { source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.0" - - project_id = var.project_id - name = "test-camp-policy-${random_id.suffix.hex}" - description = "Test Cloud Armor security policy with with rules supported by Cloud Armor Managed Protection Plus (CAMP+)" - default_rule_action = "allow" - type = "CLOUD_ARMOR" - layer_7_ddos_defense_enable = true - layer_7_ddos_defense_rule_visibility = "PREMIUM" + version = "~> 3.0" + + project_id = var.project_id + name = "test-camp-policy-${random_id.suffix.hex}" + description = "Test Cloud Armor security policy with with rules supported by Cloud Armor Enterprise (Former Managed Protection Plus - CAMP+)" + default_rule_action = "allow" + type = "CLOUD_ARMOR" + layer_7_ddos_defense_enable = true + user_ip_request_headers = ["True-Client-IP", ] ## This is an example of deny policy. Examples for redirect and throttle policies are in README. adaptive_protection_auto_deploy = { diff --git a/examples/global-backend-security-policy-enterprise/outputs.tf b/examples/global-backend-security-policy-enterprise/outputs.tf index e666783..75193d7 100644 --- a/examples/global-backend-security-policy-enterprise/outputs.tf +++ b/examples/global-backend-security-policy-enterprise/outputs.tf @@ -19,11 +19,6 @@ output "security_policy" { description = "Cloud Armor security policy created" } -output "project_id" { - value = var.project_id - description = "The project ID" -} - output "policy_name" { value = module.cloud_armor.policy.name description = "Security Policy name" diff --git a/examples/global-backend-security-policy-example/README.md b/examples/global-backend-security-policy-example/README.md index d913932..00fd053 100644 --- a/examples/global-backend-security-policy-example/README.md +++ b/examples/global-backend-security-policy-example/README.md @@ -4,6 +4,8 @@ This example configures a single cloud armor policy with following types of rule - Pre-configured rules - Custom rules - Security rules +- Threat Intelligence Rules (Requires [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview). Remove these rules if you dont have Cloud Armor Enterprise enabled for your project) +- Rule for Automatically deploying Adaptive Protection suggested rules (Requires [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview). Remove these rules if you dont have Cloud Armor Enterprise enabled for your project) ## Usage @@ -31,7 +33,6 @@ terraform apply | Name | Description | |------|-------------| | policy\_name | Security Policy name | -| project\_id | The project ID | | security\_policy | Cloud Armor security policy created | diff --git a/examples/global-backend-security-policy-example/main.tf b/examples/global-backend-security-policy-example/main.tf index 132e0a9..216d6b5 100644 --- a/examples/global-backend-security-policy-example/main.tf +++ b/examples/global-backend-security-policy-example/main.tf @@ -20,7 +20,7 @@ resource "random_id" "suffix" { module "cloud_armor" { source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id name = "test-casp-policy-${random_id.suffix.hex}" @@ -33,6 +33,7 @@ module "cloud_armor" { log_level = "VERBOSE" user_ip_request_headers = ["True-Client-IP", ] + # preconfigured WAF rules pre_configured_rules = { "sqli_sensitivity_level_4" = { @@ -41,8 +42,6 @@ module "cloud_armor" { 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" @@ -88,7 +87,6 @@ module "cloud_armor" { }, ] } - } } @@ -111,6 +109,7 @@ module "cloud_armor" { } + #Security rules blocking IP addresses security_rules = { "deny_project_honeypot" = { @@ -144,7 +143,6 @@ module "cloud_armor" { enforce_on_key = "HTTP_HEADER" enforce_on_key_name = "X-API-KEY" } - } "rate_ban_project_dropthirty" = { @@ -162,7 +160,6 @@ module "cloud_armor" { ban_http_request_interval_sec = 300 enforce_on_key = "ALL" } - } "throttle_project_droptwenty" = { @@ -185,11 +182,11 @@ module "cloud_armor" { } ] } - } } + #Custom Rules custom_rules = { deny_specific_regions = { @@ -200,7 +197,6 @@ module "cloud_armor" { expression = <<-EOT '[AU,BE]'.contains(origin.region_code) EOT - } deny_specific_ip = { @@ -211,7 +207,6 @@ module "cloud_armor" { expression = <<-EOT inIpRange(origin.ip, '47.185.201.155/32') EOT - } throttle_specific_ip = { @@ -228,7 +223,6 @@ module "cloud_armor" { rate_limit_http_request_count = 10 rate_limit_http_request_interval_sec = 60 } - } rate_ban_specific_ip = { @@ -248,7 +242,6 @@ module "cloud_armor" { ban_http_request_interval_sec = 600 enforce_on_key = "ALL" } - } allow_path_token_header = { @@ -270,7 +263,6 @@ module "cloud_armor" { header_value = "test" } ] - } deny_java_level3_with_exclude = { @@ -282,7 +274,6 @@ module "cloud_armor" { expression = <<-EOT evaluatePreconfiguredWaf('java-v33-stable', {'sensitivity': 3, 'opt_out_rule_ids': ['owasp-crs-v030301-id944240-java', 'owasp-crs-v030301-id944120-java']}) EOT - } "methodenforcement-v33-stable_level_1" = { @@ -303,7 +294,36 @@ module "cloud_armor" { ] } } + } + + #adaptive protection auto deploy rules + adaptive_protection_auto_deploy = { + enable = true + priority = 100000 + action = "deny(403)" + load_threshold = 0.3 + confidence_threshold = 0.6 + } + + # Rules based on threat intelligence + threat_intelligence_rules = { + + deny_malicious_ips = { + action = "deny(502)" + priority = 300 + description = "Deny IP addresses known to attack web applications" + preview = false + feed = "iplist-known-malicious-ips" + exclude_ip = "['47.100.100.100', '47.189.12.139']" + } + deny_tor_exit_ips = { + action = "deny(502)" + priority = 400 + description = "Deny Tor exit nodes IP addresses" + preview = false + feed = "iplist-tor-exit-nodes" + } } } diff --git a/examples/global-backend-security-policy-example/outputs.tf b/examples/global-backend-security-policy-example/outputs.tf index e666783..75193d7 100644 --- a/examples/global-backend-security-policy-example/outputs.tf +++ b/examples/global-backend-security-policy-example/outputs.tf @@ -19,11 +19,6 @@ output "security_policy" { description = "Cloud Armor security policy created" } -output "project_id" { - value = var.project_id - description = "The project ID" -} - output "policy_name" { value = module.cloud_armor.policy.name description = "Security Policy name" diff --git a/examples/global-backend-security-policy-recaptcha/README.md b/examples/global-backend-security-policy-recaptcha/README.md index 230d778..56e3d07 100644 --- a/examples/global-backend-security-policy-recaptcha/README.md +++ b/examples/global-backend-security-policy-recaptcha/README.md @@ -28,7 +28,6 @@ terraform apply | Name | Description | |------|-------------| | policy\_name | Security Policy name | -| project\_id | The project ID | | security\_policy | Cloud Armor security policy created | diff --git a/examples/global-backend-security-policy-recaptcha/main.tf b/examples/global-backend-security-policy-recaptcha/main.tf index 48ace6d..cd0767f 100644 --- a/examples/global-backend-security-policy-recaptcha/main.tf +++ b/examples/global-backend-security-policy-recaptcha/main.tf @@ -36,14 +36,14 @@ resource "random_id" "suffix" { module "cloud_armor" { source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.0" + version = "~> 3.0" - project_id = var.project_id - name = "test-policy-recaptcha-${random_id.suffix.hex}" - description = "Test Cloud Armor security policy with preconfigured rules, security rules and custom rules" - default_rule_action = "allow" - type = "CLOUD_ARMOR" - layer_7_ddos_defense_enable = true - layer_7_ddos_defense_rule_visibility = "STANDARD" - recaptcha_redirect_site_key = google_recaptcha_enterprise_key.primary.name + project_id = var.project_id + name = "test-policy-recaptcha-${random_id.suffix.hex}" + description = "Test Cloud Armor security policy with Recaptcha Enterprise" + default_rule_action = "allow" + type = "CLOUD_ARMOR" + layer_7_ddos_defense_enable = true + recaptcha_redirect_site_key = google_recaptcha_enterprise_key.primary.name + user_ip_request_headers = ["True-Client-IP", ] } diff --git a/examples/global-backend-security-policy-recaptcha/outputs.tf b/examples/global-backend-security-policy-recaptcha/outputs.tf index e666783..75193d7 100644 --- a/examples/global-backend-security-policy-recaptcha/outputs.tf +++ b/examples/global-backend-security-policy-recaptcha/outputs.tf @@ -19,11 +19,6 @@ output "security_policy" { description = "Cloud Armor security policy created" } -output "project_id" { - value = var.project_id - description = "The project ID" -} - output "policy_name" { value = module.cloud_armor.policy.name description = "Security Policy name" diff --git a/examples/global-edge-security-policy/README.md b/examples/global-edge-security-policy/README.md index e51d288..d508d6d 100644 --- a/examples/global-edge-security-policy/README.md +++ b/examples/global-edge-security-policy/README.md @@ -28,7 +28,6 @@ terraform apply | Name | Description | |------|-------------| | policy\_name | Security Policy name | -| project\_id | The project ID | | security\_policy | Cloud Armor security policy created | diff --git a/examples/global-edge-security-policy/main.tf b/examples/global-edge-security-policy/main.tf index 4157b28..c800213 100644 --- a/examples/global-edge-security-policy/main.tf +++ b/examples/global-edge-security-policy/main.tf @@ -19,7 +19,7 @@ resource "random_id" "suffix" { } module "cloud_armor" { source = "GoogleCloudPlatform/cloud-armor/google" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id name = "test-casp-edge-policy-${random_id.suffix.hex}" diff --git a/examples/global-edge-security-policy/outputs.tf b/examples/global-edge-security-policy/outputs.tf index e666783..75193d7 100644 --- a/examples/global-edge-security-policy/outputs.tf +++ b/examples/global-edge-security-policy/outputs.tf @@ -19,11 +19,6 @@ output "security_policy" { description = "Cloud Armor security policy created" } -output "project_id" { - value = var.project_id - description = "The project ID" -} - output "policy_name" { value = module.cloud_armor.policy.name description = "Security Policy name" diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/README.md b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/README.md similarity index 75% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/README.md rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/README.md index fffb8fd..277168c 100644 --- a/examples/regional-adv-ddos-and-edge-security-policy-complete/README.md +++ b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/README.md @@ -1,5 +1,7 @@ # End to end example for Cloud Armor Advanced Network DDoS Protection & Network Edge Security Policy +Advanced network DDoS protection and network edge security policy is only available to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) + This example performs the following: - Network (VPC/Subnets/Firewall-rules/NAT). - Enables `advanced network DDoS protection` in two regions `us-central1` and `us-east1`. @@ -7,8 +9,6 @@ This example performs the following: - Creates a VM instance behind a `network load balancer`. - Attaches `network edge security policy` to the backend service by passing security policy link in `security_policy` parameter in `google_compute_region_backend_service` resource. -Advanced network DDoS protection and network edge security policy is only availalable to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) - ## Usage To run this example you need to execute: @@ -31,7 +31,6 @@ terraform apply | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | project\_id | The project in which the resource belongs | `string` | n/a | yes | -| whitelisted\_ingress\_ip\_ranges | whitelisted ingress ip ranges. Replace it with your own IP address | `list(string)` | n/a | yes | ## Outputs @@ -39,8 +38,8 @@ terraform apply |------|-------------| | adv\_ddos\_protection\_policies | Advanced Network DDoS protection Security policies created | | network\_edge\_security\_services | Network edge security services created | -| policy\_rules | Security policy rules created | -| security\_policy | Regional Network Security policy created | -| test\_nlb\_url | Use this command to test access to the load balancer. Try it from the IP address provided in whitelisted\_ingress\_ip\_ranges and a different IP address | +| policy\_name | Name of Regional Network Security policy created | +| region | Name of Regional Network Security policy created | +| test\_nlb\_url | Use this command to test access to the load balancer. Try it from the IP address provided in rule 100 and a different IP address | diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/main.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/main.tf similarity index 95% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/main.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/main.tf index b1be03f..864d18d 100644 --- a/examples/regional-adv-ddos-and-edge-security-policy-complete/main.tf +++ b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/main.tf @@ -25,7 +25,7 @@ resource "random_id" "suffix" { module "advanced_network_ddos_protection" { source = "GoogleCloudPlatform/cloud-armor/google//modules/advanced-network-ddos-protection" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id regions = [local.primary_region, local.secondary_region] @@ -35,7 +35,7 @@ module "advanced_network_ddos_protection" { module "network_edge_security_policy" { source = "GoogleCloudPlatform/cloud-armor/google//modules/network-edge-security-policy" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id region = local.primary_region @@ -47,7 +47,7 @@ module "network_edge_security_policy" { action = "allow" preview = false description = "custom rule 100" - src_ip_ranges = var.whitelisted_ingress_ip_ranges + src_ip_ranges = ["70.119.66.60/32"] src_region_codes = ["US"] dest_ports = [80] }, @@ -58,6 +58,7 @@ module "network_edge_security_policy" { src_ip_ranges = ["*"] }, ] + depends_on = [ module.advanced_network_ddos_protection ] diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/network.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/network.tf similarity index 100% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/network.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/network.tf diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/nlb.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/nlb.tf similarity index 100% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/nlb.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/nlb.tf diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/outputs.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/outputs.tf similarity index 78% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/outputs.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/outputs.tf index f07b480..c48ea79 100644 --- a/examples/regional-adv-ddos-and-edge-security-policy-complete/outputs.tf +++ b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/outputs.tf @@ -14,6 +14,16 @@ * limitations under the License. */ +output "policy_name" { + value = module.network_edge_security_policy.security_policy.name + description = "Name of Regional Network Security policy created" +} + +output "region" { + value = module.network_edge_security_policy.security_policy.region + description = "Name of Regional Network Security policy created" +} + output "adv_ddos_protection_policies" { value = module.advanced_network_ddos_protection.adv_ddos_protection_policies description = "Advanced Network DDoS protection Security policies created" @@ -24,17 +34,7 @@ output "network_edge_security_services" { description = "Network edge security services created" } -output "security_policy" { - value = module.network_edge_security_policy.security_policy - description = "Regional Network Security policy created" -} - -output "policy_rules" { - value = module.network_edge_security_policy.policy_rules - description = "Security policy rules created" -} - output "test_nlb_url" { value = "curl http://${google_compute_forwarding_rule.default.ip_address}" - description = "Use this command to test access to the load balancer. Try it from the IP address provided in whitelisted_ingress_ip_ranges and a different IP address" + description = "Use this command to test access to the load balancer. Try it from the IP address provided in rule 100 and a different IP address" } diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/variables.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/variables.tf similarity index 81% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/variables.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/variables.tf index ab824e1..8221296 100644 --- a/examples/regional-adv-ddos-and-edge-security-policy-complete/variables.tf +++ b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/variables.tf @@ -18,8 +18,3 @@ variable "project_id" { description = "The project in which the resource belongs" type = string } - -variable "whitelisted_ingress_ip_ranges" { - description = "whitelisted ingress ip ranges. Replace it with your own IP address" - type = list(string) -} diff --git a/examples/regional-adv-ddos-and-edge-security-policy-complete/vm.tf b/examples/regional-adv-ddos-and-network-edge-security-policy-complete/vm.tf similarity index 100% rename from examples/regional-adv-ddos-and-edge-security-policy-complete/vm.tf rename to examples/regional-adv-ddos-and-network-edge-security-policy-complete/vm.tf diff --git a/examples/regional-advanced-network-ddos-protection-enterprise/README.md b/examples/regional-advanced-network-ddos-protection-enterprise/README.md index 5bc5b7a..065a024 100644 --- a/examples/regional-advanced-network-ddos-protection-enterprise/README.md +++ b/examples/regional-advanced-network-ddos-protection-enterprise/README.md @@ -1,6 +1,6 @@ # Enable Cloud Armor Advanced Network DDoS Protection -This example enables a [advanced network DDoS protection](https://cloud.google.com/armor/docs/armor-enterprise-overview#advanced_network_ddos_protection) in two regions `us-central1` and `us-east1`. `Advanced network DDoS protection` is only availalable to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) +This example enables a [advanced network DDoS protection](https://cloud.google.com/armor/docs/armor-enterprise-overview#advanced_network_ddos_protection) in two regions `us-central1` and `us-east1`. `Advanced network DDoS protection` is only available to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) ## Usage diff --git a/examples/regional-advanced-network-ddos-protection-enterprise/main.tf b/examples/regional-advanced-network-ddos-protection-enterprise/main.tf index c24028c..07d4cfa 100644 --- a/examples/regional-advanced-network-ddos-protection-enterprise/main.tf +++ b/examples/regional-advanced-network-ddos-protection-enterprise/main.tf @@ -20,7 +20,7 @@ resource "random_id" "suffix" { module "advanced_network_ddos_protection" { source = "GoogleCloudPlatform/cloud-armor/google//modules/advanced-network-ddos-protection" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id regions = ["us-central1", "us-east1"] diff --git a/examples/regional-backend-security-policy-example/README.md b/examples/regional-backend-security-policy-example/README.md new file mode 100644 index 0000000..cf3b671 --- /dev/null +++ b/examples/regional-backend-security-policy-example/README.md @@ -0,0 +1,36 @@ +# Regional Cloud Armor Policy with preconfigured rules, custom rules and security rules + +This example configures a single cloud armor policy with following types of rules: +- Pre-configured rules +- Custom rules +- Security rules + +## Usage + +To run this example you need to execute: + +```bash +export TF_VAR_project_id="your_project_id" +``` + +```bash +terraform init +terraform plan +terraform apply +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| project\_id | The project in which the resource belongs | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| policy\_name | Security Policy name | +| region | Name of Regional Network Security policy created | + + diff --git a/examples/regional-backend-security-policy-example/main.tf b/examples/regional-backend-security-policy-example/main.tf new file mode 100644 index 0000000..db0f8e8 --- /dev/null +++ b/examples/regional-backend-security-policy-example/main.tf @@ -0,0 +1,277 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "random_id" "suffix" { + byte_length = 4 +} + +module "cloud_armor_regional_security_policy" { + source = "GoogleCloudPlatform/cloud-armor/google//modules/regional-backend-security-policy" + version = "~> 3.0" + + project_id = var.project_id + name = "test-regional-external-sp-${random_id.suffix.hex}" + description = "Test regional Cloud Armor backend security policy with preconfigured rules, security rules and custom rules" + type = "CLOUD_ARMOR" + region = "us-central1" + + # pre-configured WAF rules + + pre_configured_rules = { + + "sqli_sensitivity_level_4" = { + 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" + + # 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" + }, + ] + } + } + } + + "xss-stable_level_2_with_exclude" = { + action = "deny(502)" + priority = 2 + 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"] + } + + "php-stable_level_0_with_include" = { + action = "deny(502)" + priority = 3 + description = "PHP Sensitivity Level 0 with included rules" + target_rule_set = "php-v33-stable" + include_target_rule_ids = ["owasp-crs-v030301-id933190-php", "owasp-crs-v030301-id933111-php"] + } + + } + + # Security Rules to block IP addresses + + security_rules = { + + "deny_project_honeypot" = { + action = "deny(502)" + priority = 11 + description = "Deny Malicious IP address from project honeypot" + src_ip_ranges = ["190.217.68.211/32", "45.116.227.68/32", "103.43.141.122/32", "123.11.215.36", "123.11.215.37", ] + preview = true + } + + "rate_ban_project_dropten" = { + action = "rate_based_ban" + priority = 12 + 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 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 120 + enforce_on_key = "HTTP_HEADER" + enforce_on_key_name = "X-API-KEY" + } + } + + "rate_ban_project_dropthirty" = { + action = "rate_based_ban" + priority = 13 + 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 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 600 + ban_http_request_count = 1000 + ban_http_request_interval_sec = 300 + enforce_on_key = "ALL" + } + } + + "throttle_project_droptwenty" = { + action = "throttle" + priority = 14 + 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 + enforce_on_key_configs = [ + { + enforce_on_key_type = "HTTP_PATH" + }, + { + enforce_on_key_type = "HTTP_COOKIE" + enforce_on_key_name = "site_id" + } + ] + } + } + } + + # Custom Rules + custom_rules = { + + deny_specific_regions = { + action = "deny(502)" + priority = 21 + description = "Deny specific Regions" + + expression = <<-EOT + '[AU,BE]'.contains(origin.region_code) + EOT + } + + deny_specific_ip = { + action = "deny(502)" + priority = 22 + description = "Deny Specific IP address" + + 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 + 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 + + expression = <<-EOT + inIpRange(origin.ip, '47.185.201.160/32') + EOT + + rate_limit_options = { + exceed_action = "deny(502)" + rate_limit_http_request_count = 10 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 120 + ban_http_request_count = 10000 + 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 + } + + deny_java_level3_with_exclude = { + action = "deny(502)" + priority = 100 + description = "Deny pre-configured rule java-v33-stable at sensitivity level 3" + preview = true + + expression = <<-EOT + evaluatePreconfiguredWaf('java-v33-stable', {'sensitivity': 3, 'opt_out_rule_ids': ['owasp-crs-v030301-id944240-java', 'owasp-crs-v030301-id944120-java']}) + EOT + } + + "methodenforcement-v33-stable_level_1" = { + action = "deny(403)" + priority = 26 + description = "Method enforcement Level 1" + preview = true + expression = "evaluatePreconfiguredWaf('methodenforcement-v33-stable', {'sensitivity': 1}) && !request.path.matches('/keyword/here/')" + + preconfigured_waf_config_exclusion = { + target_rule_set = "methodenforcement-v33-stable" + target_rule_ids = ["owasp-crs-v030301-id911100-methodenforcement"] + request_uri = [ + { + operator = "CONTAINS" + value = "/keyword/here/" + }, + ] + } + } + } + +} diff --git a/examples/regional-backend-security-policy-example/outputs.tf b/examples/regional-backend-security-policy-example/outputs.tf new file mode 100644 index 0000000..5626c3b --- /dev/null +++ b/examples/regional-backend-security-policy-example/outputs.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "policy_name" { + value = module.cloud_armor_regional_security_policy.policy.name + description = "Security Policy name" +} + +output "region" { + value = module.cloud_armor_regional_security_policy.policy.region + description = "Name of Regional Network Security policy created" +} diff --git a/examples/regional-backend-security-policy-example/variables.tf b/examples/regional-backend-security-policy-example/variables.tf new file mode 100644 index 0000000..e11d5d8 --- /dev/null +++ b/examples/regional-backend-security-policy-example/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The project in which the resource belongs" + type = string +} diff --git a/examples/regional-network-edge-security-policy-enterprise/README.md b/examples/regional-network-edge-security-policy-enterprise/README.md index 8ed2062..9a2cc2d 100644 --- a/examples/regional-network-edge-security-policy-enterprise/README.md +++ b/examples/regional-network-edge-security-policy-enterprise/README.md @@ -1,6 +1,6 @@ -# Enable Cloud Armor Network Edge Security Policy +# Cloud Armor Network Edge Security Policy -This example creates [network edge security policy](https://cloud.google.com/armor/docs/network-edge-policies) with policy rules. Feature is only availalable to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) with [Advanced network DDoS protection](https://cloud.google.com/armor/docs/advanced-network-ddos#activate-advanced-ddos-protection) enabled. You can use [this sub-module](../advanced-network-ddos-protection/) to enable `advanced network ddos protection `. See [example](../regional-advanced-network-ddos-protection-enterprise/) for enabling advanced newtork ddos protection. If you need an end to end example for deploying security policy and attach it to backend service see [complete example](../regional-adv-ddos-and-edge-security-policy-complete/) +This example creates [network edge security policy](https://cloud.google.com/armor/docs/network-edge-policies) with policy rules. Feature is only available to projects enrolled in [Cloud Armor Enterprise](https://cloud.google.com/armor/docs/armor-enterprise-overview) with [Advanced network DDoS protection](https://cloud.google.com/armor/docs/advanced-network-ddos#activate-advanced-ddos-protection) enabled. You can use [this sub-module](../advanced-network-ddos-protection/) to enable `advanced network ddos protection `. See [example](../regional-advanced-network-ddos-protection-enterprise/) for enabling advanced network ddos protection. If you need an end to end example for deploying security policy and attach it to backend service see [complete example](../regional-adv-ddos-and-edge-security-policy-complete/) ## Usage @@ -29,6 +29,5 @@ terraform apply |------|-------------| | network\_edge\_security\_policy\_no\_rules | Regional Network Security policy created | | policy\_rules | Security policy rules created | -| security\_policy | Regional Network Security policy created | diff --git a/examples/regional-network-edge-security-policy-enterprise/main.tf b/examples/regional-network-edge-security-policy-enterprise/main.tf index b148c47..0eb0902 100644 --- a/examples/regional-network-edge-security-policy-enterprise/main.tf +++ b/examples/regional-network-edge-security-policy-enterprise/main.tf @@ -20,7 +20,7 @@ resource "random_id" "suffix" { module "network_edge_security_policy" { source = "GoogleCloudPlatform/cloud-armor/google//modules/network-edge-security-policy" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id region = "us-central1" @@ -85,7 +85,7 @@ module "network_edge_security_policy" { module "network_edge_security_policy_no_rules" { source = "GoogleCloudPlatform/cloud-armor/google//modules/network-edge-security-policy" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id region = "us-central1" diff --git a/examples/regional-network-edge-security-policy-enterprise/outputs.tf b/examples/regional-network-edge-security-policy-enterprise/outputs.tf index 630da4f..395a9fb 100644 --- a/examples/regional-network-edge-security-policy-enterprise/outputs.tf +++ b/examples/regional-network-edge-security-policy-enterprise/outputs.tf @@ -14,10 +14,10 @@ * limitations under the License. */ -output "security_policy" { - value = module.network_edge_security_policy.security_policy - description = "Regional Network Security policy created" -} +# output "security_policy" { +# value = module.network_edge_security_policy.security_policy +# description = "Regional Network Security policy created" +# } output "policy_rules" { value = module.network_edge_security_policy.policy_rules diff --git a/main.tf b/main.tf index 8f6f253..e16e1b6 100644 --- a/main.tf +++ b/main.tf @@ -31,6 +31,7 @@ locals { description = policy.description preview = policy.preview redirect_type = policy.redirect_type + redirect_target = policy.redirect_target rate_limit_options = policy.rate_limit_options } if length(policy["include_target_rule_ids"]) > 0 } @@ -50,6 +51,7 @@ locals { description = policy.description preview = policy.preview redirect_type = policy.redirect_type + redirect_target = policy.redirect_target rate_limit_options = policy.rate_limit_options } if length(policy["include_target_rule_ids"]) == 0 && length(policy["exclude_target_rule_ids"]) > 0 } @@ -168,45 +170,6 @@ 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_exclusion.target_rule_set == null ? [] : ["preconfigured_waf_config_exclusion"] - content { - exclusion { - target_rule_set = rule.value.preconfigured_waf_config_exclusion.target_rule_set - target_rule_ids = rule.value.preconfigured_waf_config_exclusion.target_rule_ids - dynamic "request_header" { - for_each = rule.value.preconfigured_waf_config_exclusion.request_header == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_cookie == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_uri == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_query_param == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 - } - } - } - } - } - # 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 @@ -411,45 +374,6 @@ 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_exclusion.target_rule_set == null ? [] : ["preconfigured_waf_config_exclusion"] - content { - exclusion { - target_rule_set = rule.value.preconfigured_waf_config_exclusion.target_rule_set - target_rule_ids = rule.value.preconfigured_waf_config_exclusion.target_rule_ids - dynamic "request_header" { - for_each = rule.value.preconfigured_waf_config_exclusion.request_header == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_cookie == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_uri == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 = rule.value.preconfigured_waf_config_exclusion.request_query_param == null ? {} : { for x in rule.value.preconfigured_waf_config_exclusion.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 - } - } - } - } - } - # 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 diff --git a/modules/advanced-network-ddos-protection/README.md b/modules/advanced-network-ddos-protection/README.md index 8e2611b..fee24c9 100644 --- a/modules/advanced-network-ddos-protection/README.md +++ b/modules/advanced-network-ddos-protection/README.md @@ -16,10 +16,10 @@ There are examples included in the [examples](https://github.com/GoogleCloudPlat ``` module "advanced_network_ddos_protection" { source = "GoogleCloudPlatform/cloud-armor/google//modules/advanced-network-ddos-protection" - version = "~> 2.2" + version = "~> 3.0" - project_id = var.project_id - adv_ddos_policy_regions = ["us-central1", "us-east1"] + project_id = var.project_id + regions = ["us-central1", "us-east1"] } ``` @@ -29,13 +29,13 @@ module "advanced_network_ddos_protection" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| ddos\_protection\_config | Configuration for Google Cloud Armor DDOS Proctection Config. 1) ADVANCED: additional protections for Managed Protection Plus subscribers 2) ADVANCED\_PREVIEW: flag to enable the security policy in preview mode | `string` | `"ADVANCED"` | no | +| ddos\_protection\_config | Configuration for Google Cloud Armor DDOS Proctection Config. 1) ADVANCED: additional protections for Managed Protection Plus subscribers 2) ADVANCED\_PREVIEW: enable the security policy in preview mode | `string` | `"ADVANCED"` | no | | network\_edge\_security\_service\_description | description of edge security service for advanced network ddos protection | `string` | `"edge security service for advanced network ddos protection"` | no | | network\_edge\_security\_service\_name | Name of network edge security service resource for advanced network ddos protection | `string` | `"adv-network-ddos-protection"` | no | | policy\_description | An optional description of advanced network ddos protection security policy | `string` | `"CA Advance DDoS protection"` | no | | policy\_name | Name of the advanced network ddos protection security policy. Name must be 1-63 characters long and match the regular expression a-z? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash | `string` | `"adv-network-ddos-protection"` | no | | project\_id | The project in which the resource belongs. | `string` | n/a | yes | -| regions | The regions in which enable advanced network DDoS protection | `list(string)` | n/a | yes | +| regions | The regions in which advanced network DDoS protection will be activated | `list(string)` | n/a | yes | ## Outputs @@ -45,3 +45,14 @@ module "advanced_network_ddos_protection" { | network\_edge\_security\_services | Network edge security services created | + +## Requirements + +These sections describe requirements for using this module. + +### Software + +The following dependencies must be available: + +- [Terraform][terraform] v1.3+ +- [Terraform Provider for GCP][terraform-provider-gcp] plugin v4.80+ diff --git a/modules/advanced-network-ddos-protection/variables.tf b/modules/advanced-network-ddos-protection/variables.tf index b26162a..6207ca0 100644 --- a/modules/advanced-network-ddos-protection/variables.tf +++ b/modules/advanced-network-ddos-protection/variables.tf @@ -20,12 +20,12 @@ variable "project_id" { } variable "regions" { - description = "The regions in which enable advanced network DDoS protection" + description = "The regions in which advanced network DDoS protection will be activated" type = list(string) } variable "ddos_protection_config" { - description = "Configuration for Google Cloud Armor DDOS Proctection Config. 1) ADVANCED: additional protections for Managed Protection Plus subscribers 2) ADVANCED_PREVIEW: flag to enable the security policy in preview mode" + description = "Configuration for Google Cloud Armor DDOS Proctection Config. 1) ADVANCED: additional protections for Managed Protection Plus subscribers 2) ADVANCED_PREVIEW: enable the security policy in preview mode" type = string default = "ADVANCED" } diff --git a/modules/advanced-network-ddos-protection/versions.tf b/modules/advanced-network-ddos-protection/versions.tf index bdc33b9..f9f343d 100644 --- a/modules/advanced-network-ddos-protection/versions.tf +++ b/modules/advanced-network-ddos-protection/versions.tf @@ -19,11 +19,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.80, < 6" + version = ">= 4.80, < 7" } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.80, < 6" + version = ">= 4.80, < 7" } } provider_meta "google" { diff --git a/modules/network-edge-security-policy/README.md b/modules/network-edge-security-policy/README.md index 4820b1c..c1b8621 100644 --- a/modules/network-edge-security-policy/README.md +++ b/modules/network-edge-security-policy/README.md @@ -8,7 +8,7 @@ You can attch network edge security policy to backend services of [external pass ``` module "network_edge_security_policy" { source = "GoogleCloudPlatform/cloud-armor/google//modules/network-edge-security-policy" - version = "~> 2.2" + version = "~> 3.0" project_id = var.project_id region = "us-central1" @@ -35,7 +35,7 @@ There are examples included in the [examples](https://github.com/GoogleCloudPlat ``` module "network_edge_security_policy" { source = "GoogleCloudPlatform/cloud-armor/google//modules/network-edge-security-policy" - version = "~> 2.0" + version = "~> 3.0" project_id = var.project_id region = "us-central1" @@ -122,10 +122,10 @@ resource "google_compute_region_backend_service" "backend" { |------|-------------|------|---------|:--------:| | policy\_description | An optional description of advanced network ddos protection security policy | `string` | `"CA Advance DDoS protection"` | no | | policy\_name | Name of the advanced network ddos protection security policy. Name must be 1-63 characters long and match the regular expression a-z? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash | `string` | `"adv-network-ddos-protection"` | no | -| policy\_rules | Policy Rules |
list(object({
priority = number
action = string
preview = optional(bool)
description = optional(string)
ip_protocols = optional(list(string))
src_ip_ranges = optional(list(string))
src_asns = optional(list(string))
src_region_codes = optional(list(string))
src_ports = optional(list(string))
dest_ports = optional(list(string))
dest_ip_ranges = optional(list(string))

user_defined_fields = optional(list(object({
name = optional(string)
values = optional(list(string))
})))
}))
| `null` | no | +| policy\_rules | Policy Rules |
list(object({
priority = number
action = string
preview = optional(bool)
description = optional(string)
ip_protocols = optional(list(string), [])
src_ip_ranges = optional(list(string), [])
src_asns = optional(list(string), [])
src_region_codes = optional(list(string), [])
src_ports = optional(list(string), [])
dest_ports = optional(list(string), [])
dest_ip_ranges = optional(list(string), [])

user_defined_fields = optional(list(object({
name = optional(string)
values = optional(list(string))
})))
}))
| `null` | no | | policy\_user\_defined\_fields | Definitions of user-defined fields for CLOUD\_ARMOR\_NETWORK policies. A user-defined field consists of up to 4 bytes extracted from a fixed offset in the packet, relative to the IPv4, IPv6, TCP, or UDP header, with an optional mask to select certain bits |
list(object({
name = optional(string)
base = string
offset = optional(number)
size = optional(number)
mask = optional(string)
}))
| `null` | no | | project\_id | The project in which the resource belongs. | `string` | n/a | yes | -| region | The region in which enablesecurity policy is created | `string` | n/a | yes | +| region | The region in which security policy is created | `string` | n/a | yes | ## Outputs @@ -235,3 +235,14 @@ User-defined fields. Each element names a defined field and lists the matching v - `name`: (Optional) Name of the user-defined field, as given in the definition - `values`: (Optional) Matching values of the field. Each element can be a 32-bit unsigned decimal or hexadecimal (starting with "0x") number (e.g. "64") or range (e.g. "0x400-0x7ff") + +## Requirements + +These sections describe requirements for using this module. + +### Software + +The following dependencies must be available: + +- [Terraform][terraform] v1.3+ +- [Terraform Provider for GCP][terraform-provider-gcp] plugin v4.80+ diff --git a/modules/network-edge-security-policy/main.tf b/modules/network-edge-security-policy/main.tf index 786cd33..5222899 100644 --- a/modules/network-edge-security-policy/main.tf +++ b/modules/network-edge-security-policy/main.tf @@ -48,13 +48,13 @@ resource "google_compute_region_security_policy_rule" "policy_rules" { description = each.value.description priority = each.value.priority network_match { - src_ip_ranges = lookup(each.value, "src_ip_ranges", null) - src_ports = lookup(each.value, "src_ports", null) - src_asns = lookup(each.value, "src_asns", null) - src_region_codes = lookup(each.value, "src_region_codes", null) - ip_protocols = lookup(each.value, "ip_protocols", null) - dest_ports = lookup(each.value, "dest_ports", null) - dest_ip_ranges = lookup(each.value, "dest_ip_ranges", null) + src_ip_ranges = lookup(each.value, "src_ip_ranges", []) + src_ports = lookup(each.value, "src_ports", []) + src_asns = lookup(each.value, "src_asns", []) + src_region_codes = lookup(each.value, "src_region_codes", []) + ip_protocols = lookup(each.value, "ip_protocols", []) + dest_ports = lookup(each.value, "dest_ports", []) + dest_ip_ranges = lookup(each.value, "dest_ip_ranges", []) dynamic "user_defined_fields" { for_each = lookup(each.value, "user_defined_fields", null) == null ? [] : lookup(each.value, "user_defined_fields") content { diff --git a/modules/network-edge-security-policy/variables.tf b/modules/network-edge-security-policy/variables.tf index 03528d6..e96ba95 100644 --- a/modules/network-edge-security-policy/variables.tf +++ b/modules/network-edge-security-policy/variables.tf @@ -20,7 +20,7 @@ variable "project_id" { } variable "region" { - description = "The region in which enablesecurity policy is created" + description = "The region in which security policy is created" type = string } @@ -56,13 +56,13 @@ variable "policy_rules" { action = string preview = optional(bool) description = optional(string) - ip_protocols = optional(list(string)) - src_ip_ranges = optional(list(string)) - src_asns = optional(list(string)) - src_region_codes = optional(list(string)) - src_ports = optional(list(string)) - dest_ports = optional(list(string)) - dest_ip_ranges = optional(list(string)) + ip_protocols = optional(list(string), []) + src_ip_ranges = optional(list(string), []) + src_asns = optional(list(string), []) + src_region_codes = optional(list(string), []) + src_ports = optional(list(string), []) + dest_ports = optional(list(string), []) + dest_ip_ranges = optional(list(string), []) user_defined_fields = optional(list(object({ name = optional(string) diff --git a/modules/network-edge-security-policy/versions.tf b/modules/network-edge-security-policy/versions.tf index 00cd354..d06912b 100644 --- a/modules/network-edge-security-policy/versions.tf +++ b/modules/network-edge-security-policy/versions.tf @@ -19,11 +19,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.80, < 6" + version = ">= 4.80, < 7" } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.80, < 6" + version = ">= 4.80, < 7" } } provider_meta "google" { diff --git a/modules/regional-backend-security-policy/README.md b/modules/regional-backend-security-policy/README.md new file mode 100644 index 0000000..95c397d --- /dev/null +++ b/modules/regional-backend-security-policy/README.md @@ -0,0 +1,528 @@ +# Cloud Armor Regional backend security policy module +This module makes it easy to setup [Cloud Armor Regional Backend Security Policy](https://cloud.google.com/armor/docs/security-policy-overview#expandable-2) with Security rules. You can attach the regional Security policy to the backend services exposed by the following load balancer types: +- regional external Application Load Balancer (HTTP/HTTPS) +- regional internal Application Load Balancer (HTTP/HTTPS) + +There are `three` 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 address 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). + +## Module Format + +``` +module security_policy { + source = "GoogleCloudPlatform/cloud-armor/google" + + project_id = var.project_id + name = "test-regional-external-sp-${random_id.suffix.hex}" + description = "Test regional external Cloud Armor security policy with preconfigured rules, security rules and custom rules" + region = "us-central1" + + pre_configured_rules = {} + security_rules = {} + custom_rules = {} +} +``` + +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/GoogleCloudPlatform/terraform-google-cloud-armor/tree/main/examples) folder but simple usage is as follows: + + +``` +module "cloud_armor_regional_security_policy" { + source = "GoogleCloudPlatform/cloud-armor/google" + version = "~> 3.0" + + project_id = var.project_id + name = "test-regional-external-sp-${random_id.suffix.hex}" + description = "Test regional external Cloud Armor security policy with preconfigured rules, security rules and custom rules" + type = "CLOUD_ARMOR" + region = "us-central1" + + # pre-configured WAF rules + + pre_configured_rules = { + + "xss-stable_level_2_with_exclude" = { + action = "deny(502)" + priority = 2 + 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"] + } + + "php-stable_level_0_with_include" = { + action = "deny(502)" + priority = 3 + description = "PHP Sensitivity Level 0 with included rules" + target_rule_set = "php-v33-stable" + include_target_rule_ids = ["owasp-crs-v030301-id933190-php", "owasp-crs-v030301-id933111-php"] + } + + } + + # Security Rules to block IP addreses + + security_rules = { + + "deny_project_honeypot" = { + action = "deny(502)" + priority = 11 + description = "Deny Malicious IP address from project honeypot" + src_ip_ranges = ["190.217.68.211/32", "45.116.227.68/32", "103.43.141.122/32", "123.11.215.36", "123.11.215.37", ] + preview = true + } + + "rate_ban_project_dropthirty" = { + action = "rate_based_ban" + priority = 13 + 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 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 600 + ban_http_request_count = 1000 + ban_http_request_interval_sec = 300 + enforce_on_key = "ALL" + } + + } + + "throttle_project_droptwenty" = { + action = "throttle" + priority = 14 + 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 + enforce_on_key_configs = [ + { + enforce_on_key_type = "HTTP_PATH" + }, + { + enforce_on_key_type = "HTTP_COOKIE" + enforce_on_key_name = "site_id" + } + ] + } + + } + + } + + # Custom Rules + custom_rules = { + + deny_specific_regions = { + action = "deny(502)" + priority = 21 + description = "Deny specific Regions" + + expression = <<-EOT + '[AU,BE]'.contains(origin.region_code) + EOT + + } + + deny_specific_ip = { + action = "deny(502)" + priority = 22 + description = "Deny Specific IP address" + + 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 + 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 + + expression = <<-EOT + inIpRange(origin.ip, '47.185.201.160/32') + EOT + + rate_limit_options = { + exceed_action = "deny(502)" + rate_limit_http_request_count = 10 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 120 + ban_http_request_count = 10000 + ban_http_request_interval_sec = 600 + enforce_on_key = "ALL" + } + + } + + } + +} +``` + + + +## Inputs + +| 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
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)
}),
{})

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 | +| description | An optional description of advanced network ddos protection security policy | `string` | `"CA Advance DDoS protection"` | no | +| name | Name of regional security policy. Name must be 1-63 characters long and match the regular expression a-z? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash | `string` | `"adv-network-ddos-protection"` | no | +| pre\_configured\_rules | Map of pre-configured rules with Sensitivity levels |
map(object({
action = string
priority = number
description = optional(string)
preview = optional(bool, false)
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)
}), {})

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 | +| region | The region in which security policy is created | `string` | n/a | yes | +| 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)
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)
}),
{})
}))
| `{}` | no | +| type | Type indicates the intended use of the security policy. Possible values are CLOUD\_ARMOR and CLOUD\_ARMOR\_EDGE. | `string` | `"CLOUD_ARMOR"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| policy | Regional network Security policy created | +| security\_rules | Security policy rules created | + + + +## Rules + +[Pre-Configured Rules](#pre_configured_rules), [Security Rules](#security_rules) and [Custom Rules](#custom_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" = { + action = "deny(502)" + priority = 1 + description = "SQL Sensitivity Level 4" + preview = false + target_rule_set = "sqli-v33-stable" + sensitivity_level = 4 + include_target_rule_ids = [] + exclude_target_rule_ids = [] + rate_limit_options = {} + preconfigured_waf_config_exclusions = {} + } +``` + +`action, priority, description, preview and rate_limit_options` are common in all the rule types. Some of then are optional and some have default value see [Input](#Inputs). + +## 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). + +``` +rate_limit_options = { + exceed_action = "deny(502)" + rate_limit_http_request_count = 10 + rate_limit_http_request_interval_sec = 60 # must be one of 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 2700, 3600 seconds + ban_duration_sec = 600 # needed only if action is rate_based_ban + ban_http_request_count = 1000 # needed only if action is rate_based_ban + ban_http_request_interval_sec = 300 # must be one of 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 2700, 3600 seconds. needed only if action is rate_based_ban + enforce_on_key = "ALL" # All is default value. If null is passed terraform will use ALL as the value. Will be set to "" when `enforce_on_key_configs` is not null + + enforce_on_key_configs = [ + { + enforce_on_key_type = "HTTP_PATH" + }, + { + enforce_on_key_type = "HTTP_COOKIE" + enforce_on_key_name = "site_id" + } + ] +} +``` + +## Preconfigured WAF Config + +`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_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" + }, + ] + } + +} +``` + +## 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: + +``` + "sqli_sensitivity_level_4" = { + action = "deny(502)" + priority = 1 + description = "SQL Sensitivity Level 4" + preview = false + target_rule_set = "sqli-v33-stable" + sensitivity_level = 4 + include_target_rule_ids = [] + exclude_target_rule_ids = [] + rate_limit_options = {} + preconfigured_waf_config_exclusions = {} + } +``` + + +### Sample: + +``` +pre_configured_rules = { + + "php-stable_level_1_with_include" = { + action = "deny(502)" + priority = 3 + description = "PHP Sensitivity Level 1 with included rules" + target_rule_set = "xss-v33-stable" + sensitivity_level = 0 + include_target_rule_ids = ["owasp-crs-v030301-id933190-php", "owasp-crs-v030301-id933111-php"] + } + + "sqli_sensitivity_level_4" = { + action = "deny(502)" + priority = 1 + target_rule_set = "sqli-v33-stable" + sensitivity_level = 4 + + 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" + }, + ] + } + + } + + } + +} +``` + + +## 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: +Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. + +``` +"block_bad_actor_ip" = { + action = "deny(502)" + priority = 11 + description = "Deny Malicious IP address" + src_ip_ranges = ["A..B.C.D", "W.X.Y.Z",] + preview = false + redirect_type = null + redirect_target = null + rate_limit_options = {} +} +``` + +### Sample: + +``` +security_rules = { + + "deny_project_bad_actor" = { + action = "deny(502)" + priority = 11 + description = "Deny Malicious IP address from project bad_actor" + src_ip_ranges = ["190.217.68.211/32", "45.116.227.68/32", "103.43.141.122", "123.11.215.36", ] + } + + "throttle_project_droptwenty" = { + action = "throttle" + 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 + enforce_on_key_configs = [ + { + enforce_on_key_type = "HTTP_PATH" + }, + { + enforce_on_key_type = "HTTP_COOKIE" + enforce_on_key_name = "site_id" + } + ] + } + + } + +} +``` + +## custom_rules: +Add Custom Rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference) + +### Format: +Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. + +``` +allow_specific_regions = { + action = "allow" + priority = 21 + description = "Allow specific Regions" + preview = false + expression = <<-EOT + '[US,AU,BE]'.contains(origin.region_code) + EOT + redirect_type = null + redirect_target = null + rate_limit_options = {} +} +``` + +### Sample: + +``` +custom_rules = { + + allow_specific_regions = { + action = "allow" + priority = 21 + description = "Allow specific Regions" + preview = true + expression = <<-EOT + '[US,AU,BE]'.contains(origin.region_code) + EOT + } + + 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 + } + +} +``` + +## Requirements + +These sections describe requirements for using this module. + +### Software + +The following dependencies must be available: + +- [Terraform][terraform] v1.3+ +- [Terraform Provider for GCP][terraform-provider-gcp] plugin v5.29+ diff --git a/modules/regional-backend-security-policy/main.tf b/modules/regional-backend-security-policy/main.tf new file mode 100644 index 0000000..7b3f3c8 --- /dev/null +++ b/modules/regional-backend-security-policy/main.tf @@ -0,0 +1,336 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + ### find all the preconfigured rule with no include or exclude expression + pre_configured_rules_no_cond_expr = { for name, policy in var.pre_configured_rules : name => { + expression = "evaluatePreconfiguredWaf('${policy["target_rule_set"]}', {'sensitivity': ${policy["sensitivity_level"]}})" + } if length(policy["include_target_rule_ids"]) == 0 && length(policy["exclude_target_rule_ids"]) == 0 + } + + ### find all the preconfigured rule with include (Opt In rules) expression + pre_configured_rules_include = { for name, policy in var.pre_configured_rules : name => { + target_rule_set = policy.target_rule_set + include_target_rule_ids = replace(join(",", policy.include_target_rule_ids), ",", "','") + sensitivity_level = policy.sensitivity_level + action = policy.action + priority = 0 + description = policy.description + preview = policy.preview + rate_limit_options = policy.rate_limit_options + } if length(policy["include_target_rule_ids"]) > 0 + } + + pre_configured_rules_include_expr = { for name, policy in local.pre_configured_rules_include : name => { + expression = "evaluatePreconfiguredWaf('${policy["target_rule_set"]}', {'sensitivity': 0, 'opt_in_rule_ids': ['${policy.include_target_rule_ids}']})" + } + } + + ### find all the preconfigured rule with Exclude (Opt out rules) expression + pre_configured_rules_exclude = { for name, policy in var.pre_configured_rules : name => { + target_rule_set = policy.target_rule_set + exclude_target_rule_ids = replace(join(",", policy.exclude_target_rule_ids), ",", "','") + sensitivity_level = policy.sensitivity_level + action = policy.action + priority = policy.priority + description = policy.description + preview = policy.preview + rate_limit_options = policy.rate_limit_options + } if length(policy["include_target_rule_ids"]) == 0 && length(policy["exclude_target_rule_ids"]) > 0 + } + pre_configured_rules_exclude_expr = { for name, policy in local.pre_configured_rules_exclude : name => { + expression = "evaluatePreconfiguredWaf('${policy["target_rule_set"]}', {'sensitivity': ${policy.sensitivity_level}, 'opt_out_rule_ids': ['${policy.exclude_target_rule_ids}']})" + } + } + ## Combine all the preconfigured rules + pre_configured_rules_expr = merge(local.pre_configured_rules_no_cond_expr, local.pre_configured_rules_include_expr, local.pre_configured_rules_exclude_expr) +} + + +resource "google_compute_region_security_policy" "security_policy" { + provider = google-beta + project = var.project_id + name = var.name + description = var.description + type = var.type + region = var.region +} + +##### Security Rules Block IP addresses + +resource "google_compute_region_security_policy_rule" "security_rules" { + provider = google-beta + for_each = var.security_rules == null ? {} : { for x in var.security_rules : x.priority => x } + project = var.project_id + region = var.region + security_policy = google_compute_region_security_policy.security_policy.name + + action = each.value["action"] + priority = each.value["priority"] + preview = each.value["preview"] + description = each.value["description"] + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = each.value["src_ip_ranges"] + } + } + + ### Rate limit. Execute only if Action is "rate_based_ban" or "throttle" + dynamic "rate_limit_options" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limits"] : [] + content { + conform_action = "allow" + ban_duration_sec = each.value["action"] == "rate_based_ban" ? lookup(each.value["rate_limit_options"], "ban_duration_sec") : null + exceed_action = lookup(each.value["rate_limit_options"], "exceed_action") + enforce_on_key = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key", null) : "" + enforce_on_key_name = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key_name", null) : null + + dynamic "enforce_on_key_configs" { + for_each = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? {} : { for x in lookup(each.value["rate_limit_options"], "enforce_on_key_configs") : x.enforce_on_key_type => x } + content { + enforce_on_key_type = enforce_on_key_configs.value.enforce_on_key_type + enforce_on_key_name = enforce_on_key_configs.value.enforce_on_key_name + } + } + + ## Required for all rate limit options + dynamic "rate_limit_threshold" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limit_options"] : [] + content { + count = each.value["rate_limit_options"].rate_limit_http_request_count + interval_sec = each.value["rate_limit_options"].rate_limit_http_request_interval_sec + } + } + + ## Optional. Can be provided for for rate based ban. Not needed for throttle + dynamic "ban_threshold" { + for_each = each.value["action"] == "rate_based_ban" && lookup(each.value["rate_limit_options"], "ban_http_request_count", null) != null && lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec", null) != null ? ["ban_threshold"] : [] + content { + count = lookup(each.value["rate_limit_options"], "ban_http_request_count") + interval_sec = lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec") + } + } + } + } +} + + +##### Custom Rules + +resource "google_compute_region_security_policy_rule" "custom_rules" { + provider = google-beta + for_each = var.custom_rules == null ? {} : { for x in var.custom_rules : x.priority => x } + project = var.project_id + region = var.region + security_policy = google_compute_region_security_policy.security_policy.name + + action = each.value["action"] + priority = each.value["priority"] + preview = each.value["preview"] + description = each.value["description"] + match { + expr { + expression = each.value["expression"] + } + } + + ### Rate limit. Execute only if Action is "rate_based_ban" or "throttle" + dynamic "rate_limit_options" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limits"] : [] + content { + conform_action = "allow" + ban_duration_sec = each.value["action"] == "rate_based_ban" ? lookup(each.value["rate_limit_options"], "ban_duration_sec") : null + exceed_action = lookup(each.value["rate_limit_options"], "exceed_action") + enforce_on_key = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key", null) : "" + enforce_on_key_name = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key_name", null) : null + + dynamic "enforce_on_key_configs" { + for_each = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? {} : { for x in lookup(each.value["rate_limit_options"], "enforce_on_key_configs") : x.enforce_on_key_type => x } + content { + enforce_on_key_type = enforce_on_key_configs.value.enforce_on_key_type + enforce_on_key_name = enforce_on_key_configs.value.enforce_on_key_name + } + } + + ## Required for all rate limit options + dynamic "rate_limit_threshold" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limit_options"] : [] + content { + count = each.value["rate_limit_options"].rate_limit_http_request_count + interval_sec = each.value["rate_limit_options"].rate_limit_http_request_interval_sec + } + } + + ## Optional. Can be provided for for rate based ban. Not needed for throttle + dynamic "ban_threshold" { + for_each = each.value["action"] == "rate_based_ban" && lookup(each.value["rate_limit_options"], "ban_http_request_count", null) != null && lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec", null) != null ? ["ban_threshold"] : [] + content { + count = lookup(each.value["rate_limit_options"], "ban_http_request_count") + interval_sec = lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec") + } + } + } + } + # Optional preconfigured_waf_config Block if preconfigured_waf_config_exclusion is provided + dynamic "preconfigured_waf_config" { + for_each = each.value.preconfigured_waf_config_exclusions == null ? [] : ["preconfigured_waf_config_exclusions"] #each.value.preconfigured_waf_config_exclusions + content { + dynamic "exclusion" { + for_each = each.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 + } + } + } + } + + } + } + +} + + + + +##### Preconfigured WAF Rules + +resource "google_compute_region_security_policy_rule" "pre_configured_rules" { + provider = google-beta + for_each = var.pre_configured_rules #var.pre_configured_rules == null ? {} : { for x in var.pre_configured_rules : x.priority => x } + project = var.project_id + region = var.region + security_policy = google_compute_region_security_policy.security_policy.name + + action = each.value["action"] + priority = each.value["priority"] + preview = each.value["preview"] + description = each.value["description"] + match { + expr { + expression = local.pre_configured_rules_expr[each.key].expression + } + } + + ### Rate limit. Execute only if Action is "rate_based_ban" or "throttle" + dynamic "rate_limit_options" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limits"] : [] + content { + conform_action = "allow" + ban_duration_sec = each.value["action"] == "rate_based_ban" ? lookup(each.value["rate_limit_options"], "ban_duration_sec") : null + exceed_action = lookup(each.value["rate_limit_options"], "exceed_action") + enforce_on_key = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key", null) : "" + enforce_on_key_name = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? lookup(each.value["rate_limit_options"], "enforce_on_key_name", null) : null + + dynamic "enforce_on_key_configs" { + for_each = lookup(each.value["rate_limit_options"], "enforce_on_key_configs") == null ? {} : { for x in lookup(each.value["rate_limit_options"], "enforce_on_key_configs") : x.enforce_on_key_type => x } + content { + enforce_on_key_type = enforce_on_key_configs.value.enforce_on_key_type + enforce_on_key_name = enforce_on_key_configs.value.enforce_on_key_name + } + } + + ## Required for all rate limit options + dynamic "rate_limit_threshold" { + for_each = each.value["action"] == "rate_based_ban" || each.value["action"] == "throttle" ? ["rate_limit_options"] : [] + content { + count = each.value["rate_limit_options"].rate_limit_http_request_count + interval_sec = each.value["rate_limit_options"].rate_limit_http_request_interval_sec + } + } + + ## Optional. Can be provided for for rate based ban. Not needed for throttle + dynamic "ban_threshold" { + for_each = each.value["action"] == "rate_based_ban" && lookup(each.value["rate_limit_options"], "ban_http_request_count", null) != null && lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec", null) != null ? ["ban_threshold"] : [] + content { + count = lookup(each.value["rate_limit_options"], "ban_http_request_count") + interval_sec = lookup(each.value["rate_limit_options"], "ban_http_request_interval_sec") + } + } + } + } + # Optional preconfigured_waf_config Block if preconfigured_waf_config_exclusion is provided + dynamic "preconfigured_waf_config" { + for_each = each.value.preconfigured_waf_config_exclusions == null ? [] : ["preconfigured_waf_config_exclusions"] #each.value.preconfigured_waf_config_exclusions + content { + dynamic "exclusion" { + for_each = each.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/modules/regional-backend-security-policy/outputs.tf b/modules/regional-backend-security-policy/outputs.tf new file mode 100644 index 0000000..984714c --- /dev/null +++ b/modules/regional-backend-security-policy/outputs.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "policy" { + value = google_compute_region_security_policy.security_policy + description = "Regional network Security policy created" +} + +output "security_rules" { + value = google_compute_region_security_policy_rule.security_rules + description = "Security policy rules created" +} diff --git a/modules/regional-backend-security-policy/variables.tf b/modules/regional-backend-security-policy/variables.tf new file mode 100644 index 0000000..4b8e2ad --- /dev/null +++ b/modules/regional-backend-security-policy/variables.tf @@ -0,0 +1,172 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The project in which the resource belongs." + type = string +} + +variable "region" { + description = "The region in which security policy is created" + type = string +} + +variable "name" { + description = "Name of regional security policy. Name must be 1-63 characters long and match the regular expression a-z? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash" + type = string + default = "adv-network-ddos-protection" +} + +variable "type" { + description = "Type indicates the intended use of the security policy. Possible values are CLOUD_ARMOR and CLOUD_ARMOR_EDGE." + type = string + default = "CLOUD_ARMOR" +} + +variable "description" { + description = "An optional description of advanced network ddos protection security policy" + type = string + default = "CA Advance DDoS protection" +} + +variable "pre_configured_rules" { + description = "Map of pre-configured rules with Sensitivity levels" + type = map(object({ + action = string + priority = number + description = optional(string) + preview = optional(bool, false) + 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) + }), {}) + + 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 = {} +} + +variable "security_rules" { + description = "Map of Security rules with list of IP addresses to block or unblock." + type = map(object({ + action = string + priority = number + description = optional(string) + preview = optional(bool, false) + 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) + }), + {}) + })) + + default = {} +} + +variable "custom_rules" { + description = "Custome security rules" + type = map(object({ + action = string + priority = number + description = optional(string) + preview = optional(bool, false) + expression = 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) + }), + {}) + + 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/modules/regional-backend-security-policy/versions.tf b/modules/regional-backend-security-policy/versions.tf new file mode 100644 index 0000000..d63ce55 --- /dev/null +++ b/modules/regional-backend-security-policy/versions.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = ">= 1.3.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.29, < 7" + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 5.29, < 7" + } + } + provider_meta "google" { + module_name = "blueprints/terraform/terraform-google-cloud-armor:regional-backend-security-policy/v2.2.0" + } + provider_meta "google-beta" { + module_name = "blueprints/terraform/terraform-google-cloud-armor:regional-backend-security-policy/v2.2.0" + } +} diff --git a/test/integration/global-backend-security-policy-complete/security_policy_test.go b/test/integration/global-backend-security-policy-complete/security_policy_test.go index c25de85..d6a5445 100644 --- a/test/integration/global-backend-security-policy-complete/security_policy_test.go +++ b/test/integration/global-backend-security-policy-complete/security_policy_test.go @@ -29,7 +29,7 @@ func TestGlobalSecurityPolicyComplete(t *testing.T) { casp.DefineVerify(func(assert *assert.Assertions) { casp.DefaultVerify(assert) - projectId := casp.GetStringOutput("project_id") + projectId := casp.GetTFSetupStringOutput("project_id") policyName := casp.GetStringOutput("policy_name") spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s", policyName, projectId)) diff --git a/test/integration/global-backend-security-policy-enterprise/security_policy_enterprise_test.go b/test/integration/global-backend-security-policy-enterprise/security_policy_enterprise_test.go new file mode 100644 index 0000000..e20cfb5 --- /dev/null +++ b/test/integration/global-backend-security-policy-enterprise/security_policy_enterprise_test.go @@ -0,0 +1,63 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package security_policy_enterprise + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func TestGlobalSecurityPolicyEnterprise(t *testing.T) { + casp := tft.NewTFBlueprintTest(t) + + casp.DefineVerify(func(assert *assert.Assertions) { + casp.DefaultVerify(assert) + + projectId := casp.GetTFSetupStringOutput("project_id") + policyName := casp.GetStringOutput("policy_name") + + spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s", policyName, projectId)) + for _, sp := range spName.Array() { + pname := sp.Get("name").String() + assert.Equal(policyName, pname, "has expected name") + assert.Equal("Test Cloud Armor security policy with with rules supported by Cloud Armor Enterprise (Former Managed Protection Plus - CAMP+)", sp.Get("description").String(), "has expected description") + assert.Equal("CLOUD_ARMOR", sp.Get("type").String(), "has expected name") + assert.Equal("STANDARD", sp.Get("adaptiveProtectionConfig.layer7DdosDefenseConfig.ruleVisibility").String(), "Mismatched adaptiveProtectionConfig.layer7DdosDefenseConfig.ruleVisibility") + assert.True(sp.Get("adaptiveProtectionConfig.layer7DdosDefenseConfig.enable").Bool(), "adaptiveProtectionConfig.layer7DdosDefenseConfig.enable is not true") + } + // Rule 300 + spRule300 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 300 --security-policy=%s --project %s", policyName, projectId)) + for _, sp := range spRule300.Array() { + assert.Equal("deny(502)", sp.Get("action").String(), "priority 300 rule has mismatched action") + assert.Equal("Deny IP addresses known to attack web applications", sp.Get("description").String(), "rule 400 has mismatched description") + assert.Equal("evaluateThreatIntelligence('iplist-known-malicious-ips', ['47.100.100.100', '47.189.12.139'])", sp.Get("match.expr.expression").String(), "priority 300 rule has mismatched rule expression") + assert.False(sp.Get("preview").Bool(), "priority 300 rule Preview is set to True") + } + // Rule 400 + spRule400 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 400 --security-policy=%s --project %s", policyName, projectId)) + for _, sp := range spRule400.Array() { + assert.Equal("deny(502)", sp.Get("action").String(), "priority 400 rule has mismatched action") + assert.Equal("Deny Tor exit nodes IP addresses", sp.Get("description").String(), "rule 400 has mismatched description") + assert.Equal("evaluateThreatIntelligence('iplist-tor-exit-nodes')", sp.Get("match.expr.expression").String(), "priority 400 rule has mismatched rule expression") + assert.False(sp.Get("preview").Bool(), "priority 400 rule Preview is set to True") + } + + }) + casp.Test() +} diff --git a/test/integration/global-backend-security-policy-example/simple_example_test.go b/test/integration/global-backend-security-policy-example/simple_example_test.go index 75efe3c..56e20c8 100644 --- a/test/integration/global-backend-security-policy-example/simple_example_test.go +++ b/test/integration/global-backend-security-policy-example/simple_example_test.go @@ -23,13 +23,13 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSimpleExample(t *testing.T) { +func TestGlobalSecurityPolicyExample(t *testing.T) { casp := tft.NewTFBlueprintTest(t) casp.DefineVerify(func(assert *assert.Assertions) { casp.DefaultVerify(assert) - projectId := casp.GetStringOutput("project_id") + projectId := casp.GetTFSetupStringOutput("project_id") policyName := casp.GetStringOutput("policy_name") spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s", policyName, projectId)) @@ -49,7 +49,7 @@ func TestSimpleExample(t *testing.T) { 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") - pce := sp.Get("preconfiguredWafConfig.exclusions").Array() + 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") diff --git a/test/integration/global-backend-security-policy-recaptcha/security_policy_recaptcha_test.go b/test/integration/global-backend-security-policy-recaptcha/security_policy_recaptcha_test.go new file mode 100644 index 0000000..a1471b3 --- /dev/null +++ b/test/integration/global-backend-security-policy-recaptcha/security_policy_recaptcha_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package security_policy_recaptcha + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func TestGlobalSecurityPolicyRecaptcha(t *testing.T) { + casp := tft.NewTFBlueprintTest(t) + + casp.DefineVerify(func(assert *assert.Assertions) { + casp.DefaultVerify(assert) + + projectId := casp.GetTFSetupStringOutput("project_id") + policyName := casp.GetStringOutput("policy_name") + + spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s", policyName, projectId)) + for _, sp := range spName.Array() { + pname := sp.Get("name").String() + assert.Equal(policyName, pname, "has expected name") + assert.Equal("Test Cloud Armor security policy with Recaptcha Enterprise", sp.Get("description").String(), "has expected description") + assert.Equal("CLOUD_ARMOR", sp.Get("type").String(), "has expected name") + } + }) + casp.Test() +} diff --git a/test/integration/global-edge-security-policy/security_policy_edge_test.go b/test/integration/global-edge-security-policy/security_policy_edge_test.go index 7eeb42c..ae032b2 100644 --- a/test/integration/global-edge-security-policy/security_policy_edge_test.go +++ b/test/integration/global-edge-security-policy/security_policy_edge_test.go @@ -23,13 +23,13 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSecurityPolicyEdge(t *testing.T) { +func TestGlobalSecurityPolicyEdge(t *testing.T) { casp := tft.NewTFBlueprintTest(t) casp.DefineVerify(func(assert *assert.Assertions) { casp.DefaultVerify(assert) - projectId := casp.GetStringOutput("project_id") + projectId := casp.GetTFSetupStringOutput("project_id") policyName := casp.GetStringOutput("policy_name") spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s", policyName, projectId)) diff --git a/test/integration/regional-adv-ddos-and-network-edge-security-policy-complete/regional_network_security_policy_test.go b/test/integration/regional-adv-ddos-and-network-edge-security-policy-complete/regional_network_security_policy_test.go new file mode 100644 index 0000000..332d6b2 --- /dev/null +++ b/test/integration/regional-adv-ddos-and-network-edge-security-policy-complete/regional_network_security_policy_test.go @@ -0,0 +1,53 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package network_security_policy + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func TestRegionalNetworkEdgePolicy(t *testing.T) { + casp := tft.NewTFBlueprintTest(t) + + casp.DefineVerify(func(assert *assert.Assertions) { + // casp.DefaultVerify(assert) + + projectId := casp.GetTFSetupStringOutput("project_id") + policyName := casp.GetStringOutput("policy_name") + region := casp.GetStringOutput("region") + + spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spName.Array() { + assert.Equal(policyName, sp.Get("name").String(), "mismatched name") + assert.Equal("CA Advance DDoS protection", sp.Get("description").String(), "mismatched description") + assert.Equal("CLOUD_ARMOR_NETWORK", sp.Get("type").String(), "mismatched type") + } + + // Rule 100 + spRule1 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 100 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule1.Array() { + assert.Equal("allow", sp.Get("action").String(), "priority 100 rule has mismatched action") + assert.Equal("custom rule 100", sp.Get("description").String(), "priority 1 rule has mismatched description") + assert.False(sp.Get("preview").Bool(), "priority 1 rule Preview is set to true") + } + + }) + casp.Test() +} diff --git a/test/integration/regional-backend-security-policy-example/regional_backend_security_policy_test.go b/test/integration/regional-backend-security-policy-example/regional_backend_security_policy_test.go new file mode 100644 index 0000000..ad744f2 --- /dev/null +++ b/test/integration/regional-backend-security-policy-example/regional_backend_security_policy_test.go @@ -0,0 +1,203 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regional_backend_security_policy + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func TestRegionalBackendPolicy(t *testing.T) { + casp := tft.NewTFBlueprintTest(t) + + casp.DefineVerify(func(assert *assert.Assertions) { + // casp.DefaultVerify(assert) + + projectId := casp.GetTFSetupStringOutput("project_id") + policyName := casp.GetStringOutput("policy_name") + region := casp.GetStringOutput("region") + + spName := gcloud.Run(t, fmt.Sprintf("compute security-policies describe %s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spName.Array() { + assert.Equal(policyName, sp.Get("name").String(), "mismatched name") + assert.Equal("Test regional Cloud Armor backend security policy with preconfigured rules, security rules and custom rules", sp.Get("description").String(), "mismatched description") + assert.Equal("CLOUD_ARMOR", sp.Get("type").String(), "mismatched type") + } + + // Rule 1 + spRule1 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 1 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule1.Array() { + assert.Equal("deny(502)", sp.Get("action").String(), "priority 100 rule has mismatched action") + assert.Equal("sqli-v33-stable Sensitivity Level 4 and 2 preconfigured_waf_config_exclusions", sp.Get("description").String(), "priority 1 rule has mismatched description") + assert.False(sp.Get("preview").Bool(), "priority 1 rule Preview is set to true") + } + + // Rule 2 + spRule2 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 2 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + 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.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") + } + + spRule3 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 3 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule3.Array() { + assert.Equal("deny(502)", sp.Get("action").String(), "priority 3 rule has expected action") + assert.Equal("evaluatePreconfiguredWaf('php-v33-stable', {'sensitivity': 0, 'opt_in_rule_ids': ['owasp-crs-v030301-id933190-php','owasp-crs-v030301-id933111-php']})", sp.Get("match.expr.expression").String(), "priority 3 rule has expected rule expression") + assert.Equal("PHP Sensitivity Level 0 with included rules", sp.Get("description").String(), "priority 3 rule has expected description") + assert.False(sp.Get("preview").Bool(), "priority 3 rule Preview is set to False") + } + + // Rule 11 + spRule11 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 11 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule11.Array() { + assert.True(sp.Get("preview").Bool(), "priority 11 rule Preview is set to True") + assert.Equal("deny(502)", sp.Get("action").String(), "priority 11 rule has expected action") + assert.Equal("Deny Malicious IP address from project honeypot", sp.Get("description").String(), "priority 11 rule has expected description") + assert.Equal("SRC_IPS_V1", sp.Get("match.versionedExpr").String(), "priority 11 rule has expected redirect type") + srcIpRanges := sp.Get("match.config.srcIpRanges").Array() + assert.Equal(5, len(srcIpRanges), "found only 5 IP address") + } + + // Rule 12 + spRule12 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 12 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule12.Array() { + assert.False(sp.Get("preview").Bool(), "priority 13 rule Preview is set to False") + assert.Equal("rate_based_ban", sp.Get("action").String(), "priority 13 rule has expected action") + assert.Equal("Rate based ban for address from project dropten as soon as they cross rate limit threshold", sp.Get("description").String(), "priority 13 rule has expected description") + assert.Equal("SRC_IPS_V1", sp.Get("match.versionedExpr").String(), "priority 13 rule has expected redirect type") + + srcIpRanges := sp.Get("match.config.srcIpRanges").Array() + assert.Equal(2, len(srcIpRanges), "priority 13 rule found only 2 IP address") + assert.Equal(srcIpRanges[0].String(), "190.217.68.213/32", "priority 13 rule found first valid cidr range") + assert.Equal(srcIpRanges[1].String(), "45.116.227.70", "priority 13 rule found second valid cidr range") + assert.Equal("120", sp.Get("rateLimitOptions.banDurationSec").String(), "priority 13 rule has Rate limit ban duration") + assert.Equal("allow", sp.Get("rateLimitOptions.conformAction").String(), "priority 13 rule has Rate limit confirm action") + assert.Equal("HTTP_HEADER", sp.Get("rateLimitOptions.enforceOnKey").String(), "priority 13 rule has Rate limit Enforce on key") + assert.Equal("X-API-KEY", sp.Get("rateLimitOptions.enforceOnKeyName").String(), "priority 13 rule has Rate limit Enforce on key name") + assert.Equal("deny(502)", sp.Get("rateLimitOptions.exceedAction").String(), "priority 13 rule has Rate limit exceed action") + assert.Equal("10", sp.Get("rateLimitOptions.rateLimitThreshold.count").String(), "priority 13 rule has Rate limit threshold count") + assert.Equal("60", sp.Get("rateLimitOptions.rateLimitThreshold.intervalSec").String(), "priority 13 rule has Rate limit threshold interval") + } + + // Rule 13 + spRule13 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 13 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule13.Array() { + assert.False(sp.Get("preview").Bool(), "priority 14 rule Preview is set to False") + assert.Equal("rate_based_ban", sp.Get("action").String(), "priority 14 rule has expected action") + assert.Equal("Rate based ban for address from project dropthirty only if they cross banned threshold", sp.Get("description").String(), "priority 14 rule has expected description") + assert.Equal("SRC_IPS_V1", sp.Get("match.versionedExpr").String(), "priority 14 rule has expected redirect type") + + srcIpRanges := sp.Get("match.config.srcIpRanges").Array() + assert.Equal(2, len(srcIpRanges), "priority 14 rule found only 2 IP address") + assert.Equal(srcIpRanges[1].String(), "45.116.227.70", "priority 14 rule found second valid cidr range") + assert.Equal(srcIpRanges[0].String(), "190.217.68.213", "priority 14 rule found first valid cidr range") + assert.Equal("600", sp.Get("rateLimitOptions.banDurationSec").String(), "priority 14 rule has Rate limit ban duration") + assert.Equal("1000", sp.Get("rateLimitOptions.banThreshold.count").String(), "priority 14 rule has Rate limit threshold count") + assert.Equal("300", sp.Get("rateLimitOptions.banThreshold.intervalSec").String(), "priority 14 rule has Rate limit threshold interval") + assert.Equal("allow", sp.Get("rateLimitOptions.conformAction").String(), "priority 14 rule has Rate limit confirm action") + assert.Equal("ALL", sp.Get("rateLimitOptions.enforceOnKey").String(), "priority 14 rule has Rate limit Enforce on key") + assert.Equal("deny(502)", sp.Get("rateLimitOptions.exceedAction").String(), "priority 14 rule has Rate limit exceed action") + assert.Equal("10", sp.Get("rateLimitOptions.rateLimitThreshold.count").String(), "priority 14 rule has Rate limit threshold count") + assert.Equal("60", sp.Get("rateLimitOptions.rateLimitThreshold.intervalSec").String(), "priority 14 rule has Rate limit threshold interval") + } + + // Rule 14 + spRule14 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 14 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule14.Array() { + assert.False(sp.Get("preview").Bool(), "priority 15 rule Preview is set to False") + assert.Equal("throttle", sp.Get("action").String(), "priority 15 rule has expected action") + assert.Equal("Throttle IP addresses from project droptwenty", sp.Get("description").String(), "priority 15 rule has expected description") + assert.Equal("SRC_IPS_V1", sp.Get("match.versionedExpr").String(), "priority 15 rule has expected redirect type") + + srcIpRanges := sp.Get("match.config.srcIpRanges").Array() + assert.Equal(2, len(srcIpRanges), "priority 15 rule found only 2 IP address") + assert.Equal(srcIpRanges[0].String(), "190.217.68.214", "priority 15 rule found first valid cidr range") + assert.Equal(srcIpRanges[1].String(), "45.116.227.71", "priority 15 rule found second valid cidr range") + assert.Equal("allow", sp.Get("rateLimitOptions.conformAction").String(), "priority 15 rule has Rate limit confirm action") + assert.Equal("deny(502)", sp.Get("rateLimitOptions.exceedAction").String(), "priority 15 rule has Rate limit exceed action") + assert.Equal("10", sp.Get("rateLimitOptions.rateLimitThreshold.count").String(), "priority 15 rule has Rate limit threshold count") + assert.Equal("60", sp.Get("rateLimitOptions.rateLimitThreshold.intervalSec").String(), "priority 15 rule has Rate limit threshold interval") + assert.Equal("site_id", sp.Get("rateLimitOptions.enforceOnKeyConfigs").Array()[0].Get("enforceOnKeyName").String(), "priority 1 rule has expected requestCookiesToExclude") + assert.Equal("HTTP_COOKIE", sp.Get("rateLimitOptions.enforceOnKeyConfigs").Array()[0].Get("enforceOnKeyType").String(), "priority 1 rule has expected requestCookiesToExclude") + assert.Equal("HTTP_PATH", sp.Get("rateLimitOptions.enforceOnKeyConfigs").Array()[1].Get("enforceOnKeyType").String(), "priority 1 rule has expected requestCookiesToExclude") + } + + // Rule 21 + spRule21 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 21 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule21.Array() { + assert.False(sp.Get("preview").Bool(), "priority 21 rule Preview is set to False") + assert.Equal("deny(502)", sp.Get("action").String(), "priority 21 rule has expected action") + assert.Equal("Deny specific Regions", sp.Get("description").String(), "priority 21 rule has expected description") + assert.Equal("'[AU,BE]'.contains(origin.region_code)\n", sp.Get("match.expr.expression").String(), "priority 21 rule has expected expression") + } + + // Rule 22 + spRule22 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 22 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule22.Array() { + assert.False(sp.Get("preview").Bool(), "priority 22 rule Preview is set to False") + assert.Equal("deny(502)", sp.Get("action").String(), "priority 22 rule has expected action") + assert.Equal("Deny Specific IP address", sp.Get("description").String(), "priority 22 rule has expected description") + assert.Equal("inIpRange(origin.ip, '47.185.201.155/32')\n", sp.Get("match.expr.expression").String(), "priority 22 rule has expected expression") + } + + // Rule 23 + spRule23 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 23 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule23.Array() { + assert.False(sp.Get("preview").Bool(), "priority 23 rule Preview is set to False") + assert.Equal("throttle", sp.Get("action").String(), "priority 23 rule has expected action") + assert.Equal("Throttle specific IP address in US Region", sp.Get("description").String(), "priority 23 rule has expected description") + assert.Equal("origin.region_code == \"US\" && inIpRange(origin.ip, '47.185.201.159/32')\n", sp.Get("match.expr.expression").String(), "priority 23 rule has expected expression") + assert.Equal("allow", sp.Get("rateLimitOptions.conformAction").String(), "priority 23 rule has Rate limit confirm action") + assert.Equal("", sp.Get("rateLimitOptions.enforceOnKey").String(), "priority 23 rule has Rate limit Enforce on key") + assert.Equal("deny(502)", sp.Get("rateLimitOptions.exceedAction").String(), "priority 23 rule has Rate limit exceed action") + assert.Equal("10", sp.Get("rateLimitOptions.rateLimitThreshold.count").String(), "priority 23 rule has Rate limit threshold count") + assert.Equal("60", sp.Get("rateLimitOptions.rateLimitThreshold.intervalSec").String(), "priority 23 rule has Rate limit threshold interval") + } + + // Rule 24 + spRule24 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 24 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule24.Array() { + assert.False(sp.Get("preview").Bool(), "priority 24 rule Preview is set to False") + assert.Equal("rate_based_ban", sp.Get("action").String(), "priority 24 rule has expected action") + assert.Empty(sp.Get("description").String(), "priority 24 rule has expected description") + assert.Equal("inIpRange(origin.ip, '47.185.201.160/32')\n", sp.Get("match.expr.expression").String(), "priority 24 rule has expected expression") + assert.Equal("120", sp.Get("rateLimitOptions.banDurationSec").String(), "priority 24 rule has Rate limit ban duration") + assert.Equal("10000", sp.Get("rateLimitOptions.banThreshold.count").String(), "priority 24 rule has Rate limit threshold count") + assert.Equal("600", sp.Get("rateLimitOptions.banThreshold.intervalSec").String(), "priority 24 rule has Rate limit threshold interval") + assert.Equal("allow", sp.Get("rateLimitOptions.conformAction").String(), "priority 24 rule has Rate limit confirm action") + assert.Equal("ALL", sp.Get("rateLimitOptions.enforceOnKey").String(), "priority 24 rule has Rate limit Enforce on key") + assert.Equal("deny(502)", sp.Get("rateLimitOptions.exceedAction").String(), "priority 24 rule has Rate limit exceed action") + assert.Equal("10", sp.Get("rateLimitOptions.rateLimitThreshold.count").String(), "priority 24 rule has Rate limit threshold count") + assert.Equal("60", sp.Get("rateLimitOptions.rateLimitThreshold.intervalSec").String(), "priority 24 rule has Rate limit threshold interval") + } + + // Rule 100 + spRule100 := gcloud.Run(t, fmt.Sprintf("compute security-policies rules describe 100 --security-policy=%s --project %s --region %s", policyName, projectId, region)) + for _, sp := range spRule100.Array() { + assert.True(sp.Get("preview").Bool(), "priority 100 rule Preview is set to True") + assert.Equal("deny(502)", sp.Get("action").String(), "priority 100 rule has expected action") + assert.Equal("Deny pre-configured rule java-v33-stable at sensitivity level 3", sp.Get("description").String(), "priority 100 rule has expected description") + assert.Equal("evaluatePreconfiguredWaf('java-v33-stable', {'sensitivity': 3, 'opt_out_rule_ids': ['owasp-crs-v030301-id944240-java', 'owasp-crs-v030301-id944120-java']})\n", sp.Get("match.expr.expression").String(), "priority 100 rule has expected expression") + } + + }) + casp.Test() +} diff --git a/test/setup/main.tf b/test/setup/main.tf index b328d61..be37ffd 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -32,3 +32,11 @@ module "project" { "recaptchaenterprise.googleapis.com", ] } + +resource "google_compute_project_cloud_armor_tier" "cloud_armor_tier_config" { + + project = module.project.project_id + cloud_armor_tier = "CA_ENTERPRISE_PAYGO" + + depends_on = [module.project] +} diff --git a/variables.tf b/variables.tf index 55f7d21..9c1b5e8 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 with Sensitivity levels. preconfigured_waf_config_exclusion is obsolete and available for backward compatibility. Use preconfigured_waf_config_exclusions which allows multiple exclusions" + description = "Map of pre-configured rules with Sensitivity levels." type = map(object({ action = string priority = number @@ -75,27 +75,6 @@ variable "pre_configured_rules" { 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), []) @@ -186,27 +165,6 @@ variable "custom_rules" { 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), []) diff --git a/versions.tf b/versions.tf index 8987562..5fbf3fc 100644 --- a/versions.tf +++ b/versions.tf @@ -19,11 +19,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 4.79, < 6" + version = ">= 4.79, < 7" } google-beta = { source = "hashicorp/google-beta" - version = ">= 4.79, < 6" + version = ">= 4.79, < 7" } } provider_meta "google" {