367 lines
16 KiB
Markdown
367 lines
16 KiB
Markdown
# Google Cloud Folder Module
|
|
|
|
This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules.
|
|
|
|
<!-- BEGIN TOC -->
|
|
- [Basic example with IAM bindings](#basic-example-with-iam-bindings)
|
|
- [IAM](#iam)
|
|
- [Organization policies](#organization-policies)
|
|
- [Organization Policy Factory](#organization-policy-factory)
|
|
- [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments)
|
|
- [Log Sinks](#log-sinks)
|
|
- [Data Access Logs](#data-access-logs)
|
|
- [Tags](#tags)
|
|
- [Files](#files)
|
|
- [Variables](#variables)
|
|
- [Outputs](#outputs)
|
|
<!-- END TOC -->
|
|
|
|
## Basic example with IAM bindings
|
|
|
|
```hcl
|
|
module "folder" {
|
|
source = "./fabric/modules/folder"
|
|
parent = var.folder_id
|
|
name = "Folder name"
|
|
group_iam = {
|
|
"${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
|
|
|
|
IAM is managed via several variables that implement different features and levels of control:
|
|
|
|
- `iam` and `group_iam` configure authoritative bindings that manage individual roles exclusively, and are internally merged
|
|
- `iam_bindings` configure authoritative bindings with optional support for conditions, and are not internally merged with the previous two variables
|
|
- `iam_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 `groups_iam` variable to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph.
|
|
|
|
Refer to the [project module](../project/README.md#iam) for examples of the IAM interface.
|
|
|
|
## Organization policies
|
|
|
|
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
|
|
|
|
```hcl
|
|
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 Policy Factory
|
|
|
|
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.
|
|
|
|
```hcl
|
|
module "folder" {
|
|
source = "./fabric/modules/folder"
|
|
parent = var.folder_id
|
|
name = "Folder name"
|
|
org_policies_data_path = "configs/org-policies/"
|
|
}
|
|
# tftest modules=1 resources=8 files=boolean,list inventory=org-policies.yaml e2e
|
|
```
|
|
|
|
```yaml
|
|
# 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
|
|
```
|
|
|
|
```yaml
|
|
# 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 Policy Attachments
|
|
|
|
Hierarchical firewall policies can be managed via the [`net-firewall-policy`](../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:
|
|
|
|
```hcl
|
|
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
|
|
```
|
|
## Log Sinks
|
|
|
|
```hcl
|
|
module "gcs" {
|
|
source = "./fabric/modules/gcs"
|
|
project_id = var.project_id
|
|
name = "gcs_sink"
|
|
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 = "bucket"
|
|
}
|
|
|
|
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"
|
|
}
|
|
}
|
|
logging_exclusions = {
|
|
no-gce-instances = "resource.type=gce_instance"
|
|
}
|
|
}
|
|
# tftest modules=5 resources=14 inventory=logging.yaml e2e
|
|
```
|
|
|
|
## Data Access Logs
|
|
|
|
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:
|
|
|
|
```hcl
|
|
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
|
|
```
|
|
|
|
## Tags
|
|
|
|
Refer to the [Creating and managing tags](https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing) documentation for details on usage.
|
|
|
|
```hcl
|
|
module "org" {
|
|
source = "./fabric/modules/organization"
|
|
organization_id = var.organization_id
|
|
tags = {
|
|
environment = {
|
|
description = "Environment specification."
|
|
iam = null
|
|
values = {
|
|
dev = null
|
|
prod = null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
```
|
|
|
|
<!-- TFDOC OPTS files:1 -->
|
|
<!-- BEGIN TFDOC -->
|
|
## Files
|
|
|
|
| name | description | resources |
|
|
|---|---|---|
|
|
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> |
|
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_folder_iam_audit_config</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
|
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> · <code>google_folder</code> |
|
|
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_org_policy_policy</code> |
|
|
| [outputs.tf](./outputs.tf) | Module outputs. | |
|
|
| [tags.tf](./tags.tf) | None | <code>google_tags_tag_binding</code> |
|
|
| [variables.tf](./variables.tf) | Module variables. | |
|
|
| [versions.tf](./versions.tf) | Version pins. | |
|
|
|
|
## Variables
|
|
|
|
| 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. | <code>map(list(string))</code> | | <code>{}</code> |
|
|
| [firewall_policy](variables.tf#L24) | Hierarchical firewall policy to associate to this folder. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
|
| [folder_create](variables.tf#L33) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
|
| [group_iam](variables.tf#L39) | 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. | <code>map(list(string))</code> | | <code>{}</code> |
|
|
| [iam](variables.tf#L46) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
|
| [iam_bindings](variables.tf#L53) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
|
| [iam_bindings_additive](variables.tf#L68) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
|
| [id](variables.tf#L83) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
|
| [logging_data_access](variables.tf#L89) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
|
| [logging_exclusions](variables.tf#L104) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
|
| [logging_sinks](variables.tf#L111) | Logging sinks to create for the folder. | <code title="map(object({ bq_partitioned_table = optional(bool) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = string iam = optional(bool, true) include_children = optional(bool, true) type = string }))">map(object({…}))</code> | | <code>{}</code> |
|
|
| [name](variables.tf#L142) | Folder name. | <code>string</code> | | <code>null</code> |
|
|
| [org_policies](variables.tf#L148) | Organization policies applied to this folder keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
|
| [org_policies_data_path](variables.tf#L175) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
|
| [parent](variables.tf#L181) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
|
| [tag_bindings](variables.tf#L191) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
|
|
|
## Outputs
|
|
|
|
| name | description | sensitive |
|
|
|---|---|:---:|
|
|
| [folder](outputs.tf#L17) | Folder resource. | |
|
|
| [id](outputs.tf#L22) | Fully qualified folder id. | |
|
|
| [name](outputs.tf#L32) | Folder name. | |
|
|
| [sink_writer_identities](outputs.tf#L37) | Writer identities created for each sink. | |
|
|
<!-- END TFDOC -->
|