diff --git a/modules/folder/README.md b/modules/folder/README.md index 219df4c1..a15d4b20 100644 --- a/modules/folder/README.md +++ b/modules/folder/README.md @@ -5,6 +5,7 @@ This module allows the creation and management of folders, including support for - [Basic example with IAM bindings](#basic-example-with-iam-bindings) - [IAM](#iam) +- [Assured Workload Folder](#assured-workload-folder) - [Organization policies](#organization-policies) - [Organization Policy Factory](#organization-policy-factory) - [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments) @@ -55,6 +56,37 @@ The authoritative and additive approaches can be used together, provided differe Refer to the [project module](../project/README.md#iam) for examples of the IAM interface. +## Assured Workload Folder + +To create [Assured Workload](https://cloud.google.com/security/products/assured-workloads) folder instead of regular folder. +Note that an existing folder can not be converted to an Assured Workload folder, hence `assured_workload_config` is mutually exclusive with `folder_create=false`. + +```hcl +module "folder" { + source = "./fabric/modules/folder" + parent = var.folder_id + name = "Folder name" + + assured_workload_config = { + compliance_regime = "EU_REGIONS_AND_SUPPORT" + display_name = "workload-name" + location = "europe-west1" + organization = var.organization_id + enable_sovereign_controls = true + } + iam = { + "roles/owner" = ["serviceAccount:${var.service_account.email}"] + } + iam_bindings_additive = { + am1-storage-admin = { + member = "serviceAccount:${var.service_account.email}" + role = "roles/storage.admin" + } + } +} +# tftest modules=1 resources=3 inventory=assured-workload.yaml +``` + ## Organization policies To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project. @@ -347,7 +379,7 @@ module "folder" { |---|---|---| | [iam.tf](./iam.tf) | IAM bindings. | google_folder_iam_binding · google_folder_iam_member | | [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_folder_iam_audit_config · google_logging_folder_exclusion · google_logging_folder_settings · google_logging_folder_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_compute_firewall_policy_association · google_essential_contacts_contact · google_folder | +| [main.tf](./main.tf) | Module-level locals and resources. | google_assured_workloads_workload · google_compute_firewall_policy_association · google_essential_contacts_contact · google_folder | | [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | | [tags.tf](./tags.tf) | None | google_tags_tag_binding | @@ -360,30 +392,32 @@ module "folder" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [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)) | | {} | -| [factories_config](variables.tf#L24) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [firewall_policy](variables.tf#L33) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null | -| [folder_create](variables.tf#L42) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | +| [assured_workload_config](variables.tf#L17) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | object({…}) | | null | +| [contacts](variables.tf#L70) | 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)) | | {} | +| [factories_config](variables.tf#L77) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [firewall_policy](variables.tf#L86) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null | +| [folder_create](variables.tf#L95) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | | [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | -| [id](variables.tf#L48) | Folder ID in case you use folder_create=false. | string | | null | +| [id](variables.tf#L101) | Folder ID in case you use folder_create=false. | string | | null | | [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | | [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | | [logging_settings](variables-logging.tf#L39) | Default settings for logging resources. | object({…}) | | null | | [logging_sinks](variables-logging.tf#L49) | Logging sinks to create for the folder. | map(object({…})) | | {} | -| [name](variables.tf#L54) | Folder name. | string | | null | -| [org_policies](variables.tf#L60) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | -| [parent](variables.tf#L87) | Parent in folders/folder_id or organizations/org_id format. | string | | null | -| [tag_bindings](variables.tf#L97) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | +| [name](variables.tf#L107) | Folder name. | string | | null | +| [org_policies](variables.tf#L113) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | +| [parent](variables.tf#L140) | Parent in folders/folder_id or organizations/org_id format. | string | | null | +| [tag_bindings](variables.tf#L150) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [folder](outputs.tf#L17) | Folder resource. | | -| [id](outputs.tf#L22) | Fully qualified folder id. | | -| [name](outputs.tf#L33) | Folder name. | | -| [sink_writer_identities](outputs.tf#L38) | Writer identities created for each sink. | | +| [assured_workload](outputs.tf#L17) | Assured Workloads workload resource. | | +| [folder](outputs.tf#L22) | Folder resource. | | +| [id](outputs.tf#L27) | Fully qualified folder id. | | +| [name](outputs.tf#L38) | Folder name. | | +| [sink_writer_identities](outputs.tf#L47) | Writer identities created for each sink. | | diff --git a/modules/folder/main.tf b/modules/folder/main.tf index 8bb46a53..4f838b01 100644 --- a/modules/folder/main.tf +++ b/modules/folder/main.tf @@ -16,14 +16,28 @@ locals { folder_id = ( - var.folder_create - ? try(google_folder.folder[0].id, null) - : var.id + var.assured_workload_config == null + ? ( + var.folder_create + ? try(google_folder.folder[0].id, null) + : var.id + ) + : format("folders/%s", try(google_assured_workloads_workload.folder[0].resources[0].resource_id, "")) + ) + aw_parent = ( + # Assured Workload only accepls folder as a parent and uses organization as a parent when no value provided. + var.parent == null + ? null + : ( + try(startswith(var.parent, "folders/")) + ? var.parent + : null + ) ) } resource "google_folder" "folder" { - count = var.folder_create ? 1 : 0 + count = var.folder_create && var.assured_workload_config == null ? 1 : 0 display_name = var.name parent = var.parent } @@ -48,3 +62,30 @@ resource "google_compute_firewall_policy_association" "default" { name = var.firewall_policy.name firewall_policy = var.firewall_policy.policy } + +resource "google_assured_workloads_workload" "folder" { + count = (var.assured_workload_config != null && var.folder_create) ? 1 : 0 + compliance_regime = var.assured_workload_config.compliance_regime + display_name = var.assured_workload_config.display_name + location = var.assured_workload_config.location + organization = var.assured_workload_config.organization + enable_sovereign_controls = var.assured_workload_config.enable_sovereign_controls + labels = var.assured_workload_config.labels + partner = var.assured_workload_config.partner + dynamic "partner_permissions" { + for_each = try(var.assured_workload_config.partner_permissions, null) == null ? [] : [""] + content { + assured_workloads_monitoring = var.assured_workload_config.partner_permissions.assured_workloads_monitoring + data_logs_viewer = var.assured_workload_config.partner_permissions.data_logs_viewer + service_access_approver = var.assured_workload_config.partner_permissions.service_access_approver + } + } + + provisioned_resources_parent = local.aw_parent + + resource_settings { + display_name = var.name + resource_type = "CONSUMER_FOLDER" + } + violation_notifications_enabled = var.assured_workload_config.violation_notifications_enabled +} diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf index 79ff37cb..f9acdad4 100644 --- a/modules/folder/outputs.tf +++ b/modules/folder/outputs.tf @@ -14,6 +14,11 @@ * limitations under the License. */ +output "assured_workload" { + description = "Assured Workloads workload resource." + value = try(google_assured_workloads_workload.folder[0], null) +} + output "folder" { description = "Folder resource." value = try(google_folder.folder[0], null) @@ -32,7 +37,11 @@ output "id" { output "name" { description = "Folder name." - value = try(google_folder.folder[0].display_name, null) + value = ( + var.assured_workload_config == null + ? try(google_folder.folder[0].display_name, null) + : try(google_assured_workloads_workload.folder[0].resource_settings[0].display_name, null) + ) } output "sink_writer_identities" { diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf index e5f6d548..1c899ab0 100644 --- a/modules/folder/variables.tf +++ b/modules/folder/variables.tf @@ -14,6 +14,59 @@ * limitations under the License. */ +variable "assured_workload_config" { + description = "Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false." + type = object({ + compliance_regime = string + display_name = string + location = string + organization = string + enable_sovereign_controls = optional(bool) + labels = optional(map(string), {}) + partner = optional(string) + partner_permissions = optional(object({ + assured_workloads_monitoring = optional(bool) + data_logs_viewer = optional(bool) + service_access_approver = optional(bool) + })) + violation_notifications_enabled = optional(bool) + + }) + default = null + validation { + condition = try(contains([ + "ASSURED_WORKLOADS_FOR_PARTNERS", + "AU_REGIONS_AND_US_SUPPORT", + "CA_PROTECTED_B, IL5", + "CA_REGIONS_AND_SUPPORT", + "CJIS", + "COMPLIANCE_REGIME_UNSPECIFIED", + "EU_REGIONS_AND_SUPPORT", + "FEDRAMP_HIGH", + "FEDRAMP_MODERATE", + "HIPAA, HITRUST", + "IL2", + "IL4", + "ISR_REGIONS_AND_SUPPORT", + "ISR_REGIONS", + "ITAR", + "JP_REGIONS_AND_SUPPORT", + "US_REGIONAL_ACCESS" + ], var.assured_workload_config.compliance_regime), true) + error_message = "Field assured_workload_config.compliance_regime must be one of the values listed in https://cloud.google.com/assured-workloads/docs/reference/rest/Shared.Types/ComplianceRegime" + } + validation { + condition = try(contains([ + "LOCAL_CONTROLS_BY_S3NS", + "PARTNER_UNSPECIFIED", + "SOVEREIGN_CONTROLS_BY_PSN", + "SOVEREIGN_CONTROLS_BY_SIA_MINSAIT", + "SOVEREIGN_CONTROLS_BY_T_SYSTEMS" + ], var.assured_workload_config.partner), true) + error_message = "Field assured_workload_config.partner must be one of the values listed in https://cloud.google.com/assured-workloads/docs/reference/rest/Shared.Types/Partner" + } +} + 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)) @@ -99,3 +152,4 @@ variable "tag_bindings" { type = map(string) default = null } + diff --git a/tests/examples_e2e/setup_module/main.tf b/tests/examples_e2e/setup_module/main.tf index d459013d..f27b90e5 100644 --- a/tests/examples_e2e/setup_module/main.tf +++ b/tests/examples_e2e/setup_module/main.tf @@ -22,6 +22,7 @@ locals { services = [ # trimmed down list of services, to be extended as needed "alloydb.googleapis.com", + "assuredworkloads.googleapis.com", "apigee.googleapis.com", "bigquery.googleapis.com", "cloudbuild.googleapis.com", diff --git a/tests/modules/folder/examples/assured-workload.yaml b/tests/modules/folder/examples/assured-workload.yaml new file mode 100644 index 00000000..dfcba85a --- /dev/null +++ b/tests/modules/folder/examples/assured-workload.yaml @@ -0,0 +1,38 @@ +# 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. + +values: + module.folder.google_assured_workloads_workload.folder[0]: + compliance_regime: EU_REGIONS_AND_SUPPORT + display_name: workload-name + organization: organizations/1122334455 + location: europe-west1 + enable_sovereign_controls: true + module.folder.google_folder_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - serviceAccount:service_account_email + role: roles/owner + module.folder.google_folder_iam_member.bindings["am1-storage-admin"]: + condition: [] + member: serviceAccount:service_account_email + role: roles/storage.admin +counts: + google_assured_workloads_workload: 1 + google_folder_iam_binding: 1 + google_folder_iam_member: 1 + modules: 1 + resources: 3 + +outputs: {}