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" {