Custom role factories for organization and project modules (#1912)
* backport custom role factories * backport from fast ci/cd branch * indent * tfdoc * fix module tests
This commit is contained in:
parent
886734e1e9
commit
bba814c091
|
@ -71,13 +71,15 @@ locals {
|
|||
}
|
||||
|
||||
module "folder" {
|
||||
source = "../../../modules/folder"
|
||||
folder_create = var.folder_config.folder_create != null
|
||||
parent = try(var.folder_config.folder_create.parent, null)
|
||||
name = try(var.folder_config.folder_create.display_name, null)
|
||||
id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id
|
||||
group_iam = local.group_iam
|
||||
org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null
|
||||
source = "../../../modules/folder"
|
||||
folder_create = var.folder_config.folder_create != null
|
||||
parent = try(var.folder_config.folder_create.parent, null)
|
||||
name = try(var.folder_config.folder_create.display_name, null)
|
||||
id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id
|
||||
group_iam = local.group_iam
|
||||
factories_config = {
|
||||
org_policies = var.data_dir != null ? "${var.data_dir}/org-policies" : null
|
||||
}
|
||||
logging_sinks = var.enable_features.log_sink ? {
|
||||
for name, attrs in var.log_sinks : name => {
|
||||
bq_partitioned_table = attrs.type == "bigquery"
|
||||
|
|
|
@ -155,21 +155,21 @@ Once the configuration is done just go through the usual `init/apply` cycle. On
|
|||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pools = list(string) federated_identity_providers = map(object({ audiences = list(string) issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) service_accounts = object({ networking = string resman = string security = string dp-dev = optional(string) dp-prod = optional(string) gke-dev = optional(string) gke-prod = optional(string) pf-dev = optional(string) pf-prod = optional(string) sandbox = optional(string) teams = optional(string) }) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [billing_account](variables.tf#L52) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [organization](variables.tf#L205) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [prefix](variables.tf#L227) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [root_node](variables.tf#L237) | Root folder node for the tenant, in folders/nnnnnn format. | <code>string</code> | ✓ | | |
|
||||
| [short_name](variables.tf#L242) | Short name used to identify the tenant. | <code>string</code> | ✓ | | |
|
||||
| [tags](variables.tf#L247) | Resource management tags. | <code title="object({ keys = object({ context = string environment = string tenant = string }) names = object({ context = string environment = string tenant = string }) values = map(string) })">object({…})</code> | ✓ | | |
|
||||
| [organization](variables.tf#L214) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [prefix](variables.tf#L230) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [root_node](variables.tf#L240) | Root folder node for the tenant, in folders/nnnnnn format. | <code>string</code> | ✓ | | |
|
||||
| [short_name](variables.tf#L245) | Short name used to identify the tenant. | <code>string</code> | ✓ | | |
|
||||
| [tags](variables.tf#L250) | Resource management tags. | <code title="object({ keys = object({ context = string environment = string tenant = string }) names = object({ context = string environment = string tenant = string }) values = map(string) })">object({…})</code> | ✓ | | |
|
||||
| [cicd_repositories](variables.tf#L63) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object({ data_platform_dev = object({ branch = string identity_provider = string name = string type = string }) data_platform_prod = object({ branch = string identity_provider = string name = string type = string }) gke_dev = object({ branch = string identity_provider = string name = string type = string }) gke_prod = object({ branch = string identity_provider = string name = string type = string }) networking = object({ branch = string identity_provider = string name = string type = string }) project_factory_dev = object({ branch = string identity_provider = string name = string type = string }) project_factory_prod = object({ branch = string identity_provider = string name = string type = string }) security = object({ branch = string identity_provider = string name = string type = string }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_roles](variables.tf#L145) | Custom roles defined at the org level, in key => id format. | <code title="object({ service_project_network_admin = string })">object({…})</code> | | <code>null</code> | <code>0-bootstrap</code> |
|
||||
| [data_dir](variables.tf#L154) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>"data"</code> | |
|
||||
| [fast_features](variables.tf#L160) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | <code>0-0-bootstrap</code> |
|
||||
| [groups](variables.tf#L174) | Group names to grant organization-level permissions. | <code title="object({ gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [locations](variables.tf#L187) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | <code>0-bootstrap</code> |
|
||||
| [organization_policy_data_path](variables.tf#L215) | Path for the data folder used by the organization policies factory. | <code>string</code> | | <code>null</code> | |
|
||||
| [outputs_location](variables.tf#L221) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [team_folders](variables.tf#L265) | Team folders to be created. Format is described in a code comment. | <code title="map(object({ descriptive_name = string group_iam = map(list(string)) impersonation_groups = list(string) }))">map(object({…}))</code> | | <code>null</code> | |
|
||||
| [test_skip_data_sources](variables.tf#L275) | Used when testing to bypass data sources. | <code>bool</code> | | <code>false</code> | |
|
||||
| [factories_config](variables.tf#L160) | Configuration for the organization policies factory. | <code title="object({ org_policy = optional(string, "data/org-policies") })">object({…})</code> | | <code>{}</code> | |
|
||||
| [fast_features](variables.tf#L169) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | <code>0-0-bootstrap</code> |
|
||||
| [groups](variables.tf#L183) | Group names to grant organization-level permissions. | <code title="object({ gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [locations](variables.tf#L196) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | <code>0-bootstrap</code> |
|
||||
| [outputs_location](variables.tf#L224) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [team_folders](variables.tf#L268) | Team folders to be created. Format is described in a code comment. | <code title="map(object({ descriptive_name = string group_iam = map(list(string)) impersonation_groups = list(string) }))">map(object({…}))</code> | | <code>null</code> | |
|
||||
| [test_skip_data_sources](variables.tf#L278) | Used when testing to bypass data sources. | <code>bool</code> | | <code>false</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ module "root-folder" {
|
|||
)
|
||||
name = var.test_skip_data_sources ? "Test" : null
|
||||
# end test attributes
|
||||
factories_config = {
|
||||
org_policy = var.factories_config.org_policy
|
||||
}
|
||||
iam_bindings_additive = {
|
||||
sa_net_fw_policy_admin = {
|
||||
member = local.automation_sas_iam.networking
|
||||
|
@ -40,5 +43,4 @@ module "root-folder" {
|
|||
role = "roles/accesscontextmanager.policyAdmin"
|
||||
}
|
||||
}
|
||||
org_policies_data_path = var.organization_policy_data_path
|
||||
}
|
||||
|
|
|
@ -157,6 +157,15 @@ variable "data_dir" {
|
|||
default = "data"
|
||||
}
|
||||
|
||||
variable "factories_config" {
|
||||
description = "Configuration for the organization policies factory."
|
||||
type = object({
|
||||
org_policy = optional(string, "data/org-policies")
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "fast_features" {
|
||||
# tfdoc:variable:source 0-0-bootstrap
|
||||
description = "Selective control for top-level FAST features."
|
||||
|
@ -212,12 +221,6 @@ variable "organization" {
|
|||
})
|
||||
}
|
||||
|
||||
variable "organization_policy_data_path" {
|
||||
description = "Path for the data folder used by the organization policies factory."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "outputs_location" {
|
||||
description = "Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable."
|
||||
type = string
|
||||
|
|
|
@ -594,37 +594,36 @@ The `fast_features` variable consists of 4 toggles:
|
|||
| name | description | type | required | default | producer |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | |
|
||||
| [organization](variables.tf#L248) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L263) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
||||
| [organization](variables.tf#L235) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L250) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
||||
| [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | |
|
||||
| [cicd_repositories](variables.tf#L33) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object({ bootstrap = optional(object({ branch = string identity_provider = string name = string type = string })) resman = optional(object({ branch = string identity_provider = string name = string type = string })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | <code title="object({ organization_iam_admin = string service_project_network_admin = string tenant_network_admin = string })">object({…})</code> | | <code title="{ organization_iam_admin = "organizationIamAdmin" service_project_network_admin = "serviceProjectNetworkAdmin" tenant_network_admin = "tenantNetworkAdmin" }">{…}</code> | |
|
||||
| [custom_roles](variables.tf#L93) | Map of role names => list of permissions to additionally create at the organization level. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [factories_config](variables.tf#L100) | Configuration for the organization policies factory. | <code title="object({ org_policy_data_path = optional(string, "data/org-policies") })">object({…})</code> | | <code>{}</code> | |
|
||||
| [fast_features](variables.tf#L109) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [federated_identity_providers](variables.tf#L122) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = optional(string) issuer = string custom_settings = optional(object({ issuer_uri = optional(string) audiences = optional(list(string), []) jwks_json = optional(string) }), {}) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [group_iam](variables.tf#L142) | Organization-level authoritative IAM binding for 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> | |
|
||||
| [groups](variables.tf#L149) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | <code>map(string)</code> | | <code title="{ gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", gcp-network-admins = "gcp-network-admins" gcp-organization-admins = "gcp-organization-admins" gcp-security-admins = "gcp-security-admins" gcp-support = "gcp-devops" }">{…}</code> | |
|
||||
| [iam](variables.tf#L167) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_bindings_additive](variables.tf#L174) | Organization-level custom 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> | |
|
||||
| [locations](variables.tf#L189) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "EU") gcs = optional(string, "EU") logging = optional(string, "global") pubsub = optional(list(string), []) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [log_sinks](variables.tf#L203) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\" OR protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.TransparencyLog\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } workspace-audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Fdata_access\" and protoPayload.serviceName:\"login.googleapis.com\"" type = "logging" } }">{…}</code> | |
|
||||
| [org_policies_config](variables.tf#L232) | Organization policies customization. | <code title="object({ constraints = optional(object({ allowed_policy_member_domains = optional(list(string), []) }), {}) tag_name = optional(string, "org-policies") tag_values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) })), {}) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [outputs_location](variables.tf#L257) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [project_parent_ids](variables.tf#L272) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | <code title="object({ automation = string billing = string logging = string })">object({…})</code> | | <code title="{ automation = null billing = null logging = null }">{…}</code> | |
|
||||
| [custom_roles](variables.tf#L79) | Map of role names => list of permissions to additionally create at the organization level. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [factories_config](variables.tf#L86) | Configuration for the organization policies factory. | <code title="object({ custom_roles = optional(string, "data/custom-roles") org_policy = optional(string, "data/org-policies") })">object({…})</code> | | <code>{}</code> | |
|
||||
| [fast_features](variables.tf#L96) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [federated_identity_providers](variables.tf#L109) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = optional(string) issuer = string custom_settings = optional(object({ issuer_uri = optional(string) audiences = optional(list(string), []) jwks_json = optional(string) }), {}) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [group_iam](variables.tf#L129) | Organization-level authoritative IAM binding for 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> | |
|
||||
| [groups](variables.tf#L136) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | <code>map(string)</code> | | <code title="{ gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", gcp-network-admins = "gcp-network-admins" gcp-organization-admins = "gcp-organization-admins" gcp-security-admins = "gcp-security-admins" gcp-support = "gcp-devops" }">{…}</code> | |
|
||||
| [iam](variables.tf#L154) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_bindings_additive](variables.tf#L161) | Organization-level custom 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> | |
|
||||
| [locations](variables.tf#L176) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "EU") gcs = optional(string, "EU") logging = optional(string, "global") pubsub = optional(list(string), []) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [log_sinks](variables.tf#L190) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\" OR protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.TransparencyLog\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } workspace-audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Fdata_access\" and protoPayload.serviceName:\"login.googleapis.com\"" type = "logging" } }">{…}</code> | |
|
||||
| [org_policies_config](variables.tf#L219) | Organization policies customization. | <code title="object({ constraints = optional(object({ allowed_policy_member_domains = optional(list(string), []) }), {}) tag_name = optional(string, "org-policies") tag_values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) })), {}) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [outputs_location](variables.tf#L244) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [project_parent_ids](variables.tf#L259) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | <code title="object({ automation = string billing = string logging = string })">object({…})</code> | | <code title="{ automation = null billing = null logging = null }">{…}</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive | consumers |
|
||||
|---|---|:---:|---|
|
||||
| [automation](outputs.tf#L112) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L117) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L122) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L134) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L139) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L149) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L154) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L164) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||
| [service_accounts](outputs.tf#L171) | Automation service accounts created by this stage. | | |
|
||||
| [tfvars](outputs.tf#L180) | Terraform variable files for the following stages. | ✓ | |
|
||||
| [automation](outputs.tf#L102) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L107) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L112) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L124) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L129) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L139) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L144) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L154) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||
| [service_accounts](outputs.tf#L161) | Automation service accounts created by this stage. | | |
|
||||
| [tfvars](outputs.tf#L170) | Terraform variable files for the following stages. | ✓ | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# 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.
|
||||
|
||||
# this is needed for use in additive IAM bindings, to avoid conflicts
|
||||
name: organizationIamAdmin
|
||||
includedPermissions:
|
||||
- resourcemanager.organizations.get
|
||||
- resourcemanager.organizations.getIamPolicy
|
||||
- resourcemanager.organizations.setIamPolicy
|
|
@ -0,0 +1,31 @@
|
|||
# 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.
|
||||
|
||||
name: serviceProjectNetworkAdmin
|
||||
includedPermissions:
|
||||
- compute.globalOperations.get
|
||||
# compute.networks.updatePeering and compute.networks.get are
|
||||
# used by automation service accounts who manage service
|
||||
# projects where peering creation might be needed (e.g. GKE). If
|
||||
# you remove them your network administrators should create
|
||||
# peerings for service projects
|
||||
- compute.networks.updatePeering
|
||||
- compute.networks.get
|
||||
- compute.organizations.disableXpnResource
|
||||
- compute.organizations.enableXpnResource
|
||||
- compute.projects.get
|
||||
- compute.subnetworks.getIamPolicy
|
||||
- compute.subnetworks.setIamPolicy
|
||||
- dns.networks.bindPrivateDNSZone
|
||||
- resourcemanager.projects.get
|
|
@ -0,0 +1,17 @@
|
|||
# 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.
|
||||
|
||||
name: tenantNetworkAdmin
|
||||
includedPermissions:
|
||||
- compute.globalOperations.get
|
|
@ -95,7 +95,7 @@ module "organization" {
|
|||
iam_bindings = {
|
||||
organization_iam_admin_conditional = {
|
||||
members = [module.automation-tf-resman-sa.iam_email]
|
||||
role = module.organization.custom_role_id[var.custom_role_names.organization_iam_admin]
|
||||
role = module.organization.custom_role_id["organization_iam_admin"]
|
||||
condition = {
|
||||
expression = format(
|
||||
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
|
||||
|
@ -106,7 +106,7 @@ module "organization" {
|
|||
"roles/compute.xpnAdmin",
|
||||
"roles/orgpolicy.policyAdmin",
|
||||
"roles/resourcemanager.organizationViewer",
|
||||
module.organization.custom_role_id[var.custom_role_names.tenant_network_admin]
|
||||
module.organization.custom_role_id["tenant_network_admin"]
|
||||
],
|
||||
local.billing_mode == "org" ? [
|
||||
"roles/billing.admin",
|
||||
|
@ -120,34 +120,13 @@ module "organization" {
|
|||
}
|
||||
}
|
||||
}
|
||||
custom_roles = merge(var.custom_roles, {
|
||||
# this is needed for use in additive IAM bindings, to avoid conflicts
|
||||
(var.custom_role_names.organization_iam_admin) = [
|
||||
"resourcemanager.organizations.get",
|
||||
"resourcemanager.organizations.getIamPolicy",
|
||||
"resourcemanager.organizations.setIamPolicy"
|
||||
]
|
||||
(var.custom_role_names.service_project_network_admin) = [
|
||||
"compute.globalOperations.get",
|
||||
# compute.networks.updatePeering and compute.networks.get are
|
||||
# used by automation service accounts who manage service
|
||||
# projects where peering creation might be needed (e.g. GKE). If
|
||||
# you remove them your network administrators should create
|
||||
# peerings for service projects
|
||||
"compute.networks.updatePeering",
|
||||
"compute.networks.get",
|
||||
"compute.organizations.disableXpnResource",
|
||||
"compute.organizations.enableXpnResource",
|
||||
"compute.projects.get",
|
||||
"compute.subnetworks.getIamPolicy",
|
||||
"compute.subnetworks.setIamPolicy",
|
||||
"dns.networks.bindPrivateDNSZone",
|
||||
"resourcemanager.projects.get",
|
||||
]
|
||||
(var.custom_role_names.tenant_network_admin) = [
|
||||
"compute.globalOperations.get",
|
||||
]
|
||||
})
|
||||
custom_roles = var.custom_roles
|
||||
factories_config = {
|
||||
custom_roles = var.factories_config.custom_roles
|
||||
org_policies = (
|
||||
var.bootstrap_user != null ? null : var.factories_config.org_policy
|
||||
)
|
||||
}
|
||||
logging_sinks = {
|
||||
for name, attrs in var.log_sinks : name => {
|
||||
bq_partitioned_table = attrs.type == "bigquery"
|
||||
|
@ -156,11 +135,6 @@ module "organization" {
|
|||
type = attrs.type
|
||||
}
|
||||
}
|
||||
org_policies_data_path = (
|
||||
var.bootstrap_user != null
|
||||
? null
|
||||
: var.factories_config.org_policy_data_path
|
||||
)
|
||||
org_policies = var.bootstrap_user != null ? {} : {
|
||||
"iam.allowedPolicyMemberDomains" = {
|
||||
rules = [
|
||||
|
|
|
@ -38,16 +38,6 @@ locals {
|
|||
}
|
||||
)
|
||||
}
|
||||
custom_roles = merge(
|
||||
{
|
||||
for k, v in var.custom_role_names :
|
||||
k => try(module.organization.custom_role_id[v], "")
|
||||
},
|
||||
{
|
||||
for k, v in var.custom_roles :
|
||||
k => try(module.organization.custom_role_id[k], "")
|
||||
}
|
||||
)
|
||||
providers = {
|
||||
"0-bootstrap" = templatefile(local._tpl_providers, {
|
||||
backend_extra = null
|
||||
|
@ -82,7 +72,7 @@ locals {
|
|||
project_id = module.automation-project.project_id
|
||||
project_number = module.automation-project.number
|
||||
}
|
||||
custom_roles = local.custom_roles
|
||||
custom_roles = module.organization.custom_role_id
|
||||
logging = {
|
||||
project_id = module.log-export-project.project_id
|
||||
project_number = module.log-export-project.number
|
||||
|
@ -133,7 +123,7 @@ output "cicd_repositories" {
|
|||
|
||||
output "custom_roles" {
|
||||
description = "Organization-level custom roles."
|
||||
value = local.custom_roles
|
||||
value = module.organization.custom_role_id
|
||||
}
|
||||
|
||||
output "federated_identity" {
|
||||
|
|
|
@ -76,20 +76,6 @@ variable "cicd_repositories" {
|
|||
}
|
||||
}
|
||||
|
||||
variable "custom_role_names" {
|
||||
description = "Names of custom roles defined at the org level."
|
||||
type = object({
|
||||
organization_iam_admin = string
|
||||
service_project_network_admin = string
|
||||
tenant_network_admin = string
|
||||
})
|
||||
default = {
|
||||
organization_iam_admin = "organizationIamAdmin"
|
||||
service_project_network_admin = "serviceProjectNetworkAdmin"
|
||||
tenant_network_admin = "tenantNetworkAdmin"
|
||||
}
|
||||
}
|
||||
|
||||
variable "custom_roles" {
|
||||
description = "Map of role names => list of permissions to additionally create at the organization level."
|
||||
type = map(list(string))
|
||||
|
@ -100,7 +86,8 @@ variable "custom_roles" {
|
|||
variable "factories_config" {
|
||||
description = "Configuration for the organization policies factory."
|
||||
type = object({
|
||||
org_policy_data_path = optional(string, "data/org-policies")
|
||||
custom_roles = optional(string, "data/custom-roles")
|
||||
org_policy = optional(string, "data/org-policies")
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
|
|
|
@ -122,10 +122,12 @@ The example below deploys a few organization policies split between two YAML fil
|
|||
|
||||
```hcl
|
||||
module "folder" {
|
||||
source = "./fabric/modules/folder"
|
||||
parent = var.folder_id
|
||||
name = "Folder name"
|
||||
org_policies_data_path = "configs/org-policies/"
|
||||
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
|
||||
```
|
||||
|
@ -198,6 +200,7 @@ module "folder" {
|
|||
}
|
||||
# tftest modules=2 resources=3 e2e serial
|
||||
```
|
||||
|
||||
## Log Sinks
|
||||
|
||||
```hcl
|
||||
|
@ -340,21 +343,21 @@ 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. | <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> |
|
||||
| [factories_config](variables.tf#L24) | Paths to data files and folders that enable factory functionality. | <code title="object({ org_policies = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L33) | Hierarchical firewall policy to associate to this folder. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [folder_create](variables.tf#L42) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
||||
| [group_iam](variables.tf#L48) | 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#L55) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L62) | 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#L77) | 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#L92) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
||||
| [logging_data_access](variables.tf#L98) | 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#L113) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L120) | 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#L151) | Folder name. | <code>string</code> | | <code>null</code> |
|
||||
| [org_policies](variables.tf#L157) | 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> |
|
||||
| [parent](variables.tf#L184) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
||||
| [tag_bindings](variables.tf#L194) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
|
||||
locals {
|
||||
_factory_data_raw = merge([
|
||||
for f in try(fileset(var.org_policies_data_path, "*.yaml"), []) :
|
||||
yamldecode(file("${var.org_policies_data_path}/${f}"))
|
||||
for f in try(fileset(var.factories_config.org_policies, "*.yaml"), []) :
|
||||
yamldecode(file("${var.factories_config.org_policies}/${f}"))
|
||||
]...)
|
||||
|
||||
# simulate applying defaults to data coming from yaml files
|
||||
_factory_data = {
|
||||
for k, v in local._factory_data_raw :
|
||||
|
@ -49,9 +48,7 @@ locals {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
_org_policies = merge(local._factory_data, var.org_policies)
|
||||
|
||||
org_policies = {
|
||||
for k, v in local._org_policies :
|
||||
k => merge(v, {
|
||||
|
|
|
@ -21,6 +21,15 @@ variable "contacts" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "factories_config" {
|
||||
description = "Paths to data files and folders that enable factory functionality."
|
||||
type = object({
|
||||
org_policies = optional(string)
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "firewall_policy" {
|
||||
description = "Hierarchical firewall policy to associate to this folder."
|
||||
type = object({
|
||||
|
@ -172,12 +181,6 @@ variable "org_policies" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "org_policies_data_path" {
|
||||
description = "Path containing org policies in YAML format."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "parent" {
|
||||
description = "Parent in folders/folder_id or organizations/org_id format."
|
||||
type = string
|
||||
|
|
|
@ -102,7 +102,6 @@ module "org" {
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
"compute.trustedImageProjects" = {
|
||||
rules = [{
|
||||
allow = {
|
||||
|
@ -145,7 +144,6 @@ To manage organization policy custom constraints, the `orgpolicy.googleapis.com`
|
|||
module "org" {
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
|
||||
org_policy_custom_constraints = {
|
||||
"custom.gkeEnableAutoUpgrade" = {
|
||||
resource_types = ["container.googleapis.com/NodePool"]
|
||||
|
@ -156,7 +154,6 @@ module "org" {
|
|||
description = "All node pools must have node auto-upgrade enabled."
|
||||
}
|
||||
}
|
||||
|
||||
# not necessarily to enforce on the org level, policy may be applied on folder/project levels
|
||||
org_policies = {
|
||||
"custom.gkeEnableAutoUpgrade" = {
|
||||
|
@ -177,9 +174,11 @@ The example below deploys a few org policy custom constraints split between two
|
|||
|
||||
```hcl
|
||||
module "org" {
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
org_policy_custom_constraints_data_path = "configs/custom-constraints"
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
factories_config = {
|
||||
org_policy_custom_constraints = "configs/custom-constraints"
|
||||
}
|
||||
org_policies = {
|
||||
"custom.gkeEnableAutoUpgrade" = {
|
||||
rules = [{ enforce = true }]
|
||||
|
@ -373,6 +372,41 @@ module "org" {
|
|||
# tftest modules=1 resources=2 inventory=roles.yaml e2e serial
|
||||
```
|
||||
|
||||
Custom roles can also be specified via a factory in a similar way to organization policies and policy constraints. Each file is mapped to a custom role, where
|
||||
|
||||
- the role name defaults to the file name but can be overridden via a `name` attribute in the yaml
|
||||
- role permissions are defined in an `includedPermissions` map
|
||||
|
||||
Custom roles defined via the variable are merged with those coming from the factory, and override them in case of duplicate names.
|
||||
|
||||
```hcl
|
||||
module "org" {
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
factories_config = {
|
||||
custom_roles = "data/custom_roles"
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=2 files=custom-role-1,custom-role-2 inventory=custom-roles.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# tftest-file id=custom-role-1 path=data/custom_roles/test_1.yaml
|
||||
|
||||
includedPermissions:
|
||||
- compute.globalOperations.get
|
||||
```
|
||||
|
||||
```yaml
|
||||
# tftest-file id=custom-role-2 path=data/custom_roles/test_2.yaml
|
||||
|
||||
name: projectViewer
|
||||
includedPermissions:
|
||||
- resourcemanager.projects.get
|
||||
- resourcemanager.projects.getIamPolicy
|
||||
- resourcemanager.projects.list
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
@ -453,24 +487,23 @@ module "org" {
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [organization_id](variables.tf#L212) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
|
||||
| [organization_id](variables.tf#L211) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
|
||||
| [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> |
|
||||
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L31) | Hierarchical firewall policies to associate to the organization. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [group_iam](variables.tf#L40) | 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#L47) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L54) | 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#L69) | 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> |
|
||||
| [logging_data_access](variables.tf#L84) | 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#L99) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L106) | Logging sinks to create for the organization. | <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> |
|
||||
| [network_tags](variables.tf#L137) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) network = string # project_id/vpc_name values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies](variables.tf#L159) | Organization policies applied to this organization 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#L186) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
| [org_policy_custom_constraints](variables.tf#L192) | Organization policy custom constraints keyed by constraint name. | <code title="map(object({ display_name = optional(string) description = optional(string) action_type = string condition = string method_types = list(string) resource_types = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policy_custom_constraints_data_path](variables.tf#L206) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
| [tag_bindings](variables.tf#L221) | Tag bindings for this organization, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tags](variables.tf#L227) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [factories_config](variables.tf#L31) | Paths to data files and folders that enable factory functionality. | <code title="object({ custom_roles = optional(string) org_policies = optional(string) org_policy_custom_constraints = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [firewall_policy](variables.tf#L42) | Hierarchical firewall policies to associate to the organization. | <code title="object({ name = string policy = string })">object({…})</code> | | <code>null</code> |
|
||||
| [group_iam](variables.tf#L51) | 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#L58) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L65) | 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#L80) | 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> |
|
||||
| [logging_data_access](variables.tf#L95) | 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#L110) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L117) | Logging sinks to create for the organization. | <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> |
|
||||
| [network_tags](variables.tf#L148) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) network = string # project_id/vpc_name values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies](variables.tf#L170) | Organization policies applied to this organization 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_policy_custom_constraints](variables.tf#L197) | Organization policy custom constraints keyed by constraint name. | <code title="map(object({ display_name = optional(string) description = optional(string) action_type = string condition = string method_types = list(string) resource_types = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tag_bindings](variables.tf#L220) | Tag bindings for this organization, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tags](variables.tf#L226) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) id = optional(string) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
@ -478,12 +511,12 @@ module "org" {
|
|||
|---|---|:---:|
|
||||
| [custom_constraint_ids](outputs.tf#L17) | Map of CUSTOM_CONSTRAINTS => ID in the organization. | |
|
||||
| [custom_role_id](outputs.tf#L22) | Map of custom role IDs created in the organization. | |
|
||||
| [custom_roles](outputs.tf#L35) | Map of custom roles resources created in the organization. | |
|
||||
| [id](outputs.tf#L40) | Fully qualified organization id. | |
|
||||
| [network_tag_keys](outputs.tf#L57) | Tag key resources. | |
|
||||
| [network_tag_values](outputs.tf#L66) | Tag value resources. | |
|
||||
| [organization_id](outputs.tf#L76) | Organization id dependent on module resources. | |
|
||||
| [sink_writer_identities](outputs.tf#L93) | Writer identities created for each sink. | |
|
||||
| [tag_keys](outputs.tf#L101) | Tag key resources. | |
|
||||
| [tag_values](outputs.tf#L110) | Tag value resources. | |
|
||||
| [custom_roles](outputs.tf#L32) | Map of custom roles resources created in the organization. | |
|
||||
| [id](outputs.tf#L37) | Fully qualified organization id. | |
|
||||
| [network_tag_keys](outputs.tf#L54) | Tag key resources. | |
|
||||
| [network_tag_values](outputs.tf#L63) | Tag value resources. | |
|
||||
| [organization_id](outputs.tf#L73) | Organization id dependent on module resources. | |
|
||||
| [sink_writer_identities](outputs.tf#L90) | Writer identities created for each sink. | |
|
||||
| [tag_keys](outputs.tf#L98) | Tag key resources. | |
|
||||
| [tag_values](outputs.tf#L107) | Tag value resources. | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -17,12 +17,32 @@
|
|||
# tfdoc:file:description IAM bindings, roles and audit logging resources.
|
||||
|
||||
locals {
|
||||
_custom_roles = {
|
||||
for f in try(fileset(var.factories_config.custom_roles, "*.yaml"), []) :
|
||||
replace(f, ".yaml", "") => yamldecode(
|
||||
file("${var.factories_config.custom_roles}/${f}")
|
||||
)
|
||||
}
|
||||
_group_iam_roles = distinct(flatten(values(var.group_iam)))
|
||||
_group_iam = {
|
||||
for r in local._group_iam_roles : r => [
|
||||
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
custom_roles = merge(
|
||||
{
|
||||
for k, v in local._custom_roles : k => {
|
||||
name = lookup(v, "name", k)
|
||||
permissions = v["includedPermissions"]
|
||||
}
|
||||
},
|
||||
{
|
||||
for k, v in var.custom_roles : k => {
|
||||
name = k
|
||||
permissions = v
|
||||
}
|
||||
}
|
||||
)
|
||||
iam = {
|
||||
for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
|
||||
role => concat(
|
||||
|
@ -32,13 +52,27 @@ locals {
|
|||
}
|
||||
}
|
||||
|
||||
# we use a different key for custom roles to allow referring to the role alias
|
||||
# in Terraform, while still being able to define unique role names
|
||||
|
||||
check "custom_roles" {
|
||||
assert {
|
||||
condition = (
|
||||
length(local.custom_roles) == length({
|
||||
for k, v in local.custom_roles : v.name => null
|
||||
})
|
||||
)
|
||||
error_message = "Duplicate role name in custom roles."
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_organization_iam_custom_role" "roles" {
|
||||
for_each = var.custom_roles
|
||||
for_each = local.custom_roles
|
||||
org_id = local.organization_id_numeric
|
||||
role_id = each.key
|
||||
title = "Custom role ${each.key}"
|
||||
role_id = each.value.name
|
||||
title = "Custom role ${each.value.name}"
|
||||
description = "Terraform-managed."
|
||||
permissions = each.value
|
||||
permissions = each.value.permissions
|
||||
}
|
||||
|
||||
resource "google_organization_iam_binding" "authoritative" {
|
||||
|
|
|
@ -16,11 +16,9 @@
|
|||
|
||||
locals {
|
||||
_custom_constraints_factory_data_raw = merge([
|
||||
for f in try(fileset(var.org_policy_custom_constraints_data_path, "*.yaml"), []) :
|
||||
yamldecode(file("${var.org_policy_custom_constraints_data_path}/${f}"))
|
||||
for f in try(fileset(var.factories_config.org_policy_custom_constraints, "*.yaml"), []) :
|
||||
yamldecode(file("${var.factories_config.org_policy_custom_constraints}/${f}"))
|
||||
]...)
|
||||
|
||||
|
||||
_custom_constraints_factory_data = {
|
||||
for k, v in local._custom_constraints_factory_data_raw :
|
||||
k => {
|
||||
|
@ -32,9 +30,10 @@ locals {
|
|||
resource_types = v.resource_types
|
||||
}
|
||||
}
|
||||
|
||||
_custom_constraints = merge(local._custom_constraints_factory_data, var.org_policy_custom_constraints)
|
||||
|
||||
_custom_constraints = merge(
|
||||
local._custom_constraints_factory_data,
|
||||
var.org_policy_custom_constraints
|
||||
)
|
||||
custom_constraints = {
|
||||
for k, v in local._custom_constraints :
|
||||
k => merge(v, {
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
|
||||
locals {
|
||||
_factory_data_raw = merge([
|
||||
for f in try(fileset(var.org_policies_data_path, "*.yaml"), []) :
|
||||
yamldecode(file("${var.org_policies_data_path}/${f}"))
|
||||
for f in try(fileset(var.factories_config.org_policies, "*.yaml"), []) :
|
||||
yamldecode(file("${var.factories_config.org_policies}/${f}"))
|
||||
]...)
|
||||
|
||||
# simulate applying defaults to data coming from yaml files
|
||||
_factory_data = {
|
||||
for k, v in local._factory_data_raw :
|
||||
|
@ -49,9 +48,7 @@ locals {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
_org_policies = merge(local._factory_data, var.org_policies)
|
||||
|
||||
org_policies = {
|
||||
for k, v in local._org_policies :
|
||||
k => merge(v, {
|
||||
|
|
|
@ -22,14 +22,11 @@ output "custom_constraint_ids" {
|
|||
output "custom_role_id" {
|
||||
description = "Map of custom role IDs created in the organization."
|
||||
value = {
|
||||
for role_id, role in google_organization_iam_custom_role.roles :
|
||||
for k, v in google_organization_iam_custom_role.roles :
|
||||
# build the string manually so that role IDs can be used as map
|
||||
# keys (useful for folder/organization/project-level iam bindings)
|
||||
(role_id) => "${var.organization_id}/roles/${role_id}"
|
||||
(k) => "${var.organization_id}/roles/${local.custom_roles[k].name}"
|
||||
}
|
||||
depends_on = [
|
||||
google_organization_iam_custom_role.roles
|
||||
]
|
||||
}
|
||||
|
||||
output "custom_roles" {
|
||||
|
|
|
@ -28,6 +28,17 @@ variable "custom_roles" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "factories_config" {
|
||||
description = "Paths to data files and folders that enable factory functionality."
|
||||
type = object({
|
||||
custom_roles = optional(string)
|
||||
org_policies = optional(string)
|
||||
org_policy_custom_constraints = optional(string)
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "firewall_policy" {
|
||||
description = "Hierarchical firewall policies to associate to the organization."
|
||||
type = object({
|
||||
|
@ -183,12 +194,6 @@ variable "org_policies" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "org_policies_data_path" {
|
||||
description = "Path containing org policies in YAML format."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "org_policy_custom_constraints" {
|
||||
description = "Organization policy custom constraints keyed by constraint name."
|
||||
type = map(object({
|
||||
|
@ -203,12 +208,6 @@ variable "org_policy_custom_constraints" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "org_policy_custom_constraints_data_path" {
|
||||
description = "Path containing org policy custom constraints in YAML format."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "organization_id" {
|
||||
description = "Organization id in organizations/nnnnnn format."
|
||||
type = string
|
||||
|
|
|
@ -421,12 +421,14 @@ The example below deploys a few organization policies split between two YAML fil
|
|||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
billing_account = var.billing_account_id
|
||||
name = "project"
|
||||
parent = var.folder_id
|
||||
prefix = var.prefix
|
||||
org_policies_data_path = "configs/org-policies/"
|
||||
source = "./fabric/modules/project"
|
||||
billing_account = var.billing_account_id
|
||||
name = "project"
|
||||
parent = var.folder_id
|
||||
prefix = var.prefix
|
||||
factories_config = {
|
||||
org_policies = "configs/org-policies/"
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=8 files=boolean,list inventory=org-policies.yaml e2e
|
||||
```
|
||||
|
@ -898,7 +900,7 @@ module "bucket" {
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [name](variables.tf#L186) | Project name and id suffix. | <code>string</code> | ✓ | |
|
||||
| [name](variables.tf#L196) | Project name and id suffix. | <code>string</code> | ✓ | |
|
||||
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | <code>bool</code> | | <code>false</code> |
|
||||
| [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | | <code>null</code> |
|
||||
| [compute_metadata](variables.tf#L29) | Optional compute metadata key/values. Only usable if compute API has been enabled. | <code>map(string)</code> | | <code>{}</code> |
|
||||
|
@ -906,41 +908,41 @@ module "bucket" {
|
|||
| [custom_roles](variables.tf#L43) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [default_service_account](variables.tf#L50) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | <code>string</code> | | <code>"keep"</code> |
|
||||
| [descriptive_name](variables.tf#L63) | Name of the project name. Used for project name instead of `name` variable. | <code>string</code> | | <code>null</code> |
|
||||
| [group_iam](variables.tf#L69) | 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#L76) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L83) | 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#L98) | 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> |
|
||||
| [labels](variables.tf#L113) | Resource labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lien_reason](variables.tf#L120) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>null</code> |
|
||||
| [logging_data_access](variables.tf#L126) | 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#L141) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L148) | Logging sinks to create for this project. | <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) type = string unique_writer = optional(bool, true) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [metric_scopes](variables.tf#L179) | List of projects that will act as metric scopes for this project. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [org_policies](variables.tf#L191) | Organization policies applied to this project 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#L218) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
| [parent](variables.tf#L224) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
|
||||
| [prefix](variables.tf#L234) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L244) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
|
||||
| [service_config](variables.tf#L250) | Configure service API activation. | <code title="object({ disable_on_destroy = bool disable_dependent_services = bool })">object({…})</code> | | <code title="{ disable_on_destroy = false disable_dependent_services = false }">{…}</code> |
|
||||
| [service_encryption_key_ids](variables.tf#L262) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_perimeter_bridges](variables.tf#L269) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [service_perimeter_standard](variables.tf#L276) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
|
||||
| [services](variables.tf#L282) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [shared_vpc_host_config](variables.tf#L288) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L297) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L320) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
|
||||
| [tag_bindings](variables.tf#L326) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [factories_config](variables.tf#L69) | Paths to data files and folders that enable factory functionality. | <code title="object({ custom_roles = optional(string) org_policies = optional(string) })">object({…})</code> | | <code>{}</code> |
|
||||
| [group_iam](variables.tf#L79) | 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#L86) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L93) | 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#L108) | 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> |
|
||||
| [labels](variables.tf#L123) | Resource labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lien_reason](variables.tf#L130) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>null</code> |
|
||||
| [logging_data_access](variables.tf#L136) | 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#L151) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L158) | Logging sinks to create for this project. | <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) type = string unique_writer = optional(bool, true) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [metric_scopes](variables.tf#L189) | List of projects that will act as metric scopes for this project. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [org_policies](variables.tf#L201) | Organization policies applied to this project 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> |
|
||||
| [parent](variables.tf#L228) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
|
||||
| [prefix](variables.tf#L238) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L248) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
|
||||
| [service_config](variables.tf#L254) | Configure service API activation. | <code title="object({ disable_on_destroy = bool disable_dependent_services = bool })">object({…})</code> | | <code title="{ disable_on_destroy = false disable_dependent_services = false }">{…}</code> |
|
||||
| [service_encryption_key_ids](variables.tf#L266) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_perimeter_bridges](variables.tf#L273) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [service_perimeter_standard](variables.tf#L280) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
|
||||
| [services](variables.tf#L286) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [shared_vpc_host_config](variables.tf#L292) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L301) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L324) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
|
||||
| [tag_bindings](variables.tf#L330) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [custom_roles](outputs.tf#L17) | Ids of the created custom roles. | |
|
||||
| [id](outputs.tf#L25) | Project id. | |
|
||||
| [name](outputs.tf#L44) | Project name. | |
|
||||
| [number](outputs.tf#L56) | Project number. | |
|
||||
| [project_id](outputs.tf#L75) | Project id. | |
|
||||
| [service_accounts](outputs.tf#L94) | Product robot service accounts in project. | |
|
||||
| [services](outputs.tf#L110) | Service APIs to enabled in the project. | |
|
||||
| [sink_writer_identities](outputs.tf#L119) | Writer identities created for each sink. | |
|
||||
| [custom_role_ids](outputs.tf#L17) | Map of custom role IDs created in the project. | |
|
||||
| [id](outputs.tf#L27) | Project id. | |
|
||||
| [name](outputs.tf#L46) | Project name. | |
|
||||
| [number](outputs.tf#L58) | Project number. | |
|
||||
| [project_id](outputs.tf#L77) | Project id. | |
|
||||
| [service_accounts](outputs.tf#L96) | Product robot service accounts in project. | |
|
||||
| [services](outputs.tf#L112) | Service APIs to enabled in the project. | |
|
||||
| [sink_writer_identities](outputs.tf#L121) | Writer identities created for each sink. | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -20,12 +20,32 @@
|
|||
# - external users need to have accepted the invitation email to join
|
||||
|
||||
locals {
|
||||
_custom_roles = {
|
||||
for f in try(fileset(var.factories_config.custom_roles, "*.yaml"), []) :
|
||||
replace(f, ".yaml", "") => yamldecode(
|
||||
file("${var.factories_config.custom_roles}/${f}")
|
||||
)
|
||||
}
|
||||
_group_iam_roles = distinct(flatten(values(var.group_iam)))
|
||||
_group_iam = {
|
||||
for r in local._group_iam_roles : r => [
|
||||
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
custom_roles = merge(
|
||||
{
|
||||
for k, v in local._custom_roles : k => {
|
||||
name = lookup(v, "name", k)
|
||||
permissions = v["includedPermissions"]
|
||||
}
|
||||
},
|
||||
{
|
||||
for k, v in var.custom_roles : k => {
|
||||
name = k
|
||||
permissions = v
|
||||
}
|
||||
}
|
||||
)
|
||||
iam = {
|
||||
for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
|
||||
role => concat(
|
||||
|
@ -35,13 +55,27 @@ locals {
|
|||
}
|
||||
}
|
||||
|
||||
# we use a different key for custom roles to allow referring to the role alias
|
||||
# in Terraform, while still being able to define unique role names
|
||||
|
||||
check "custom_roles" {
|
||||
assert {
|
||||
condition = (
|
||||
length(local.custom_roles) == length({
|
||||
for k, v in local.custom_roles : v.name => null
|
||||
})
|
||||
)
|
||||
error_message = "Duplicate role name in custom roles."
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_project_iam_custom_role" "roles" {
|
||||
for_each = var.custom_roles
|
||||
for_each = local.custom_roles
|
||||
project = local.project.project_id
|
||||
role_id = each.key
|
||||
title = "Custom role ${each.key}"
|
||||
role_id = each.value.name
|
||||
title = "Custom role ${each.value.name}"
|
||||
description = "Terraform-managed."
|
||||
permissions = each.value
|
||||
permissions = each.value.permissions
|
||||
}
|
||||
|
||||
resource "google_project_iam_binding" "authoritative" {
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
|
||||
locals {
|
||||
_factory_data_raw = merge([
|
||||
for f in try(fileset(var.org_policies_data_path, "*.yaml"), []) :
|
||||
yamldecode(file("${var.org_policies_data_path}/${f}"))
|
||||
for f in try(fileset(var.factories_config.org_policies, "*.yaml"), []) :
|
||||
yamldecode(file("${var.factories_config.org_policies}/${f}"))
|
||||
]...)
|
||||
|
||||
# simulate applying defaults to data coming from yaml files
|
||||
_factory_data = {
|
||||
for k, v in local._factory_data_raw :
|
||||
|
@ -49,9 +48,7 @@ locals {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
_org_policies = merge(local._factory_data, var.org_policies)
|
||||
|
||||
org_policies = {
|
||||
for k, v in local._org_policies :
|
||||
k => merge(v, {
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
output "custom_roles" {
|
||||
description = "Ids of the created custom roles."
|
||||
output "custom_role_ids" {
|
||||
description = "Map of custom role IDs created in the project."
|
||||
value = {
|
||||
for name, role in google_project_iam_custom_role.roles :
|
||||
name => role.id
|
||||
for k, v in google_project_iam_custom_role.roles :
|
||||
# build the string manually so that role IDs can be used as map
|
||||
# keys (useful for folder/organization/project-level iam bindings)
|
||||
(k) => "projects/${local.prefix}${var.name}/roles/${local.custom_roles[k].name}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,16 @@ variable "descriptive_name" {
|
|||
default = null
|
||||
}
|
||||
|
||||
variable "factories_config" {
|
||||
description = "Paths to data files and folders that enable factory functionality."
|
||||
type = object({
|
||||
custom_roles = optional(string)
|
||||
org_policies = optional(string)
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -215,12 +225,6 @@ variable "org_policies" {
|
|||
nullable = false
|
||||
}
|
||||
|
||||
variable "org_policies_data_path" {
|
||||
description = "Path containing org policies in YAML format."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "parent" {
|
||||
description = "Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format."
|
||||
type = string
|
||||
|
|
|
@ -25,5 +25,5 @@ def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
|
|||
'modules/folder',
|
||||
tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
|
||||
yaml_plan = plan_summary('modules/folder', tf_var_files=['common.tfvars'],
|
||||
org_policies_data_path=f'{tmp_path}')
|
||||
factories_config=f'{{org_policies="{tmp_path}"}}')
|
||||
assert tfvars_plan.values == yaml_plan.values
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# 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.org.google_organization_iam_custom_role.roles["test_2"]:
|
||||
description: Terraform-managed.
|
||||
org_id: '1122334455'
|
||||
permissions:
|
||||
- resourcemanager.projects.get
|
||||
- resourcemanager.projects.getIamPolicy
|
||||
- resourcemanager.projects.list
|
||||
role_id: projectViewer
|
||||
stage: GA
|
||||
title: Custom role projectViewer
|
||||
module.org.google_organization_iam_custom_role.roles["test_1"]:
|
||||
description: Terraform-managed.
|
||||
org_id: '1122334455'
|
||||
permissions:
|
||||
- compute.globalOperations.get
|
||||
role_id: test_1
|
||||
stage: GA
|
||||
title: Custom role test_1
|
||||
|
||||
counts:
|
||||
google_organization_iam_custom_role: 2
|
||||
modules: 1
|
||||
resources: 2
|
||||
|
||||
outputs: {}
|
|
@ -26,7 +26,7 @@ def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
|
|||
tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
|
||||
yaml_plan = plan_summary('modules/organization',
|
||||
tf_var_files=['common.tfvars'],
|
||||
org_policies_data_path=f'{tmp_path}')
|
||||
factories_config=f'{{org_policies="{tmp_path}"}}')
|
||||
assert tfvars_plan.values == yaml_plan.values
|
||||
|
||||
|
||||
|
@ -39,5 +39,5 @@ def test_custom_constraint_factory(plan_summary, tfvars_to_yaml, tmp_path):
|
|||
tf_var_files=['common.tfvars', f'org_policies_custom_constraints.tfvars'])
|
||||
yaml_plan = plan_summary(
|
||||
'modules/organization', tf_var_files=['common.tfvars'],
|
||||
org_policy_custom_constraints_data_path=f'{tmp_path}')
|
||||
factories_config=f'{{org_policy_custom_constraints="{tmp_path}"}}')
|
||||
assert tfvars_plan.values == yaml_plan.values
|
||||
|
|
|
@ -34,7 +34,7 @@ def test_policy_implementation():
|
|||
'@@ -17 +17 @@\n',
|
||||
'-# tfdoc:file:description Project-level organization policies.\n',
|
||||
'+# tfdoc:file:description Folder-level organization policies.\n',
|
||||
'@@ -58,2 +58,2 @@\n',
|
||||
'@@ -55,2 +55,2 @@\n',
|
||||
'- name = "projects/${local.project.project_id}/policies/${k}"\n',
|
||||
'- parent = "projects/${local.project.project_id}"\n',
|
||||
'+ name = "${local.folder.name}/policies/${k}"\n',
|
||||
|
@ -49,12 +49,12 @@ def test_policy_implementation():
|
|||
'@@ -17 +17 @@\n',
|
||||
'-# tfdoc:file:description Folder-level organization policies.\n',
|
||||
'+# tfdoc:file:description Organization-level organization policies.\n',
|
||||
'@@ -58,2 +58,2 @@\n',
|
||||
'@@ -55,2 +55,2 @@\n',
|
||||
'- name = "${local.folder.name}/policies/${k}"\n',
|
||||
'- parent = local.folder.name\n',
|
||||
'+ name = "${var.organization_id}/policies/${k}"\n',
|
||||
'+ parent = var.organization_id\n',
|
||||
'@@ -116,0 +117,9 @@\n',
|
||||
'@@ -113,0 +114,9 @@\n',
|
||||
'+ depends_on = [\n',
|
||||
'+ google_organization_iam_binding.authoritative,\n',
|
||||
'+ google_organization_iam_binding.bindings,\n',
|
||||
|
|
|
@ -25,5 +25,5 @@ def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
|
|||
'modules/project',
|
||||
tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
|
||||
yaml_plan = plan_summary('modules/project', tf_var_files=['common.tfvars'],
|
||||
org_policies_data_path=f'{tmp_path}')
|
||||
factories_config=f'{{org_policies="{tmp_path}"}}')
|
||||
assert tfvars_plan.values == yaml_plan.values
|
||||
|
|
Loading…
Reference in New Issue