From eb0ab2c1ec6b6dc523e1d197a261663be8e860e3 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 29 Jan 2022 10:08:17 +0100 Subject: [PATCH] Split organization module in files, add nullables --- modules/organization/README.md | 47 +++++++---- modules/organization/firewall-policy.tf | 2 + modules/organization/iam.tf | 2 + modules/organization/logging.tf | 9 +- modules/organization/main.tf | 89 -------------------- modules/organization/org-policy.tf | 106 ++++++++++++++++++++++++ modules/organization/variables.tf | 16 +++- modules/organization/versions.tf | 2 +- 8 files changed, 161 insertions(+), 112 deletions(-) create mode 100644 modules/organization/org-policy.tf diff --git a/modules/organization/README.md b/modules/organization/README.md index c8ecac9a..4cda102b 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -235,29 +235,44 @@ module "org" { } # tftest modules=1 resources=2 ``` + + +## Files + +| name | description | resources | +|---|---|---| +| [firewall-policy.tf](./firewall-policy.tf) | Hierarchical firewall policies. | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule | +| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_organization_iam_audit_config · google_organization_iam_binding · google_organization_iam_custom_role · google_organization_iam_member · google_organization_iam_policy | +| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_logging_organization_exclusion · google_logging_organization_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member | +| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact | +| [org-policy.tf](./org-policy.tf) | Organization policies. | google_organization_policy | +| [outputs.tf](./outputs.tf) | Module outputs. | | +| [variables.tf](./variables.tf) | Module variables. | | +| [versions.tf](./versions.tf) | Version pins. | | + ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [organization_id](variables.tf#L141) | Organization id in organizations/nnnnnn format. | string | ✓ | | +| [organization_id](variables.tf#L151) | Organization id in organizations/nnnnnn format. | string | ✓ | | | [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES | map(list(string)) | | {} | -| [custom_roles](variables.tf#L23) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| [firewall_policies](variables.tf#L29) | Hierarchical firewall policy rules created in the organization. | map(map(object({…}))) | | {} | -| [firewall_policy_association](variables.tf#L46) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | map(string) | | {} | -| [firewall_policy_factory](variables.tf#L52) | Configuration for the firewall policy factory. | object({…}) | | null | -| [group_iam](variables.tf#L62) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | -| [iam](variables.tf#L68) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive](variables.tf#L74) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive_members](variables.tf#L80) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_audit_config](variables.tf#L86) | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | map(map(list(string))) | | {} | -| [iam_audit_config_authoritative](variables.tf#L97) | IAM Authoritative service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. Audit config should also be authoritative when using authoritative bindings. Use with caution. | map(map(list(string))) | | null | -| [iam_bindings_authoritative](variables.tf#L108) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string)) | | null | -| [logging_exclusions](variables.tf#L114) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L120) | Logging sinks to create for this organization. | map(object({…})) | | {} | -| [policy_boolean](variables.tf#L150) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | -| [policy_list](variables.tf#L156) | 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({…})) | | {} | +| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | +| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | map(map(object({…}))) | | {} | +| [firewall_policy_association](variables.tf#L48) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | map(string) | | {} | +| [firewall_policy_factory](variables.tf#L55) | Configuration for the firewall policy factory. | object({…}) | | null | +| [group_iam](variables.tf#L65) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | +| [iam](variables.tf#L72) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive](variables.tf#L79) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive_members](variables.tf#L86) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | +| [iam_audit_config](variables.tf#L93) | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | map(map(list(string))) | | {} | +| [iam_audit_config_authoritative](variables.tf#L105) | IAM Authoritative service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. Audit config should also be authoritative when using authoritative bindings. Use with caution. | map(map(list(string))) | | null | +| [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string)) | | null | +| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | map(object({…})) | | {} | +| [policy_boolean](variables.tf#L160) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | +| [policy_list](variables.tf#L167) | 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({…})) | | {} | ## Outputs diff --git a/modules/organization/firewall-policy.tf b/modules/organization/firewall-policy.tf index 20dd3f29..864a4477 100644 --- a/modules/organization/firewall-policy.tf +++ b/modules/organization/firewall-policy.tf @@ -14,6 +14,8 @@ * limitations under the License. */ +# tfdoc:file:description Hierarchical firewall policies. + locals { _factory_cidrs = try( yamldecode(file(var.firewall_policy_factory.cidr_file)), {} diff --git a/modules/organization/iam.tf b/modules/organization/iam.tf index bafa5e16..501633ea 100644 --- a/modules/organization/iam.tf +++ b/modules/organization/iam.tf @@ -14,6 +14,8 @@ * limitations under the License. */ +# tfdoc:file:description IAM bindings, roles and audit logging resources. + locals { _group_iam_roles = distinct(flatten(values(var.group_iam))) _group_iam = { diff --git a/modules/organization/logging.tf b/modules/organization/logging.tf index a3b2422b..4f799bf7 100644 --- a/modules/organization/logging.tf +++ b/modules/organization/logging.tf @@ -14,19 +14,20 @@ * limitations under the License. */ +# tfdoc:file:description Log sinks and supporting resources. + locals { - logging_sinks = coalesce(var.logging_sinks, {}) sink_bindings = { for type in ["bigquery", "logging", "pubsub", "storage"] : type => { - for name, sink in local.logging_sinks : + for name, sink in var.logging_sinks : name => sink if sink.type == type } } } resource "google_logging_organization_sink" "sink" { - for_each = local.logging_sinks + for_each = var.logging_sinks name = each.key org_id = local.organization_id_numeric destination = "${each.value.type}.googleapis.com/${each.value.destination}" @@ -87,7 +88,7 @@ resource "google_project_iam_member" "bucket-sinks-binding" { } resource "google_logging_organization_exclusion" "logging-exclusion" { - for_each = coalesce(var.logging_exclusions, {}) + for_each = var.logging_exclusions name = each.key org_id = local.organization_id_numeric description = "${each.key} (Terraform-managed)" diff --git a/modules/organization/main.tf b/modules/organization/main.tf index 60aa65a0..cc757bc6 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -18,95 +18,6 @@ locals { organization_id_numeric = split("/", var.organization_id)[1] } -resource "google_organization_policy" "boolean" { - for_each = var.policy_boolean - org_id = local.organization_id_numeric - constraint = each.key - - dynamic "boolean_policy" { - for_each = each.value == null ? [] : [each.value] - iterator = policy - content { - enforced = policy.value - } - } - - dynamic "restore_policy" { - for_each = each.value == null ? [""] : [] - content { - default = true - } - } - - depends_on = [ - google_organization_iam_audit_config.config, - google_organization_iam_binding.authoritative, - google_organization_iam_custom_role.roles, - google_organization_iam_member.additive, - google_organization_iam_policy.authoritative, - ] -} - -resource "google_organization_policy" "list" { - for_each = var.policy_list - org_id = local.organization_id_numeric - constraint = each.key - - dynamic "list_policy" { - for_each = each.value.status == null ? [] : [each.value] - iterator = policy - content { - inherit_from_parent = policy.value.inherit_from_parent - suggested_value = policy.value.suggested_value - dynamic "allow" { - for_each = policy.value.status ? [""] : [] - content { - values = ( - try(length(policy.value.values) > 0, false) - ? policy.value.values - : null - ) - all = ( - try(length(policy.value.values) > 0, false) - ? null - : true - ) - } - } - dynamic "deny" { - for_each = policy.value.status ? [] : [""] - content { - values = ( - try(length(policy.value.values) > 0, false) - ? policy.value.values - : null - ) - all = ( - try(length(policy.value.values) > 0, false) - ? null - : true - ) - } - } - } - } - - dynamic "restore_policy" { - for_each = each.value.status == null ? [true] : [] - content { - default = true - } - } - - depends_on = [ - google_organization_iam_audit_config.config, - google_organization_iam_binding.authoritative, - google_organization_iam_custom_role.roles, - google_organization_iam_member.additive, - google_organization_iam_policy.authoritative, - ] -} - resource "google_essential_contacts_contact" "contact" { provider = google-beta for_each = var.contacts diff --git a/modules/organization/org-policy.tf b/modules/organization/org-policy.tf new file mode 100644 index 00000000..c0078379 --- /dev/null +++ b/modules/organization/org-policy.tf @@ -0,0 +1,106 @@ +/** + * Copyright 2022 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. + */ + +# tfdoc:file:description Organization policies. + +resource "google_organization_policy" "boolean" { + for_each = var.policy_boolean + org_id = local.organization_id_numeric + constraint = each.key + + dynamic "boolean_policy" { + for_each = each.value == null ? [] : [each.value] + iterator = policy + content { + enforced = policy.value + } + } + + dynamic "restore_policy" { + for_each = each.value == null ? [""] : [] + content { + default = true + } + } + + depends_on = [ + google_organization_iam_audit_config.config, + google_organization_iam_binding.authoritative, + google_organization_iam_custom_role.roles, + google_organization_iam_member.additive, + google_organization_iam_policy.authoritative, + ] +} + +resource "google_organization_policy" "list" { + for_each = var.policy_list + org_id = local.organization_id_numeric + constraint = each.key + + dynamic "list_policy" { + for_each = each.value.status == null ? [] : [each.value] + iterator = policy + content { + inherit_from_parent = policy.value.inherit_from_parent + suggested_value = policy.value.suggested_value + dynamic "allow" { + for_each = policy.value.status ? [""] : [] + content { + values = ( + try(length(policy.value.values) > 0, false) + ? policy.value.values + : null + ) + all = ( + try(length(policy.value.values) > 0, false) + ? null + : true + ) + } + } + dynamic "deny" { + for_each = policy.value.status ? [] : [""] + content { + values = ( + try(length(policy.value.values) > 0, false) + ? policy.value.values + : null + ) + all = ( + try(length(policy.value.values) > 0, false) + ? null + : true + ) + } + } + } + } + + dynamic "restore_policy" { + for_each = each.value.status == null ? [true] : [] + content { + default = true + } + } + + depends_on = [ + google_organization_iam_audit_config.config, + google_organization_iam_binding.authoritative, + google_organization_iam_custom_role.roles, + google_organization_iam_member.additive, + google_organization_iam_policy.authoritative, + ] +} diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index f5a9157a..0e12c5d2 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -18,12 +18,14 @@ variable "contacts" { description = "List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES" type = map(list(string)) default = {} + nullable = false } variable "custom_roles" { description = "Map of role name => list of permissions to create in this project." type = map(list(string)) default = {} + nullable = false } variable "firewall_policies" { @@ -47,6 +49,7 @@ variable "firewall_policy_association" { description = "The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else." type = map(string) default = {} + nullable = false } variable "firewall_policy_factory" { @@ -63,30 +66,35 @@ variable "group_iam" { description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable." type = map(list(string)) default = {} + nullable = false } variable "iam" { description = "IAM bindings, in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} + nullable = false } variable "iam_additive" { description = "Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} + nullable = false } variable "iam_additive_members" { description = "IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values." type = map(list(string)) default = {} + nullable = false } variable "iam_audit_config" { description = "Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service." type = map(map(list(string))) default = {} + nullable = false # default = { # allServices = { # DATA_READ = ["user:me@example.org"] @@ -115,6 +123,7 @@ variable "logging_exclusions" { description = "Logging exclusions for this organization in the form {NAME -> FILTER}." type = map(string) default = {} + nullable = false } variable "logging_sinks" { @@ -135,7 +144,8 @@ variable "logging_sinks" { ]) error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'." } - default = {} + default = {} + nullable = false } variable "organization_id" { @@ -151,6 +161,7 @@ variable "policy_boolean" { description = "Map of boolean org policies and enforcement value, set value to null for policy restore." type = map(bool) default = {} + nullable = false } variable "policy_list" { @@ -161,5 +172,6 @@ variable "policy_list" { status = bool values = list(string) })) - default = {} + default = {} + nullable = false } diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf index 29041268..e72a7800 100644 --- a/modules/organization/versions.tf +++ b/modules/organization/versions.tf @@ -13,7 +13,7 @@ # limitations under the License. terraform { - required_version = ">= 1.0.0" + required_version = ">= 1.1.0" required_providers { google = { source = "hashicorp/google"