diff --git a/modules/folder/README.md b/modules/folder/README.md
index e9c92f29..76bc2a2a 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -41,6 +41,48 @@ module "folder" {
# tftest:modules=1:resources=4
```
+### Hierarchical firewall policies
+
+```hcl
+module "folder1" {
+ source = "./modules/folder"
+ parent = var.organization_id
+ name = "policy-container"
+
+ firewall_policies = {
+ iap-policy = {
+ allow-iap-ssh = {
+ description = "Always allow ssh from IAP"
+ direction = "INGRESS"
+ action = "allow"
+ priority = 100
+ ranges = ["35.235.240.0/20"]
+ ports = {
+ tcp = ["22"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ }
+ }
+ firewall_policy_attachments = {
+ iap-policy = module.folder1.firewall_policy_id["iap-policy"]
+ }
+}
+
+module "folder2" {
+ source = "./modules/folder"
+ parent = var.organization_id
+ name = "hf2"
+ firewall_policy_attachments = {
+ iap-policy = module.folder1.firewall_policy_id["iap-policy"]
+ }
+}
+# tftest:modules=2:resources=6
+```
+
+
## Variables
@@ -48,6 +90,8 @@ module "folder" {
|---|---|:---: |:---:|:---:|
| name | Folder name. | string
| ✓ | |
| parent | Parent in folders/folder_id or organizations/org_id format. | string
| ✓ | |
+| *firewall_policies* | Hierarchical firewall policies to *create* in this folder. | map(map(object({...})))
| | {}
|
+| *firewall_policy_attachments* | List of hierarchical firewall policy IDs to *attach* to this folder. | set(string)
| | []
|
| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(set(string))
| | {}
|
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool)
| | {}
|
| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...}))
| | {}
|
@@ -56,6 +100,8 @@ module "folder" {
| name | description | sensitive |
|---|---|:---:|
+| firewall_policies | Map of firewall policy resources created in this folder. | |
+| firewall_policy_id | Map of firewall policy ids created in this folder. | |
| folder | Folder resource. | |
| id | Folder id. | |
| name | Folder name. | |
diff --git a/modules/folder/main.tf b/modules/folder/main.tf
index fb018b7a..6fb50acb 100644
--- a/modules/folder/main.tf
+++ b/modules/folder/main.tf
@@ -14,6 +14,18 @@
* limitations under the License.
*/
+locals {
+ extended_rules = flatten([
+ for policy, rules in var.firewall_policies : [
+ for rule_name, rule in rules :
+ merge(rule, { policy = policy, name = rule_name })
+ ]
+ ])
+ rules_map = {
+ for rule in local.extended_rules :
+ "${rule.policy}-${rule.name}" => rule
+ }
+}
resource "google_folder" "folder" {
display_name = var.name
@@ -99,3 +111,48 @@ resource "google_folder_organization_policy" "list" {
}
}
}
+
+resource "google_compute_organization_security_policy" "policy" {
+ provider = google-beta
+ for_each = var.firewall_policies
+
+ display_name = each.key
+ parent = google_folder.folder.id
+}
+
+resource "google_compute_organization_security_policy_rule" "rule" {
+ provider = google-beta
+ for_each = local.rules_map
+
+ policy_id = google_compute_organization_security_policy.policy[each.value.policy].id
+ action = each.value.action
+ direction = each.value.direction
+ priority = each.value.priority
+ target_resources = each.value.target_resources
+ target_service_accounts = each.value.target_service_accounts
+ enable_logging = each.value.logging
+ # preview = each.value.preview
+ match {
+ description = each.value.description
+ config {
+ src_ip_ranges = each.value.direction == "INGRESS" ? each.value.ranges : null
+ dest_ip_ranges = each.value.direction == "EGRESS" ? each.value.ranges : null
+ dynamic "layer4_config" {
+ for_each = each.value.ports
+ iterator = port
+ content {
+ ip_protocol = port.key
+ ports = port.value
+ }
+ }
+ }
+ }
+}
+
+resource "google_compute_organization_security_policy_association" "attachment" {
+ provider = google-beta
+ for_each = var.firewall_policy_attachments
+ name = "${google_folder.folder.id}-${each.key}"
+ attachment_id = google_folder.folder.id
+ policy_id = each.value
+}
diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf
index 4be12eb1..c521367f 100644
--- a/modules/folder/outputs.tf
+++ b/modules/folder/outputs.tf
@@ -33,3 +33,19 @@ output "name" {
description = "Folder name."
value = google_folder.folder.display_name
}
+
+output "firewall_policies" {
+ description = "Map of firewall policy resources created in this folder."
+ value = {
+ for name, _ in var.firewall_policies :
+ name => google_compute_organization_security_policy.policy[name]
+ }
+}
+
+output "firewall_policy_id" {
+ description = "Map of firewall policy ids created in this folder."
+ value = {
+ for name, _ in var.firewall_policies :
+ name => google_compute_organization_security_policy.policy[name].id
+ }
+}
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 1231be0d..24c0c171 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -50,3 +50,27 @@ variable "policy_list" {
}))
default = {}
}
+
+variable "firewall_policies" {
+ description = "Hierarchical firewall policies to *create* in this folder."
+ type = map(map(object({
+ description = string
+ direction = string
+ action = string
+ priority = number
+ ranges = list(string)
+ ports = map(list(string))
+ target_service_accounts = list(string)
+ target_resources = list(string)
+ logging = bool
+ #preview = bool
+ })))
+ default = {}
+}
+
+variable "firewall_policy_attachments" {
+ description = "List of hierarchical firewall policy IDs to *attach* to this folder."
+ # set to avoid manual casting with toset()
+ type = map(string)
+ default = {}
+}
diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf
index 69b3cdec..962a2fad 100644
--- a/tests/examples/variables.tf
+++ b/tests/examples/variables.tf
@@ -15,7 +15,7 @@
# common variables used for examples
variable "organization_id" {
- default = "organization/organization"
+ default = "organizations/1122334455"
}
variable "project_id" {