This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules.
- Basic example with IAM bindings
- IAM
- Assured Workload Folder
- Organization policies
- Hierarchical Firewall Policy Attachments
- Log Sinks
- Data Access Logs
- Tags
- Files
- Variables
- Outputs
module "folder" {
source = "./fabric/modules/folder"
parent = var.folder_id
name = "Folder name"
iam_by_principals = {
"group:${var.group_email}" = [
"roles/owner",
"roles/resourcemanager.folderAdmin",
"roles/resourcemanager.projectCreator"
]
}
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=5 inventory=iam.yaml e2e
IAM is managed via several variables that implement different features and levels of control:
iam
andiam_by_principals
configure authoritative bindings that manage individual roles exclusively, and are internally mergediam_bindings
configure authoritative bindings with optional support for conditions, and are not internally merged with the previous two variablesiam_bindings_additive
configure additive bindings via individual role/member pairs with optional support conditions
The authoritative and additive approaches can be used together, provided different roles are managed by each. Some care must also be taken with the iam_by_principals
variable to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph.
Refer to the project module for examples of the IAM interface.
To create Assured Workload 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
.
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
To manage organization policies, the orgpolicy.googleapis.com
service should be enabled in the quota project.
module "folder" {
source = "./fabric/modules/folder"
parent = var.folder_id
name = "Folder name"
org_policies = {
"compute.disableGuestAttributesAccess" = {
rules = [{ enforce = true }]
}
"compute.skipDefaultNetworkCreation" = {
rules = [{ enforce = true }]
}
"iam.disableServiceAccountKeyCreation" = {
rules = [{ enforce = true }]
}
"iam.disableServiceAccountKeyUpload" = {
rules = [
{
condition = {
expression = "resource.matchTagId('tagKeys/1234', 'tagValues/1234')"
title = "condition"
description = "test condition"
location = "somewhere"
}
enforce = true
},
{
enforce = false
}
]
}
"iam.allowedPolicyMemberDomains" = {
rules = [{
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}]
}
"compute.trustedImageProjects" = {
rules = [{
allow = {
values = ["projects/my-project"]
}
}]
}
"compute.vmExternalIpAccess" = {
rules = [{ deny = { all = true } }]
}
}
}
# tftest modules=1 resources=8 inventory=org-policies.yaml e2e
Organization policies can be loaded from a directory containing YAML files where each file defines one or more constraints. The structure of the YAML files is exactly the same as the org_policies variable.
Note that constraints defined via org_policies take precedence over those in org_policies_data_path. In other words, if you specify the same constraint in a YAML file and in the org_policies variable, the latter will take priority.
The example below deploys a few organization policies split between two YAML files.
module "folder" {
source = "./fabric/modules/folder"
parent = var.folder_id
name = "Folder name"
factories_config = {
org_policies = "configs/org-policies/"
}
}
# tftest modules=1 resources=8 files=boolean,list inventory=org-policies.yaml e2e
# tftest-file id=boolean path=configs/org-policies/boolean.yaml
compute.disableGuestAttributesAccess:
rules:
- enforce: true
compute.skipDefaultNetworkCreation:
rules:
- enforce: true
iam.disableServiceAccountKeyCreation:
rules:
- enforce: true
iam.disableServiceAccountKeyUpload:
rules:
- condition:
description: test condition
expression: resource.matchTagId('tagKeys/1234', 'tagValues/1234')
location: somewhere
title: condition
enforce: true
- enforce: false
# tftest-file id=list path=configs/org-policies/list.yaml
compute.trustedImageProjects:
rules:
- allow:
values:
- projects/my-project
compute.vmExternalIpAccess:
rules:
- deny:
all: true
iam.allowedPolicyMemberDomains:
rules:
- allow:
values:
- C0xxxxxxx
- C0yyyyyyy
Hierarchical firewall policies can be managed via the net-firewall-policy
module, including support for factories. Once a policy is available, attaching it to the organization can be done either in the firewall policy module itself, or here:
module "firewall-policy" {
source = "./fabric/modules/net-firewall-policy"
name = "test-1"
parent_id = module.folder.id
# attachment via the firewall policy module
# attachments = {
# folder-1 = module.folder.id
# }
}
module "folder" {
source = "./fabric/modules/folder"
parent = var.folder_id
name = "Folder name"
# attachment via the organization module
firewall_policy = {
name = "test-1"
policy = module.firewall-policy.id
}
}
# tftest modules=2 resources=3 e2e serial
module "gcs" {
source = "./fabric/modules/gcs"
project_id = var.project_id
prefix = var.prefix
name = "gcs_sink"
location = "EU"
force_destroy = true
}
module "dataset" {
source = "./fabric/modules/bigquery-dataset"
project_id = var.project_id
id = "bq_sink"
}
module "pubsub" {
source = "./fabric/modules/pubsub"
project_id = var.project_id
name = "pubsub_sink"
}
module "bucket" {
source = "./fabric/modules/logging-bucket"
parent_type = "project"
parent = var.project_id
id = "${var.prefix}-bucket"
}
module "destination-project" {
source = "./fabric/modules/project"
name = "dest-prj"
billing_account = var.billing_account_id
parent = var.folder_id
prefix = var.prefix
services = [
"logging.googleapis.com"
]
}
module "folder-sink" {
source = "./fabric/modules/folder"
name = "Folder name"
parent = var.folder_id
logging_sinks = {
warnings = {
destination = module.gcs.id
filter = "severity=WARNING"
type = "storage"
}
info = {
destination = module.dataset.id
filter = "severity=INFO"
type = "bigquery"
}
notice = {
destination = module.pubsub.id
filter = "severity=NOTICE"
type = "pubsub"
}
debug = {
destination = module.bucket.id
filter = "severity=DEBUG"
exclusions = {
no-compute = "logName:compute"
}
type = "logging"
}
alert = {
destination = module.destination-project.id
filter = "severity=ALERT"
type = "project"
}
}
logging_exclusions = {
no-gce-instances = "resource.type=gce_instance"
}
}
# tftest modules=6 resources=18 inventory=logging.yaml e2e
Activation of data access logs can be controlled via the logging_data_access
variable. If the iam_bindings_authoritative
variable is used to set a resource-level IAM policy, the data access log configuration will also be authoritative as part of the policy.
This example shows how to set a non-authoritative access log configuration:
module "folder" {
source = "./fabric/modules/folder"
parent = var.folder_id
name = "Folder name"
logging_data_access = {
allServices = {
# logs for principals listed here will be excluded
ADMIN_READ = ["group:${var.group_email}"]
}
"storage.googleapis.com" = {
DATA_READ = []
DATA_WRITE = []
}
}
}
# tftest modules=1 resources=3 inventory=logging-data-access.yaml e2e
Refer to the Creating and managing tags documentation for details on usage.
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
tags = {
environment = {
description = "Environment specification."
values = {
dev = {}
prod = {}
}
}
}
}
module "folder" {
source = "./fabric/modules/folder"
name = "Folder name"
parent = var.folder_id
tag_bindings = {
env-prod = module.org.tag_values["environment/prod"].id
}
}
# tftest modules=2 resources=5 inventory=tags.yaml e2e serial
name | description | resources |
---|---|---|
iam.tf | IAM bindings. | google_folder_iam_binding · google_folder_iam_member |
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 | Module-level locals and resources. | google_assured_workloads_workload · google_compute_firewall_policy_association · google_essential_contacts_contact · google_folder |
organization-policies.tf | Folder-level organization policies. | google_org_policy_policy |
outputs.tf | Module outputs. | |
tags.tf | None | google_tags_tag_binding |
variables-iam.tf | None | |
variables-logging.tf | None | |
variables.tf | Module variables. | |
versions.tf | Version pins. |
name | description | type | required | default |
---|---|---|---|---|
assured_workload_config | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | object({…}) |
null |
|
contacts | 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 | Paths to data files and folders that enable factory functionality. | object({…}) |
{} |
|
firewall_policy | Hierarchical firewall policy to associate to this folder. | object({…}) |
null |
|
folder_create | Create folder. When set to false, uses id to reference an existing folder. | bool |
true |
|
iam | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) |
{} |
|
iam_bindings | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) |
{} |
|
iam_bindings_additive | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) |
{} |
|
iam_by_principals | 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 | Folder ID in case you use folder_create=false. | string |
null |
|
logging_data_access | 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 | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) |
{} |
|
logging_settings | Default settings for logging resources. | object({…}) |
null |
|
logging_sinks | Logging sinks to create for the folder. | map(object({…})) |
{} |
|
name | Folder name. | string |
null |
|
org_policies | Organization policies applied to this folder keyed by policy name. | map(object({…})) |
{} |
|
parent | Parent in folders/folder_id or organizations/org_id format. | string |
null |
|
tag_bindings | Tag bindings for this folder, in key => tag value id format. | map(string) |
null |
name | description | sensitive |
---|---|---|
assured_workload | Assured Workloads workload resource. | |
folder | Folder resource. | |
id | Fully qualified folder id. | |
name | Folder name. | |
sink_writer_identities | Writer identities created for each sink. |