diff --git a/Makefile b/Makefile index 2774961..fd1a26f 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,6 @@ docker_test_lint_gha: .PHONY: docker_generate_docs docker_generate_docs: docker run --rm -it \ - -e DISABLE_BPMETADATA=1 \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /bin/bash -c 'source /usr/local/bin/task_helper_functions.sh && generate_docs' diff --git a/README.md b/README.md index 2b4bf19..a185463 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Cloud Armor Terraform Module This module makes it easy to setup [Cloud Armor Security Policy](https://cloud.google.com/armor/docs/cloud-armor-overview#security_policies) with Security rules. There are four type of rules you can create in each policy: -- [Pre-Configured Rules](#pre_conf_rules): These are based on [pre-configured waf rules](https://cloud.google.com/armor/docs/waf-rules) -- [Security Rules](#security_rules): Allow or Deny traffic from set of IP addresses -- [Custom Rules](#custom_rules): You can create your own rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference) -- [Threat Intelligence Rules](#threat_intelligence_rules): Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). You need to have managed protection plus enable to use this feature +- [Pre-Configured Rules](#pre_configured_rules): These are based on [pre-configured waf rules](https://cloud.google.com/armor/docs/waf-rules). +- [Security Rules](#security_rules): Allow or Deny traffic from list of IP addresses or IP adress ranges. +- [Custom Rules](#custom_rules): You can create your own rules using [Common Expression Language (CEL)](https://cloud.google.com/armor/docs/rules-language-reference). +- [Threat Intelligence Rules](#threat_intelligence_rules): Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). [Managed protection plus](https://cloud.google.com/armor/docs/managed-protection-overview) subscription is needed to use this feature. ## Compatibility -This module is meant for use with Terraform 1.3+ and tested using Terraform 1.3+. If you find incompatibilities using Terraform >=0.13, please open an issue. +This module is meant for use with Terraform 1.3+ and tested using Terraform 1.3+. If you find incompatibilities using Terraform >=1.3, please open an issue. ## Module Format @@ -36,15 +36,15 @@ module "security_policy" { version = "~> 0.1.0" project_id = var.project_id - name = var.name - description = var.description - default_rule_action = var.default_rule_action + name = "my-test-security-policy" + description = "Test Security Policy" + default_rule_action = "allow" type = "CLOUD_ARMOR" layer_7_ddos_defense_enable = true layer_7_ddos_defense_rule_visibility = "STANDARD" - pre_configured_rules = { + "sqli_sensitivity_level_4" = { action = "deny(502)" priority = 1 @@ -52,100 +52,69 @@ module "security_policy" { } "xss-stable_level_2_with_exclude" = { - action = "throttle" + action = "deny(502)" priority = 2 description = "XSS Sensitivity Level 2 with excluded rules" preview = true target_rule_set = "xss-v33-stable" sensitivity_level = 2 - exclude_target_rule_ids = ["owasp-crs-v030301-id941380-xss", "owasp-crs-v030301-id941340-xss"] - rate_limit_options = { - exceed_action = "deny(502)" - rate_limit_http_request_count = 10 - rate_limit_http_request_interval_sec = 60 - } + exclude_target_rule_ids = ["owasp-crs-v030301-id941380-xss", "owasp-crs-v030301-id941280-xss"] } - "php-stable_level_1_with_include" = { - action = "rate_based_ban" + "php-stable_level_0_with_include" = { + action = "deny(502)" priority = 3 - description = "PHP Sensitivity Level 1 with included rules" - target_rule_set = "xss-v33-stable" - sensitivity_level = 0 + 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"] - exclude_target_rule_ids = [] - rate_limit_options = { - ban_duration_sec = 600 - enforce_on_key = "ALL" - exceed_action = "deny(502)" - rate_limit_http_request_count = 10 - rate_limit_http_request_interval_sec = 60 - ban_http_request_count = 1000 - ban_http_request_interval_sec = 300 - } - } - - "rfi_sensitivity_level_4" = { - action = "redirect" - priority = 4 - description = "Remote file inclusion 4" - redirect_type = "GOOGLE_RECAPTCHA" - target_rule_set = "rfi-v33-stable" } } security_rules = { - "deny_project_honeypot" = { + + "deny_project_bad_actor1" = { action = "deny(502)" priority = 11 - description = "Deny Malicious IP address from project honeypot" - src_ip_ranges = ["190.217.68.211", "45.116.227.68", "103.43.141.122", "123.11.215.36", ] + description = "Deny Malicious IP address from project bad_actor1" + 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 } - "redirect_project_drop" = { - action = "redirect" - priority = 12 - description = "Redirect IP address from project drop" - src_ip_ranges = ["190.217.68.212", "45.116.227.69", ] - redirect_type = "GOOGLE_RECAPTCHA" - } - - "rate_ban_project_dropten" = { + "rate_ban_project_bad_actor2" = { action = "rate_based_ban" priority = 13 - description = "Rate based ban for address from project dropten as soon as they cross rate limit threshold" - src_ip_ranges = ["190.217.68.213", "45.116.227.70", ] + description = "Rate based ban for address from project bad_actor2 as soon as they cross rate limit threshold" + src_ip_ranges = ["190.217.68.213/32", "45.116.227.70", ] rate_limit_options = { - ban_duration_sec = 120 - enforce_on_key = "ALL" exceed_action = "deny(502)" rate_limit_http_request_count = 10 rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 120 + enforce_on_key = "ALL" } } - "rate_ban_project_dropthirty" = { + "rate_ban_project_bad_actor3" = { action = "rate_based_ban" priority = 14 - description = "Rate based ban for address from project dropthirty only if they cross banned threshold" + description = "Rate based ban for address from project bad_actor3 only if they cross banned threshold" src_ip_ranges = ["190.217.68.213", "45.116.227.70", ] rate_limit_options = { - ban_duration_sec = 300 - enforce_on_key = "ALL" 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" = { + "throttle_project_bad_actor4" = { action = "throttle" priority = 15 - description = "Throttle IP addresses from project droptwenty" + description = "Throttle IP addresses from project bad_actor4" src_ip_ranges = ["190.217.68.214", "45.116.227.71", ] rate_limit_options = { exceed_action = "deny(502)" @@ -153,15 +122,17 @@ module "security_policy" { rate_limit_http_request_interval_sec = 60 } } + } custom_rules = { - allow_specific_regions = { - action = "allow" + + deny_specific_regions = { + action = "deny(502)" priority = 21 - description = "Allow specific Regions" + description = "Deny specific Regions" expression = <<-EOT - '[US,AU,BE]'.contains(origin.region_code) + '[AU,BE]'.contains(origin.region_code) EOT } @@ -173,7 +144,8 @@ module "security_policy" { inIpRange(origin.ip, '47.185.201.155/32') EOT } - throttle_specific_ip = { + + throttle_specific_ip_region = { action = "throttle" priority = 23 description = "Throttle specific IP address in US Region" @@ -186,6 +158,7 @@ module "security_policy" { rate_limit_http_request_interval_sec = 60 } } + rate_ban_specific_ip = { action = "rate_based_ban" priority = 24 @@ -193,41 +166,29 @@ module "security_policy" { inIpRange(origin.ip, '47.185.201.160/32') EOT rate_limit_options = { - ban_duration_sec = 120 - enforce_on_key = "ALL" 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" } } - test-sl = { + + deny_java_level3_with_exclude = { action = "deny(502)" priority = 100 - description = "test Sensitivity level policies" + description = "Deny pre-configured rule java-v33-stable at sensitivity level 3" preview = true expression = <<-EOT - evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 4, 'opt_out_rule_ids': ['owasp-crs-v030301-id942350-sqli', 'owasp-crs-v030301-id942360-sqli']}) + evaluatePreconfiguredWaf('java-v33-stable', {'sensitivity': 3, 'opt_out_rule_ids': ['owasp-crs-v030301-id944240-java', 'owasp-crs-v030301-id944120-java']}) EOT } - } - ## threat_intelligence_rules needs manage protection plus - threat_intelligence_rules = { - deny_crawlers_ip = { - action = "deny(502)" - priority = 31 - description = "Deny IP addresses of search engine crawlers" - preview = false - feed = "iplist-search-engines-crawlers" #https://cloud.google.com/armor/docs/threat-intelligence#configure-nti - redirect_type = null - rate_limit_options = {} - } } } - ``` @@ -259,7 +220,7 @@ module "security_policy" { ### Rules -`pre_configured_rules`, `security_rules`, `custom_rules` and `threat_intelligence_rules` are maps of rules. Each rule is a map of strings which provides details about the rule. For example: +`pre_configured_rules`, `security_rules`, `custom_rules` and `threat_intelligence_rules` are maps of rules. Each rule is a map which provides details about the rule. For example: ``` "my_rule" = { @@ -276,23 +237,25 @@ module "security_policy" { } ``` -### Rate limit options -`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) +`action, priority, description, preview, rate_limit_options` are common in all the rule types. + +### 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 = { - ban_duration_sec = 600 # 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 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_http_request_count = 1000 - ban_http_request_interval_sec = 300 # 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 } ``` ## 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 +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: @@ -313,25 +276,19 @@ List of preconfigured rules are available [here](https://cloud.google.com/armor/ ### Sample: + ``` pre_configured_rules = { + "php-stable_level_1_with_include" = { - action = "rate_based_ban" + 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"] - rate_limit_options = { - ban_duration_sec = 600 - enforce_on_key = "ALL" - exceed_action = "deny(502)" - rate_limit_http_request_count = 10 - rate_limit_http_request_interval_sec = 60 - ban_http_request_count = 1000 - ban_http_request_interval_sec = 300 - } } + "rfi_sensitivity_level_4" = { action = "redirect" priority = 4 @@ -341,6 +298,7 @@ pre_configured_rules = { target_rule_set = "rfi-v33-stable" sensitivity_level = 4 } + } ``` @@ -364,18 +322,21 @@ Each rule is key value pair where key is a unique name of the rule and value is ``` ### Sample: + ``` security_rules = { - "deny_project_honeypot" = { + + "deny_project_bad_actor" = { action = "deny(502)" priority = 11 - description = "Deny Malicious IP address from project honeypot" + description = "Deny Malicious IP address from project bad_actor" src_ip_ranges = ["190.217.68.211", "45.116.227.68", "103.43.141.122", "123.11.215.36", ] } - "throttle_project_droptwenty" = { + + "throttle_project_bad_actor4" = { action = "throttle" priority = 15 - description = "Throttle IP addresses from project droptwenty" + description = "Throttle IP addresses from project bad_actor4" src_ip_ranges = ["190.217.68.214", "45.116.227.71", ] preview = true rate_limit_options = { @@ -384,6 +345,7 @@ security_rules = { rate_limit_http_request_interval_sec = 60 } } + } ``` @@ -392,6 +354,7 @@ Add Custom Rules using [Common Expression Language (CEL)](https://cloud.google.c ### 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" @@ -407,8 +370,10 @@ allow_specific_regions = { ``` ### Sample: + ``` custom_rules = { + allow_specific_regions = { action = "allow" priority = 21 @@ -418,22 +383,26 @@ custom_rules = { '[US,AU,BE]'.contains(origin.region_code) EOT } - test-sl = { - action = "deny(502)" - priority = 10 - description = "test Sensitivity level policies" - expression = <<-EOT + + deny_xss_level4_with_exclude = { + action = "deny(502)" + priority = 100 + description = "test preconfigured policy with Sensitivity level and opt out policies" + preview = true + expression = <<-EOT evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 4, 'opt_out_rule_ids': ['owasp-crs-v030301-id942350-sqli', 'owasp-crs-v030301-id942360-sqli']}) EOT } + } ``` ## threat_intelligence_rules: -Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). You need to enable [managed protection plus](https://cloud.google.com/armor/docs/managed-protection-overview#standard_versus_plus) to use this feature +Add Rules based on [threat intelligence](https://cloud.google.com/armor/docs/threat-intelligence). [Managed protection plus](https://cloud.google.com/armor/docs/managed-protection-overview) subscription is needed to use this feature. ### Format: Each rule is key value pair where key is a unique name of the rule and value is the action associated with it. + ``` threat_intelligence_rules = { deny_crawlers_ip = { @@ -449,8 +418,10 @@ threat_intelligence_rules = { ``` ### Sample: + ``` threat_intelligence_rules = { + deny_crawlers_ip = { action = "deny(502)" priority = 31 @@ -458,6 +429,7 @@ threat_intelligence_rules = { preview = true feed = "iplist-search-engines-crawlers" } + } ``` diff --git a/examples/simple-example/README.md b/examples/simple-example/README.md new file mode 100644 index 0000000..8568553 --- /dev/null +++ b/examples/simple-example/README.md @@ -0,0 +1,21 @@ +# Cloud Armor Policy with preconfigured rules, custom rules and security rules + +This is a simple example. Configures a single cloud armor policy with different types of rules. + + + +## 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 | +| project\_id | The project ID | +| security\_policy | Cloud Armor security policy created | + + diff --git a/examples/simple-example/main.tf b/examples/simple-example/main.tf new file mode 100644 index 0000000..6a6639d --- /dev/null +++ b/examples/simple-example/main.tf @@ -0,0 +1,176 @@ +/** + * 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" { + source = "../../" + + project_id = var.project_id + name = "test-casp-policy-${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" + + pre_configured_rules = { + + "sqli_sensitivity_level_4" = { + action = "deny(502)" + priority = 1 + target_rule_set = "sqli-v33-stable" + } + + "xss-stable_level_2_with_exclude" = { + action = "deny(502)" + priority = 2 + description = "XSS Sensitivity Level 2 with excluded rules" + preview = true + target_rule_set = "xss-v33-stable" + sensitivity_level = 2 + exclude_target_rule_ids = ["owasp-crs-v030301-id941380-xss", "owasp-crs-v030301-id941280-xss"] + } + + "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 = { + + "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 = 13 + description = "Rate based ban for address from project dropten as soon as they cross rate limit threshold" + src_ip_ranges = ["190.217.68.213/32", "45.116.227.70", ] + rate_limit_options = { + exceed_action = "deny(502)" + rate_limit_http_request_count = 10 + rate_limit_http_request_interval_sec = 60 + ban_duration_sec = 120 + enforce_on_key = "ALL" + } + } + + "rate_ban_project_dropthirty" = { + action = "rate_based_ban" + priority = 14 + description = "Rate based ban for address from project dropthirty only if they cross banned threshold" + src_ip_ranges = ["190.217.68.213", "45.116.227.70", ] + rate_limit_options = { + exceed_action = "deny(502)" + rate_limit_http_request_count = 10 + 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 = 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 + } + } + + } + + 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" + } + } + + deny_xss_level3_with_exclude = { + action = "deny(502)" + priority = 100 + description = "test Sensitivity level policies" + 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 + } + + } + +} diff --git a/examples/simple-example/outputs.tf b/examples/simple-example/outputs.tf new file mode 100644 index 0000000..e666783 --- /dev/null +++ b/examples/simple-example/outputs.tf @@ -0,0 +1,30 @@ +/** + * 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 "security_policy" { + value = module.cloud_armor.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/simple-example/variables.tf b/examples/simple-example/variables.tf new file mode 100644 index 0000000..e11d5d8 --- /dev/null +++ b/examples/simple-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/simple-example/versions.tf b/examples/simple-example/versions.tf new file mode 100644 index 0000000..e220e27 --- /dev/null +++ b/examples/simple-example/versions.tf @@ -0,0 +1,28 @@ +/** + * 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. + */ + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + google-beta = { + source = "hashicorp/google-beta" + version = "~> 4.0" + } + } +}