diff --git a/README.md b/README.md index 11bd6235..1c770f02 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,27 @@ It supports creating: - A Google Virtual Private Network (VPC) - Subnets within the VPC - Secondary ranges for the subnets (if applicable) - -Sub modules are provided for creating individual vpc, subnets, and routes. See the modules directory for the various sub modules usage. +- routes +- firewall rules + +[Sub modules](./modules/) are provided for creating individual vpc, subnets, routes, firewall rules, and firewall policies. See the [modules](./modules/) directory for the various sub modules usage. +- [vpc](./modules/vpc/) +- [subnet](./modules/subnets/) +- [route](./modules/routes/) +- [firewall rules](./modules/firewall-rules/) +- [hierarchical firewall policy](./modules/hierarchical-firewall-policy/) +- [network firewall policy](./modules/network-firewall-policy/) +- [serverless vpc access connector](./modules/vpc-serverless-connector-beta/) +- [hierarchical firewall policy](./modules/hierarchical-firewall-policy/) ## Compatibility This module is meant for use with Terraform 1.3+ and tested using Terraform 1.4+. If you find incompatibilities using Terraform `>=1.3`, please open an issue. -If you haven't [upgraded][terraform-0.13-upgrade] and need a Terraform -0.12.x-compatible version of this module, the last released version -intended for Terraform 0.12.x is [2.6.0]. ## Usage -You can go to the examples folder, however the usage of the module could be like this in your own main.tf file: +You can go to the [examples](./examples/) folder, however the usage of the module could be like this in your own main.tf file: ```hcl module "vpc" { diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 627ebfc0..f2bbbc2d 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -211,6 +211,21 @@ steps: - verify firewall-rule name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestAll/examples/bidirectional-firewall-rules --stage teardown --verbose'] +- id: converge hierarchical-firewall-policy + waitFor: + - destroy firewall-rule + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestHierarchicalFirewallPolicy --stage apply --verbose'] +- id: verify hierarchical-firewall-policy + waitFor: + - converge hierarchical-firewall-policy + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestHierarchicalFirewallPolicy --stage verify --verbose'] +- id: destroy hierarchical-firewall-policy + waitFor: + - verify hierarchical-firewall-policy + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestHierarchicalFirewallPolicy --stage teardown --verbose'] tags: - 'ci' - 'integration' diff --git a/examples/hierarchical-firewall-policy/README.md b/examples/hierarchical-firewall-policy/README.md new file mode 100644 index 00000000..b953d860 --- /dev/null +++ b/examples/hierarchical-firewall-policy/README.md @@ -0,0 +1,30 @@ +# hierarchical Firewall Policy Rule + +This example creates a Service Account and 2 hierarchical firewall policy. First policy will have a few rules and will be attached to folders. Second policy will not be attached and any folders/org and will not have any rules. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| folder1 | The folder\_id ID 1 to to create firewall policy in | `any` | n/a | yes | +| folder2 | The folder\_id ID 2 to attach firewal policy to | `any` | n/a | yes | +| folder3 | The folder\_id ID 3 to attach firewal policy to | `any` | n/a | yes | +| org\_id | The org ID attach firewal policy to | `any` | n/a | yes | +| project\_id | The project ID to host the network in | `any` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| firewal\_policy\_no\_rules\_id | ID of Firewall policy created without any rules and association | +| firewal\_policy\_no\_rules\_name | Name of Firewall policy created without any rules and association | +| firewal\_policy\_no\_rules\_parent\_folder | Firewall policy parent | +| fw\_policy\_id | Firewall policy ID | +| fw\_policy\_name | Firewall policy name | +| fw\_policy\_parent\_folder | Firewall policy parent | +| project\_id | Project ID | +| rules | Firewall policy rules | +| target\_associations | Firewall policy association | + + diff --git a/examples/hierarchical-firewall-policy/main.tf b/examples/hierarchical-firewall-policy/main.tf new file mode 100644 index 00000000..22927b00 --- /dev/null +++ b/examples/hierarchical-firewall-policy/main.tf @@ -0,0 +1,190 @@ +/** + * 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. + */ + +locals { + prefix = "hierarchical" +} + +resource "random_string" "random_suffix" { + length = 6 + special = false + lower = true + upper = false +} + +resource "google_service_account" "service_account" { + project = var.project_id + account_id = "${local.prefix}-fw-test-svc-acct" + display_name = "${local.prefix} firewall policy test service account" +} + +resource "google_compute_network" "network" { + project = var.project_id + name = "${local.prefix}-network" +} + +resource "google_compute_network" "network_backup" { + project = var.project_id + name = "${local.prefix}-network-backup" +} + +module "firewal_policy" { + source = "terraform-google-modules/network/google//modules/hierarchical-firewall-policy" + version = "~> 9.0" + + parent_node = "folders/${var.folder1}" + policy_name = "${local.prefix}-firewall-policy-${random_string.random_suffix.result}" + description = "test ${local.prefix} firewall policy" + target_org = var.org_id + target_folders = [var.folder2, var.folder3] + + rules = [ + { + priority = "1" + direction = "INGRESS" + action = "allow" + rule_name = "ingress-1" + description = "test ingres rule 1" + enable_logging = true + match = { + src_ip_ranges = ["10.100.0.1/32"] + src_fqdns = ["example.com"] + src_region_codes = ["US"] + src_threat_intelligences = ["iplist-public-clouds"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "2" + direction = "INGRESS" + action = "deny" + rule_name = "ingress-2" + disabled = true + description = "test ingres rule 2" + target_resources = [ + "projects/${var.project_id}/global/networks/${local.prefix}-network-backup", + ] + match = { + src_ip_ranges = ["10.100.0.2/32"] + src_fqdns = ["example.org"] + src_region_codes = ["BE"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "3" + direction = "INGRESS" + action = "allow" + rule_name = "ingress-3" + disabled = true + description = "test ingres rule 3" + enable_logging = true + target_service_accounts = ["fw-test-svc-acct@${var.project_id}.iam.gserviceaccount.com"] + match = { + src_ip_ranges = ["10.100.0.3/32"] + dest_ip_ranges = ["10.100.0.103/32"] + layer4_configs = [ + { + ip_protocol = "tcp" + ports = ["80"] + }, + ] + } + }, + { + priority = "101" + direction = "EGRESS" + action = "allow" + rule_name = "egress-101" + description = "test egress rule 101" + enable_logging = true + match = { + src_ip_ranges = ["10.100.0.2/32"] + dest_fqdns = ["example.com"] + dest_region_codes = ["US"] + dest_threat_intelligences = ["iplist-public-clouds"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "102" + direction = "EGRESS" + action = "deny" + rule_name = "egress-102" + disabled = true + description = "test egress rule 102" + target_resources = [ + "projects/${var.project_id}/global/networks/${local.prefix}-network", + ] + match = { + src_ip_ranges = ["10.100.0.102/32"] + dest_ip_ranges = ["10.100.0.2/32"] + dest_region_codes = ["AR"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "103" + direction = "EGRESS" + action = "allow" + rule_name = "egress-103" + disabled = true + description = "test ingres rule 103" + enable_logging = true + target_service_accounts = ["fw-test-svc-acct@${var.project_id}.iam.gserviceaccount.com"] + match = { + dest_ip_ranges = ["10.100.0.103/32"] + layer4_configs = [ + { + ip_protocol = "tcp" + ports = ["80", "8080", "8081-8085"] + }, + ] + } + }, + + ] + depends_on = [ + google_compute_network.network, + google_compute_network.network_backup, + ] + +} + +module "firewal_policy_no_rule" { + source = "terraform-google-modules/network/google//modules/hierarchical-firewall-policy" + version = "~> 9.0" + + parent_node = "folders/${var.folder1}" + policy_name = "${local.prefix}-firewall-policy-no-rules-${random_string.random_suffix.result}" + description = "${local.prefix} test firewall policy without any rules" +} diff --git a/examples/hierarchical-firewall-policy/outputs.tf b/examples/hierarchical-firewall-policy/outputs.tf new file mode 100644 index 00000000..4e5aa7d0 --- /dev/null +++ b/examples/hierarchical-firewall-policy/outputs.tf @@ -0,0 +1,60 @@ +/** + * 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 "project_id" { + value = var.project_id + description = "Project ID" +} + +output "fw_policy_id" { + value = module.firewal_policy.fw_policy.name + description = "Firewall policy ID" +} + +output "fw_policy_parent_folder" { + value = module.firewal_policy.fw_policy.parent + description = "Firewall policy parent" +} + +output "fw_policy_name" { + value = module.firewal_policy.fw_policy.short_name + description = "Firewall policy name" +} + +output "target_associations" { + value = module.firewal_policy.target_associations + description = "Firewall policy association" +} + +output "rules" { + value = module.firewal_policy.rules + description = "Firewall policy rules" +} + +output "firewal_policy_no_rules_id" { + value = module.firewal_policy_no_rule.fw_policy.name + description = "ID of Firewall policy created without any rules and association" +} + +output "firewal_policy_no_rules_name" { + value = module.firewal_policy_no_rule.fw_policy.short_name + description = "Name of Firewall policy created without any rules and association" +} + +output "firewal_policy_no_rules_parent_folder" { + value = module.firewal_policy.fw_policy.parent + description = "Firewall policy parent" +} diff --git a/examples/hierarchical-firewall-policy/variables.tf b/examples/hierarchical-firewall-policy/variables.tf new file mode 100644 index 00000000..2d6a5544 --- /dev/null +++ b/examples/hierarchical-firewall-policy/variables.tf @@ -0,0 +1,35 @@ +/** + * 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 ID to host the network in" +} + +variable "folder1" { + description = "The folder_id ID 1 to to create firewall policy in" +} + +variable "folder2" { + description = "The folder_id ID 2 to attach firewal policy to" +} + +variable "folder3" { + description = "The folder_id ID 3 to attach firewal policy to" +} + +variable "org_id" { + description = "The org ID attach firewal policy to" +} diff --git a/modules/fabric-net-firewall/main.tf b/modules/fabric-net-firewall/main.tf index f41df390..62d55ed0 100644 --- a/modules/fabric-net-firewall/main.tf +++ b/modules/fabric-net-firewall/main.tf @@ -116,7 +116,6 @@ resource "google_compute_firewall" "allow-tag-https" { ################################################################################ resource "google_compute_firewall" "custom" { - # provider = "google-beta" for_each = var.custom_rules name = each.key description = each.value.description diff --git a/modules/hierarchical-firewall-policy/README.md b/modules/hierarchical-firewall-policy/README.md new file mode 100644 index 00000000..1cdcf5d2 --- /dev/null +++ b/modules/hierarchical-firewall-policy/README.md @@ -0,0 +1,249 @@ +# Google Cloud Hierarchical Firewall Policy + +This module allows creation of [hierarchical firewall policy](https://cloud.google.com/firewall/docs/firewall-policies) and [Rules](https://cloud.google.com/firewall/docs/firewall-policies-rule-details). It can also attach hierarchical firewall policy to multiple folders or an organization. It can create both [Cloud Firewall Essentials](https://cloud.google.com/firewall/docs/about-firewalls#firewall-essentials) and [Cloud Firewall Standard](https://cloud.google.com/firewall/docs/about-firewalls#firewall-standard) tier rules. You can create an empty firewall policy without any rules and without attaching it to any folder or organization. Hierarchical firewall policy limitations are available [here](https://cloud.google.com/firewall/docs/using-firewall-policies#limitations) + +## Module Format + +Variable `rules` details are available [here](#firwall-policy-rules-format). High level format of this module is as follows: + +``` +module "hierarchical_firewall_policy" { + source = "terraform-google-modules/network/google//modules/hierarchical-firewall-policy" + version = "~> 9.0" + + parent_node = "folders/123456789012" + policy_name = "test-policy" + description = "test firewall policy" + target_folders = ["123456789012", "987654321098"] + target_org = "123456123456" + + rules = [ + {}, + {}, + ] +} +``` + +## Usage + +There are examples included for [hierarchical firewall policy](../../examples/hierarchical-firewall-policy/) is in the [examples](../../examples/) folder. Basic usage of this module is as follows: + +```hcl +module "firewal_policy" { + source = "terraform-google-modules/network/google//modules/hierarchical-firewall-policy" + version = "~> 9.0" + + parent_node = "folders/123456789012" + policy_name = "test-policy" + description = "test firewall policy" + target_folders = ["123456789012", "987654321098"] + target_org = "123456123456" + + rules = [ + { + priority = "1" + direction = "INGRESS" + action = "allow" + rule_name = "ingress-1" + description = "test ingres rule 1" + enable_logging = true + match = { + src_ip_ranges = ["10.100.0.1/32"] + src_fqdns = ["example.com"] + src_region_codes = ["US"] + src_threat_intelligences = ["iplist-public-clouds"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "2" + direction = "INGRESS" + action = "deny" + rule_name = "ingress-2" + disabled = true + description = "test ingres rule 2" + match = { + src_ip_ranges = ["10.100.0.2/32"] + src_fqdns = ["example.org"] + src_region_codes = ["BE"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "3" + direction = "INGRESS" + action = "allow" + rule_name = "ingress-3" + disabled = true + description = "test ingres rule 3" + enable_logging = true + target_service_accounts = ["fw-test-svc-acct@${var.project_id}.iam.gserviceaccount.com"] + match = { + src_ip_ranges = ["10.100.0.3/32"] + dest_ip_ranges = ["10.100.0.103/32"] + layer4_configs = [ + { + ip_protocol = "tcp" + ports = ["80"] + }, + ] + } + }, + { + priority = "101" + direction = "EGRESS" + action = "allow" + rule_name = "egress-101" + description = "test egress rule 101" + enable_logging = true + match = { + src_ip_ranges = ["10.100.0.2/32"] + dest_fqdns = ["example.com"] + dest_region_codes = ["US"] + dest_threat_intelligences = ["iplist-public-clouds"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "102" + direction = "EGRESS" + action = "deny" + rule_name = "egress-102" + disabled = true + description = "test egress rule 102" + match = { + src_ip_ranges = ["10.100.0.102/32"] + dest_ip_ranges = ["10.100.0.2/32"] + dest_region_codes = ["AR"] + layer4_configs = [ + { + ip_protocol = "all" + }, + ] + } + }, + { + priority = "103" + direction = "EGRESS" + action = "allow" + rule_name = "egress-103" + disabled = true + description = "test ingres rule 103" + enable_logging = true + target_service_accounts = ["fw-test-svc-acct@${var.project_id}.iam.gserviceaccount.com"] + match = { + dest_ip_ranges = ["10.100.0.103/32"] + layer4_configs = [ + { + ip_protocol = "tcp" + ports = ["80", "8080", "8081-8085"] + }, + ] + } + }, + + ] + +} +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| description | An optional description of this resource. Provide this property when you create the resource | `string` | `null` | no | +| parent\_node | The parent of the firewall policy. Parent should be in format organizations/ or folders/ | `string` | n/a | yes | +| policy\_name | User-provided name of the hierarchical firewall policy | `string` | n/a | yes | +| rules | List of Ingress/Egress rules |
list(object({
priority = number
direction = string
action = string
rule_name = optional(string)
disabled = optional(bool)
description = optional(string)
enable_logging = optional(bool)
target_service_accounts = optional(list(string), [])
target_resources = optional(list(string), [])
match = object({
src_ip_ranges = optional(list(string), [])
src_fqdns = optional(list(string), [])
src_region_codes = optional(list(string), [])
src_threat_intelligences = optional(list(string), [])
src_address_groups = optional(list(string), [])
dest_ip_ranges = optional(list(string), [])
dest_fqdns = optional(list(string), [])
dest_region_codes = optional(list(string), [])
dest_threat_intelligences = optional(list(string), [])
dest_address_groups = optional(list(string), [])
layer4_configs = optional(list(object({
ip_protocol = optional(string, "all")
ports = optional(list(string), [])
})), [{}])
})
}))
| `[]` | no | +| target\_folders | List of target folders IDs that the firewall policy will be attached to | `list(string)` | `[]` | no | +| target\_org | Target org id that the firewall policy will be attached to | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| fw\_policy | Firewall policy created | +| rules | Firewall policy rules created | +| target\_associations | folders/orgs associations created | + + + +## Firwall Policy Rules Format + +In a [firewall policy rule](https://cloud.google.com/firewall/docs/firewall-policies-rule-details), you specify a set of components that define what the rule does. Some of the values are optional and some have default value. See [Inputs](#Inputs). For sample code check [hierarchical firewall policy](../../examples/hierarchical-firewall-policy/) in [examples](../../examples/) folder. + +- `priority`: An integer indicating the priority of a rule in the list. The `priority` must be a positive value between 0 and 2147483647 and It has to be unique for every rule. +- `target_resources`: A list of network resource URLs to which this rule applies. This field allows you to control which network's VMs get this rule. If this field is left blank, all VMs within the organization will receive the rule +- `target_service_accounts`: A list of network resource URLs to which this rule applies. This field allows you to control which network's VMs get this rule. If this field is left blank, all VMs within the organization will receive the rule +- `dest_fqdns`, `dest_region_codes`, `dest_threat_intelligences` and `dest_address_groups` values are not needed and ignored by the this for `INGRESS` policies. +- `src_fqdns`, `src_region_codes`, `src_threat_intelligences` and `src_address_groups` values are not needed and ignored by this module for `EGRESS` policies. +- `layer4_configs` is a list of maps. + - `ip_protocol`: IP protocol to which this rule applies. The protocol type is required when creating a firewall rule. This value can either be one of the following well known protocol strings (`tcp`, `udp`, `icmp`, `esp`, `ah`, `ipip`, `sctp`), or the IP protocol number. + - `ports`: An optional list of ports to which this rule applies. Field is only applicable for UDP or TCP protocol. Each entry must be either an integer or a range. If not specified, this rule applies to connections through any port. + +### Format + +``` + { + priority = 1 + direction = "INGRESS" + action = allow + rule_name = "my-test-policy" + disabled = false + description = "My test firewall policy" + enable_logging = true + target_service_accounts = [] + target_resources = [] + + match = { + src_ip_ranges = [] + src_fqdns = [] + src_region_codes = [] + src_address_groups = [] + src_threat_intelligences = [] + dest_ip_ranges = [] + dest_fqdns = [] + dest_region_codes = [] + dest_threat_intelligences = [] + dest_address_groups = [] + layer4_configs = [ + { + ip_protocol = "tcp" + ports = ["80", "8080", "8081-8085"] + }, + ] + } + } +``` + +## Requirements +### Installed Software +- [Terraform](https://www.terraform.io/downloads.html) >= 1.3 +- [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) >= 4.64 +- [Terraform Provider for GCP Beta](https://github.com/terraform-providers/terraform-provider-google-beta) >= 4.64 + +### Configure a Service Account +In order to execute this module you must have a Service Account with the following roles: + +- roles/compute.orgFirewallPolicyAdmin at organization level +- compute.orgSecurityResourceAdmin on the folder to which the firewall policy will be attached +- compute.orgFirewallPolicyUser on the folder to which the firewall policy will be attached + +### Enable API's +In order to operate with the Service Account you must activate the following API on the project where the Service Account was created: + +- compute.googleapis.com +- networksecurity.googleapis.com diff --git a/modules/hierarchical-firewall-policy/main.tf b/modules/hierarchical-firewall-policy/main.tf new file mode 100644 index 00000000..8fbcca84 --- /dev/null +++ b/modules/hierarchical-firewall-policy/main.tf @@ -0,0 +1,74 @@ +/** + * 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. + */ + +locals { + attachment_targets = toset(flatten([formatlist("folders/%s", var.target_folders), local.org])) + org = var.target_org == null ? [] : [format("organizations/%s", var.target_org)] +} + +# Create a firewall policy +resource "google_compute_firewall_policy" "fw_policy" { + short_name = var.policy_name + parent = var.parent_node + description = var.description +} + +# Associate the firewall policy with the target folders and organizations +resource "google_compute_firewall_policy_association" "target_associations" { + for_each = length(local.attachment_targets) > 0 ? local.attachment_targets : [] + name = element(split("/", each.value), length(split("/", each.value)) - 1) + attachment_target = each.value + firewall_policy = google_compute_firewall_policy.fw_policy.name +} + +# Create firewall policy rules +resource "google_compute_firewall_policy_rule" "rules" { + provider = google-beta + + for_each = { for x in var.rules : x.priority => x } + priority = each.key + action = each.value.action + description = each.value.description + direction = each.value.direction + disabled = each.value.disabled + enable_logging = each.value.enable_logging + firewall_policy = google_compute_firewall_policy.fw_policy.name + target_service_accounts = each.value.target_service_accounts + target_resources = formatlist("https://www.googleapis.com/compute/v1/%s", each.value.target_resources) + + match { + src_ip_ranges = lookup(each.value.match, "src_ip_ranges", []) + src_fqdns = each.value.direction == "INGRESS" ? lookup(each.value.match, "src_fqdns", []) : [] + src_region_codes = each.value.direction == "INGRESS" ? lookup(each.value.match, "src_region_codes", []) : [] + src_threat_intelligences = each.value.direction == "INGRESS" ? lookup(each.value.match, "src_threat_intelligences", []) : [] + src_address_groups = each.value.direction == "INGRESS" ? lookup(each.value.match, "src_address_groups", []) : [] + dest_ip_ranges = lookup(each.value.match, "dest_ip_ranges", []) + dest_fqdns = each.value.direction == "EGRESS" ? lookup(each.value.match, "dest_fqdns", []) : [] + dest_region_codes = each.value.direction == "EGRESS" ? lookup(each.value.match, "dest_region_codes", []) : [] + dest_threat_intelligences = each.value.direction == "EGRESS" ? lookup(each.value.match, "dest_threat_intelligences", []) : [] + dest_address_groups = each.value.direction == "EGRESS" ? lookup(each.value.match, "dest_address_groups", []) : [] + + dynamic "layer4_configs" { + for_each = each.value.match.layer4_configs + content { + ip_protocol = layer4_configs.value.ip_protocol + ports = layer4_configs.value.ports + } + } + + } + +} diff --git a/examples/global-network-firewall-policy/versions.tf b/modules/hierarchical-firewall-policy/outputs.tf similarity index 59% rename from examples/global-network-firewall-policy/versions.tf rename to modules/hierarchical-firewall-policy/outputs.tf index e9a41a32..75e69314 100644 --- a/examples/global-network-firewall-policy/versions.tf +++ b/modules/hierarchical-firewall-policy/outputs.tf @@ -14,17 +14,17 @@ * limitations under the License. */ -terraform { - required_version = ">= 1.3.0" +output "fw_policy" { + value = google_compute_firewall_policy.fw_policy + description = "Firewall policy created" +} + +output "target_associations" { + value = google_compute_firewall_policy_association.target_associations + description = "folders/orgs associations created" +} - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.64" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.64" - } - } +output "rules" { + value = google_compute_firewall_policy_rule.rules + description = "Firewall policy rules created" } diff --git a/modules/hierarchical-firewall-policy/variables.tf b/modules/hierarchical-firewall-policy/variables.tf new file mode 100644 index 00000000..e63170c6 --- /dev/null +++ b/modules/hierarchical-firewall-policy/variables.tf @@ -0,0 +1,79 @@ +/** + * 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 "parent_node" { + description = "The parent of the firewall policy. Parent should be in format organizations/ or folders/" + type = string +} + +variable "policy_name" { + description = "User-provided name of the hierarchical firewall policy" + type = string + validation { + condition = can(regex("^[a-z][a-z0-9-]{0,62}$", var.policy_name)) + error_message = "User-provided name of the Organization firewall policy. The name should be unique in the organization in which the firewall policy is created. The 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" + } +} + +variable "description" { + description = "An optional description of this resource. Provide this property when you create the resource" + type = string + default = null +} + +variable "target_folders" { + description = "List of target folders IDs that the firewall policy will be attached to" + type = list(string) + default = [] +} + +variable "target_org" { + description = "Target org id that the firewall policy will be attached to" + type = string + default = null +} + +variable "rules" { + description = "List of Ingress/Egress rules" + type = list(object({ + priority = number + direction = string + action = string + rule_name = optional(string) + disabled = optional(bool) + description = optional(string) + enable_logging = optional(bool) + target_service_accounts = optional(list(string), []) + target_resources = optional(list(string), []) + match = object({ + src_ip_ranges = optional(list(string), []) + src_fqdns = optional(list(string), []) + src_region_codes = optional(list(string), []) + src_threat_intelligences = optional(list(string), []) + src_address_groups = optional(list(string), []) + dest_ip_ranges = optional(list(string), []) + dest_fqdns = optional(list(string), []) + dest_region_codes = optional(list(string), []) + dest_threat_intelligences = optional(list(string), []) + dest_address_groups = optional(list(string), []) + layer4_configs = optional(list(object({ + ip_protocol = optional(string, "all") + ports = optional(list(string), []) + })), [{}]) + }) + })) + default = [] +} diff --git a/examples/regional-network-firewall-policy/versions.tf b/modules/hierarchical-firewall-policy/versions.tf similarity index 86% rename from examples/regional-network-firewall-policy/versions.tf rename to modules/hierarchical-firewall-policy/versions.tf index b0d9480b..e760c48c 100644 --- a/examples/regional-network-firewall-policy/versions.tf +++ b/modules/hierarchical-firewall-policy/versions.tf @@ -27,4 +27,8 @@ terraform { version = ">= 4.64, < 6" } } + + provider_meta "google" { + module_name = "blueprints/terraform/terraform-google-network:hierarchical-firewall-policy/v9.0.0" + } } diff --git a/modules/network-firewall-policy/.gitignore b/modules/network-firewall-policy/.gitignore deleted file mode 100644 index 3f5ca68a..00000000 --- a/modules/network-firewall-policy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -terraform.tfvars diff --git a/modules/network-firewall-policy/README.md b/modules/network-firewall-policy/README.md index 35a464a2..d0ee545d 100644 --- a/modules/network-firewall-policy/README.md +++ b/modules/network-firewall-policy/README.md @@ -1,15 +1,15 @@ -# Google Cloud VPC Firewall Policy +# Google Cloud Network Firewall Policy -This module allows creation of [Global](https://cloud.google.com/firewall/docs/network-firewall-policies) and [Regional](https://cloud.google.com/firewall/docs/regional-firewall-policies) Network Firewall Policy and [Rules](https://cloud.google.com/firewall/docs/firewall-policies-rule-details). It can also attach network firewall policy to multiple VPCs. Module will create a `Regional` network firewall policy if a value is provided for the variable `policy_region`, otherwise a `Global` network firewall policy will be created. Module can create both [Cloud Firewall Essentials](https://cloud.google.com/firewall/docs/about-firewalls#firewall-essentials) and [Cloud Firewall Standard](https://cloud.google.com/firewall/docs/about-firewalls#firewall-standard) tier rules. Firewall Rules and Target VPC attachment is optional. +This module allows creation of [Global](https://cloud.google.com/firewall/docs/network-firewall-policies), [Regional](https://cloud.google.com/firewall/docs/regional-firewall-policies) Network Firewall Policy and [Rules](https://cloud.google.com/firewall/docs/firewall-policies-rule-details). It can also attach network firewall policy to multiple VPCs. Module will create a `Regional` network firewall policy if a value is provided for the variable `policy_region`, otherwise a `Global` network firewall policy will be created. Module can create both [Cloud Firewall Essentials](https://cloud.google.com/firewall/docs/about-firewalls#firewall-essentials) and [Cloud Firewall Standard](https://cloud.google.com/firewall/docs/about-firewalls#firewall-standard) tier rules. Firewall Rules and Target VPC attachment is optional. ## Module Format Variable `rules` details are available [here](#firwall-policy-rules-format). High level format of this module is as follows: ``` -module "firewall_rules" { +module "network_firewall_policy" { source = "terraform-google-modules/network/google//modules/network-firewall-policy" - version = "~> 8.0" + version = "~> 9.0" project_id = var.project_id policy_name = "my-firewall-policy" description = "Test firewall policy" @@ -27,9 +27,9 @@ module "firewall_rules" { There are examples included for [global](https://github.com/terraform-google-modules/terraform-google-network/tree/master/examples/global-network-firewall-policy) and [regional](https://github.com/terraform-google-modules/terraform-google-network/tree/master/examples/regional-network-firewall-policy) firewall policies in the [examples](https://github.com/terraform-google-modules/terraform-google-network/tree/master/examples/) folder. Basic usage of this module is as follows: ```hcl -module "firewall_rules" { +module "network_firewall_policy" { source = "terraform-google-modules/network/google//modules/network-firewall-policy" - version = "~> 7.2" + version = "~> 9.0" project_id = var.project_id policy_name = "my-firewall-policy" description = "Test firewall policy" @@ -193,3 +193,21 @@ In a [firewall policy rule](https://cloud.google.com/firewall/docs/firewall-poli } } ``` + +## Requirements +### Installed Software +- [Terraform](https://www.terraform.io/downloads.html) >= 1.3 +- [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) >= 4.64 +- [Terraform Provider for GCP Beta](https://github.com/terraform-providers/terraform-provider-google-beta) >= 4.64 + +### Configure a Service Account +In order to execute this module you must have a Service Account with the following roles: + +- roles/compute.securityAdmin +- roles/compute.networkAdmin + +### Enable API's +In order to operate with the Service Account you must activate the following API on the project where the Service Account was created: + +- compute.googleapis.com +- networksecurity.googleapis.com diff --git a/test/integration/global-network-firewall-policy/global_firewall_policy_test.go b/test/integration/global-network-firewall-policy/global_firewall_policy_test.go index f07378f9..fd9b0d5b 100644 --- a/test/integration/global-network-firewall-policy/global_firewall_policy_test.go +++ b/test/integration/global-network-firewall-policy/global_firewall_policy_test.go @@ -118,7 +118,7 @@ func TestGlobalNetworkFirewallPolicy(t *testing.T) { assert.Equal("10.100.0.2/32", sp102.Get("match.destIpRanges").Array()[0].String(), "has expected destIpRanges") assert.Equal("AR", sp102.Get("match.destRegionCodes").Array()[0].String(), "has expected destRegionCodes") assert.Equal("all", sp102.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") - secureTags102 := sp2.Get("targetSecureTags").Array() + secureTags102 := sp102.Get("targetSecureTags").Array() assert.Equal(1, len(secureTags102), "should have the correct targetSecureTags count - 1") rule103 := gcloud.Runf(t, "compute network-firewall-policies rules describe 103 --global-firewall-policy --firewall-policy %s --project %s", policyName, projectId) diff --git a/test/integration/hierarchical-firewall-policy/hierarchical_firewall_policy_test.go b/test/integration/hierarchical-firewall-policy/hierarchical_firewall_policy_test.go new file mode 100644 index 00000000..a30912f5 --- /dev/null +++ b/test/integration/hierarchical-firewall-policy/hierarchical_firewall_policy_test.go @@ -0,0 +1,126 @@ +// Copyright 2021 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 hierarchical_firewall_policy + +import ( + "testing" + // "fmt" + "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 TestHierarchicalFirewallPolicy(t *testing.T) { + fwp := tft.NewTFBlueprintTest(t) + fwp.DefineVerify( + func(assert *assert.Assertions) { + + // Commenting Default Verify because the provider updates rule_tuple_count, results in a permadiff. + fwp.DefaultVerify(assert) + projectId := fwp.GetStringOutput("project_id") + policyName := fwp.GetStringOutput("fw_policy_name") + policyId := fwp.GetStringOutput("fw_policy_id") + policyParentFolder := fwp.GetStringOutput("fw_policy_parent_folder") + PolicyNoRulesName := fwp.GetStringOutput("firewal_policy_no_rules_name") + PolicyNoRulesId := fwp.GetStringOutput("firewal_policy_no_rules_id") + PolicyNoRulesParentFolder := fwp.GetStringOutput("firewal_policy_no_rules_parent_folder") + + policyNoRules := gcloud.Runf(t, "compute firewall-policies describe %s --project %s", PolicyNoRulesId, projectId) + spnr := policyNoRules.Array()[0] + assert.Equal(PolicyNoRulesName, spnr.Get("displayName").String(), "has expected name") + assert.Equal("hierarchical test firewall policy without any rules", spnr.Get("description").String(), "has expected description") + assert.Equal(PolicyNoRulesParentFolder, spnr.Get("parent").String(), "has expected parent folder") + + policy := gcloud.Runf(t, "compute firewall-policies describe %s --project %s", policyId, projectId) + sp := policy.Array()[0] + assert.Equal(policyName, sp.Get("displayName").String(), "has expected policy name") + assert.Equal(policyParentFolder, sp.Get("parent").String(), "has expected parent folder") + assert.Equal("test hierarchical firewall policy", sp.Get("description").String(), "has expected description") + + rule1 := gcloud.Runf(t, "compute firewall-policies rules describe 1 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("allow", rule1.Get("action").String(), "Rule1 action should be allow") + assert.Equal("test ingres rule 1", rule1.Get("description").String(), "Rule1 has expected description") + assert.Equal("1", rule1.Get("priority").String(), "Rule1 has priority 1") + assert.Equal("INGRESS", rule1.Get("direction").String(), "Rule1 direction should be INGRESS") + assert.False(rule1.Get("disabled").Bool(), "Rule1 should be enabled") + assert.True(rule1.Get("enableLogging").Bool(), "Rule1 Logging should be enabled") + assert.Equal("example.com", rule1.Get("match.srcFqdns").Array()[0].String(), "has expected srcFqdns") + assert.Equal("10.100.0.1/32", rule1.Get("match.srcIpRanges").Array()[0].String(), "has expected srcIpRanges") + assert.Equal("US", rule1.Get("match.srcRegionCodes").Array()[0].String(), "has expected srcRegionCodes") + assert.Equal("all", rule1.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + + rule2 := gcloud.Runf(t, "compute firewall-policies rules describe 2 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("deny", rule2.Get("action").String(), "Rule2 action should be deny") + assert.Equal("test ingres rule 2", rule2.Get("description").String(), "Rule2 has expected description") + assert.Equal("INGRESS", rule2.Get("direction").String(), "Rule2 direction should be INGRESS") + assert.True(rule2.Get("disabled").Bool(), "Rule2 should be Disabled") + assert.False(rule2.Get("enableLogging").Bool(), "Rule2 Logging should be disabled") + assert.Equal("example.org", rule2.Get("match.srcFqdns").Array()[0].String(), "has expected srcFqdns") + assert.Equal("10.100.0.2/32", rule2.Get("match.srcIpRanges").Array()[0].String(), "has expected srcIpRanges") + assert.Equal("BE", rule2.Get("match.srcRegionCodes").Array()[0].String(), "has expected srcRegionCodes") + assert.Equal("all", rule2.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + + rule3 := gcloud.Runf(t, "compute firewall-policies rules describe 3 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("allow", rule3.Get("action").String(), "Rule3 action should be allow") + assert.Equal("test ingres rule 3", rule3.Get("description").String(), "Rule3 has expected description") + assert.Equal("INGRESS", rule3.Get("direction").String(), "Rule3 direction should be INGRESS") + assert.True(rule3.Get("disabled").Bool(), "Rule3 should be disabled") + assert.True(rule3.Get("enableLogging").Bool(), "Rule3 Logging should be enabled") + assert.Equal("10.100.0.3/32", rule3.Get("match.srcIpRanges").Array()[0].String(), "has expected srcIpRanges") + assert.Equal("10.100.0.103/32", rule3.Get("match.destIpRanges").Array()[0].String(), "has expected destIpRanges") + assert.Equal("tcp", rule3.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + layer4ConfigsPorts3 := rule3.Get("match.layer4Configs").Array()[0].Get("ports").Array() + assert.Equal(1, len(layer4ConfigsPorts3), "should have the correct layer4Configs port count") + targetServiceAccounts3 := rule3.Get("targetServiceAccounts").Array() + assert.Equal(1, len(targetServiceAccounts3), "Rule3 should have the correct targetServiceAccounts count") + + rule101 := gcloud.Runf(t, "compute firewall-policies rules describe 101 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("allow", rule101.Get("action").String(), "Rule101 action should be allow") + assert.Equal("test egress rule 101", rule101.Get("description").String(), "Rule101has expected description") + assert.Equal("EGRESS", rule101.Get("direction").String(), "Rule101 direction should be EGRESS") + assert.False(rule101.Get("disabled").Bool(), "Rule101 should be enabled") + assert.True(rule101.Get("enableLogging").Bool(), "Rule101 Logging should be enabled") + assert.Equal("example.com", rule101.Get("match.destFqdns").Array()[0].String(), "has expected destFqdns") + assert.Equal("10.100.0.2/32", rule101.Get("match.srcIpRanges").Array()[0].String(), "has expected srcIpRanges") + assert.Equal("US", rule101.Get("match.destRegionCodes").Array()[0].String(), "has expected destRegionCodes") + assert.Equal("all", rule101.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + assert.Equal("iplist-public-clouds", rule101.Get("match.destThreatIntelligences").Array()[0].String(), "has expected srcIpRanges") + + rule102 := gcloud.Runf(t, "compute firewall-policies rules describe 102 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("deny", rule102.Get("action").String(), "Rule102 action should be deny") + assert.Equal("test egress rule 102", rule102.Get("description").String(), "Rule102 has expected description") + assert.Equal("EGRESS", rule102.Get("direction").String(), "Rule102 direction should be EGRESS") + assert.True(rule102.Get("disabled").Bool(), "Rule102 should be disabled") + assert.False(rule102.Get("enableLogging").Bool(), "Rule102 Logging should be disabled") + assert.Equal("10.100.0.2/32", rule102.Get("match.destIpRanges").Array()[0].String(), "has expected destIpRanges") + assert.Equal("AR", rule102.Get("match.destRegionCodes").Array()[0].String(), "has expected destRegionCodes") + assert.Equal("all", rule102.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + + rule103 := gcloud.Runf(t, "compute firewall-policies rules describe 103 --firewall-policy %s --project %s", policyId, projectId) + assert.Equal("allow", rule103.Get("action").String(), "Rule103 action should be allow") + assert.Equal("test ingres rule 103", rule103.Get("description").String(), "Rule103 has expected description") + assert.Equal("EGRESS", rule103.Get("direction").String(), "Rule103 direction should be EGRESS") + assert.True(rule103.Get("disabled").Bool(), "Rule103 should be disabled") + assert.True(rule103.Get("enableLogging").Bool(), "Rule103 Logging should be enabled") + assert.Equal("10.100.0.103/32", rule103.Get("match.destIpRanges").Array()[0].String(), "has expected destIpRanges") + assert.Equal("tcp", rule103.Get("match.layer4Configs").Array()[0].Get("ipProtocol").String(), "has expected layer4Configs.ipProtocol") + layer4ConfigsPorts103 := rule103.Get("match.layer4Configs").Array()[0].Get("ports").Array() + assert.Equal(3, len(layer4ConfigsPorts103), "Rule3 should have the correct layer4Configs.ports count") + targetServiceAccounts103 := rule103.Get("targetServiceAccounts").Array() + assert.Equal(1, len(targetServiceAccounts103), "Rule3should have the correct targetServiceAccounts count") + + }) + fwp.Test() +} diff --git a/test/setup/README.md b/test/setup/README.md index c2358652..6673751e 100644 --- a/test/setup/README.md +++ b/test/setup/README.md @@ -40,13 +40,17 @@ run the `make test_integration_docker` target | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | billing\_account | The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ | `any` | n/a | yes | -| folder\_id | The folder to deploy in | `any` | n/a | yes | +| folder\_id | The folder to deploy in | `any` | `null` | no | | org\_id | The numeric organization id | `any` | n/a | yes | ## Outputs | Name | Description | |------|-------------| +| folder1 | n/a | +| folder2 | n/a | +| folder3 | n/a | +| org\_id | n/a | | project\_id | n/a | | sa\_key | n/a | diff --git a/test/setup/iam.tf b/test/setup/iam.tf index fd4f3389..c13c61d3 100644 --- a/test/setup/iam.tf +++ b/test/setup/iam.tf @@ -24,6 +24,7 @@ locals { "roles/dns.admin", "roles/resourcemanager.tagAdmin", "roles/iam.serviceAccountAdmin", + "roles/compute.orgFirewallPolicyAdmin", ] } @@ -44,3 +45,43 @@ resource "google_project_iam_member" "int_test" { resource "google_service_account_key" "int_test" { service_account_id = google_service_account.int_test.id } + +# due to limitation we need to assign this role at org level otherwise TF throws an error. Issue is only happening when deployedusing APIs like in TF. Console works fine +# b/265054739 + +resource "google_organization_iam_member" "organization" { + org_id = var.org_id + role = "roles/compute.orgFirewallPolicyAdmin" + member = "serviceAccount:${google_service_account.int_test.email}" +} + + +# Roles needed on folders to create firewall policies +resource "google_folder_iam_member" "folder1" { + for_each = toset(["roles/compute.orgSecurityResourceAdmin", ]) + folder = google_folder.folder1.id + role = each.value + member = "serviceAccount:${google_service_account.int_test.email}" +} + +# Roles needed on folders to create Attach firewall policies to the folders/org + +resource "google_organization_iam_member" "org_permission" { + org_id = var.org_id + role = "roles/compute.orgSecurityResourceAdmin" + member = "serviceAccount:${google_service_account.int_test.email}" +} + +resource "google_folder_iam_member" "folder2" { + for_each = toset(["roles/compute.orgSecurityResourceAdmin", "roles/compute.orgFirewallPolicyUser"]) + folder = google_folder.folder2.id + role = each.value + member = "serviceAccount:${google_service_account.int_test.email}" +} + +resource "google_folder_iam_member" "folder3" { + for_each = toset(["roles/compute.orgSecurityResourceAdmin", "roles/compute.orgFirewallPolicyUser"]) + folder = google_folder.folder3.id + role = each.value + member = "serviceAccount:${google_service_account.int_test.email}" +} diff --git a/test/setup/main.tf b/test/setup/main.tf index 1ba5e5fe..6bd5431b 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -14,6 +14,28 @@ * limitations under the License. */ +resource "random_string" "random_suffix" { + length = 4 + special = false + lower = true + upper = false +} + +resource "google_folder" "folder1" { + display_name = "ci-network1-${random_string.random_suffix.result}" + parent = var.folder_id != null ? "folders/${var.folder_id}" : "organizations/${var.org_id}" +} + +resource "google_folder" "folder2" { + display_name = "ci-network2-${random_string.random_suffix.result}" + parent = var.folder_id != null ? "folders/${var.folder_id}" : "organizations/${var.org_id}" +} + +resource "google_folder" "folder3" { + display_name = "ci-network3-${random_string.random_suffix.result}" + parent = var.folder_id != null ? "folders/${var.folder_id}" : "organizations/${var.org_id}" +} + module "project" { source = "terraform-google-modules/project-factory/google" version = "~> 14.0" @@ -21,7 +43,7 @@ module "project" { name = "ci-network" random_project_id = "true" org_id = var.org_id - folder_id = var.folder_id + folder_id = google_folder.folder2.id billing_account = var.billing_account activate_apis = [ diff --git a/test/setup/outputs.tf b/test/setup/outputs.tf index 08753a4b..e6412aca 100644 --- a/test/setup/outputs.tf +++ b/test/setup/outputs.tf @@ -22,3 +22,19 @@ output "sa_key" { value = google_service_account_key.int_test.private_key sensitive = true } + +output "folder1" { + value = element(split("/", google_folder.folder1.id), length(split("/", google_folder.folder1.id)) - 1) +} + +output "folder2" { + value = element(split("/", google_folder.folder2.id), length(split("/", google_folder.folder2.id)) - 1) +} + +output "folder3" { + value = element(split("/", google_folder.folder3.id), length(split("/", google_folder.folder3.id)) - 1) +} + +output "org_id" { + value = var.org_id +} diff --git a/test/setup/variables.tf b/test/setup/variables.tf index 53dd1ed7..117e2e1c 100644 --- a/test/setup/variables.tf +++ b/test/setup/variables.tf @@ -19,6 +19,7 @@ variable "org_id" { variable "folder_id" { description = "The folder to deploy in" + default = null } variable "billing_account" {