FAST: initial implementation of lightweight tenants (#1470)
* initial import * fixes * fixes * fixes * red SA roles * red SA roles * org-level custom roles var, tenants IAM config * tfdoc * allow core SA to write output files to tenant bucket * README * implement comments on PR * show tenant org example * update example
This commit is contained in:
parent
623c886e95
commit
154df17951
|
@ -502,34 +502,35 @@ The remaining configuration is manual, as it regards the repositories themselves
|
|||
| 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#L194) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L209) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
||||
| [organization](variables.tf#L201) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L216) | 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> | |
|
||||
| [fast_features](variables.tf#L93) | 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#L106) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = string issuer = string custom_settings = object({ issuer_uri = string allowed_audiences = list(string) }) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [groups](variables.tf#L120) | 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#L138) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_additive](variables.tf#L144) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [locations](variables.tf#L150) | 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> | |
|
||||
| [log_sinks](variables.tf#L169) | 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\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L203) | 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#L218) | 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#L93) | Map of role names => list of permissions to additionally create at the organization level. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [fast_features](variables.tf#L100) | 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#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = string issuer = string custom_settings = object({ issuer_uri = string allowed_audiences = list(string) }) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [groups](variables.tf#L127) | 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#L145) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_additive](variables.tf#L151) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [locations](variables.tf#L157) | 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> | |
|
||||
| [log_sinks](variables.tf#L176) | 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\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L210) | 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#L225) | 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#L91) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L96) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L101) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L113) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L118) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L128) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L133) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L143) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||
| [service_accounts](outputs.tf#L150) | Automation service accounts created by this stage. | | |
|
||||
| [tfvars](outputs.tf#L159) | Terraform variable files for the following stages. | ✓ | |
|
||||
| [automation](outputs.tf#L97) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L102) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L107) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L119) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L124) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L134) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L139) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L149) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||
| [service_accounts](outputs.tf#L156) | Automation service accounts created by this stage. | | |
|
||||
| [tfvars](outputs.tf#L165) | Terraform variable files for the following stages. | ✓ | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -163,7 +163,7 @@ module "organization" {
|
|||
iam = local.iam
|
||||
# additive bindings, used for roles co-managed by different stages
|
||||
iam_additive = local.iam_additive
|
||||
custom_roles = {
|
||||
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",
|
||||
|
@ -190,7 +190,7 @@ module "organization" {
|
|||
(var.custom_role_names.tenant_network_admin) = [
|
||||
"compute.globalOperations.get",
|
||||
]
|
||||
}
|
||||
})
|
||||
logging_sinks = {
|
||||
for name, attrs in var.log_sinks : name => {
|
||||
bq_partitioned_table = attrs.type == "bigquery"
|
||||
|
|
|
@ -33,10 +33,16 @@ locals {
|
|||
}
|
||||
)
|
||||
}
|
||||
custom_roles = {
|
||||
for k, v in var.custom_role_names :
|
||||
k => try(module.organization.custom_role_id[v], null)
|
||||
}
|
||||
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
|
||||
|
|
|
@ -90,6 +90,13 @@ variable "custom_role_names" {
|
|||
}
|
||||
}
|
||||
|
||||
variable "custom_roles" {
|
||||
description = "Map of role names => list of permissions to additionally create at the organization level."
|
||||
type = map(list(string))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "fast_features" {
|
||||
description = "Selective control for top-level FAST features."
|
||||
type = object({
|
||||
|
|
|
@ -25,6 +25,7 @@ The following diagram is a high level reference of the resources created and man
|
|||
- [Running the stage](#running-the-stage)
|
||||
- [Customizations](#customizations)
|
||||
- [Secure tags](#secure-tags)
|
||||
- [Lightweight multitenancy](#lightweight-multitenancy)
|
||||
- [Team folders](#team-folders)
|
||||
- [Organization Policies](#organization-policies)
|
||||
- [IAM](#iam)
|
||||
|
@ -166,6 +167,125 @@ tags = {
|
|||
}
|
||||
```
|
||||
|
||||
### Lightweight multitenancy
|
||||
|
||||
If the organization needs to support tenants without the full complexity and separation offered by our [full multitenant support](../../stages-multitenant/), this stage offers a simplified setup which is suitable for cases where tenants have less autonomy, and don't need to implement FAST stages inside their reserved partition.
|
||||
|
||||
This mode is activated by defining tenants in the `tenants` variable, while IAM configurations that apply to every tenant can be optionally set in the `tenants_config` variable.
|
||||
|
||||
The resulting setup provides a new "Tenants" branch in the hierarchy with one second-level folder for each tenant, and additional folders inside it to host tenant resources managed from the central team, and tenant resources managed by the tenant itself. Automation resources are provided for both teams.
|
||||
|
||||
This allows subsequent Terraform stages to create network resources for each tenant which are centrally managed and connected to central networking, and tenants themselves to optionally manage their own networking and application projects.
|
||||
|
||||
The default roles applied on tenant folders are
|
||||
|
||||
- on the top-level folder for each tenant
|
||||
- for the core IaC service account
|
||||
- `roles/cloudasset.owner`
|
||||
- `roles/compute.xpnAdmin`
|
||||
- `roles/logging.admin`
|
||||
- `roles/resourcemanager.folderAdmin`
|
||||
- `roles/resourcemanager.projectCreator`
|
||||
- `roles/resourcemanager.tagUser`
|
||||
- on the core folder for each tenant
|
||||
- for the core IaC service account
|
||||
- `roles/owner`
|
||||
- for the tenant admin group and IaC service account
|
||||
- `roles/viewer`
|
||||
- on the tenant folder for each tenant
|
||||
- for the tenant admin group and IaC service account
|
||||
- `roles/cloudasset.owner`
|
||||
- `roles/compute.xpnAdmin`
|
||||
- `roles/logging.admin`
|
||||
- `roles/resourcemanager.folderAdmin`
|
||||
- `roles/resourcemanager.projectCreator`
|
||||
- `roles/resourcemanager.tagUser`
|
||||
- `roles/owner`
|
||||
|
||||
Further customization is possible via the `tenants_config` variable.
|
||||
|
||||
This is a high level diagram of the design described above.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme':'base'}}%%
|
||||
classDiagram
|
||||
Organization -- Tenants_root~📁~
|
||||
Organization -- org_iac
|
||||
Tenants_root~📁~ -- Tenant_0_root~📁~
|
||||
Tenants_root~📁~ -- Tenant_1_root~📁~
|
||||
Tenant_0_root~📁~ -- Tenant_0_core~📁~
|
||||
Tenant_0_root~📁~ -- Tenant_0_self~📁~
|
||||
Tenant_0_self~📁~ -- tenant0_iac
|
||||
Tenant_1_root~📁~ -- Tenant_1_core~📁~
|
||||
Tenant_1_root~📁~ -- Tenant_1_self~📁~
|
||||
Tenant_1_self~📁~ -- tenant1_iac
|
||||
class org_iac["org_iac (from stage 0)"] {
|
||||
- GCS buckets
|
||||
- service accounts
|
||||
}
|
||||
class Tenants_root~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_0_root~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_0_core~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_0_self~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class tenant0_iac {
|
||||
- GCS buckets
|
||||
- service account
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_1_root~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_1_core~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class Tenant_1_self~📁~ {
|
||||
- IAM bindings()
|
||||
}
|
||||
class tenant1_iac {
|
||||
- GCS buckets
|
||||
- service account
|
||||
- IAM bindings()
|
||||
}
|
||||
```
|
||||
|
||||
This is an example that shows how to populate the relevant variables.
|
||||
|
||||
```hcl
|
||||
tenants = {
|
||||
tn0 = {
|
||||
admin_group_email = "tn-0-admins@tenant.example.org"
|
||||
descriptive_name = "Tenant 0"
|
||||
# an optional billing account and org can be specified for the tenant
|
||||
organization = {
|
||||
customer_id = "CAbCde0123"
|
||||
domain = "tenant.example.com"
|
||||
id = 1234567890
|
||||
}
|
||||
}
|
||||
tnq = {
|
||||
admin_group_email = "tn-1-admins@example.org"
|
||||
descriptive_name = "Tenant 1"
|
||||
}
|
||||
}
|
||||
tenants_config = {
|
||||
core_folder_roles = [
|
||||
"roles/compute.instanceAdmin.v1",
|
||||
"organizations/1234567890/roles/tenantLoadBalancerAdmin"
|
||||
]
|
||||
top_folder_roles = ["roles/logging.admin", "roles/monitoring.admin"]
|
||||
}
|
||||
```
|
||||
|
||||
Providers and tfvars files will be created for each tenant.
|
||||
|
||||
### Team folders
|
||||
|
||||
This stage provides a single built-in customization that offers a minimal (but usable) implementation of the "application" or "business" grouping for resources discussed above. The `team_folders` variable allows you to specify a map of team name and groups, that will result in folders, automation service accounts, and IAM policies applied.
|
||||
|
@ -227,6 +347,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
|||
| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> |
|
||||
| [branch-security.tf](./branch-security.tf) | Security stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
||||
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
||||
| [branch-tenants.tf](./branch-tenants.tf) | Lightweight tenant resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> · <code>organization</code> · <code>project</code> | |
|
||||
| [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||
| [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||
| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||
|
@ -236,6 +357,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
|||
| [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | |
|
||||
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
|
||||
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
|
||||
| [outputs-tenants.tf](./outputs-tenants.tf) | None | | <code>google_storage_bucket_object</code> · <code>local_file</code> |
|
||||
| [outputs.tf](./outputs.tf) | Module outputs. | | |
|
||||
| [variables.tf](./variables.tf) | Module variables. | | |
|
||||
|
||||
|
@ -258,6 +380,8 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
|||
| [tag_names](variables.tf#L226) | Customized names for resource management tags. | <code title="object({ context = string environment = string org-policies = string tenant = string })">object({…})</code> | | <code title="{ context = "context" environment = "environment" org-policies = "org-policies" tenant = "tenant" }">{…}</code> | |
|
||||
| [tags](variables.tf#L247) | Custome secure tags by key name. 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)), {}) 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> | |
|
||||
| [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> | |
|
||||
| [tenants](variables.tf#L278) | Lightweight tenant definitions. | <code title="map(object({ admin_group_email = string descriptive_name = string billing_account = optional(string) organization = optional(object({ customer_id = string domain = string id = number })) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [tenants_config](variables.tf#L294) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object({ core_folder_roles = optional(list(string), []) tenant_folder_roles = optional(list(string), []) top_folder_roles = optional(list(string), []) })">object({…})</code> | | <code>{}</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Lightweight tenant resources.
|
||||
|
||||
locals {
|
||||
tenant_iam = {
|
||||
for k, v in var.tenants : k => [
|
||||
"group:${v.admin_group_email}",
|
||||
module.tenant-self-iac-sa[k].iam_email
|
||||
]
|
||||
}
|
||||
tenant_org_iam = compact(flatten([
|
||||
for k, v in var.tenants : [
|
||||
"group:${v.admin_group_email}",
|
||||
v.organization != null ? "domain:${v.organization.domain}" : null
|
||||
]
|
||||
]))
|
||||
}
|
||||
|
||||
|
||||
# org-level roles for each tenant (additive)
|
||||
|
||||
module "tenant-org-iam" {
|
||||
source = "../../../modules/organization"
|
||||
organization_id = "organizations/${var.organization.id}"
|
||||
iam_additive = {
|
||||
"roles/compute.osLoginExternalUser" = [
|
||||
for k, v in var.tenants :
|
||||
"domain:${v.organization.domain}" if v.organization != null
|
||||
]
|
||||
"roles/resourcemanager.organizationViewer" = local.tenant_org_iam
|
||||
}
|
||||
}
|
||||
|
||||
# top-level "Tenants" folder
|
||||
|
||||
module "tenant-tenants-folder" {
|
||||
source = "../../../modules/folder"
|
||||
parent = "organizations/${var.organization.id}"
|
||||
name = "Tenants"
|
||||
tag_bindings = {
|
||||
context = module.organization.tag_values["context/tenant"].id
|
||||
}
|
||||
}
|
||||
|
||||
# Tenant folders (top, core, self)
|
||||
|
||||
module "tenant-top-folder" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
parent = module.tenant-tenants-folder.id
|
||||
name = each.value.descriptive_name
|
||||
group_iam = {
|
||||
(each.value.admin_group_email) = ["roles/browser"]
|
||||
}
|
||||
}
|
||||
|
||||
module "tenant-top-folder-iam" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
id = module.tenant-top-folder[each.key].id
|
||||
folder_create = false
|
||||
tag_bindings = {
|
||||
tenant = module.organization.tag_values["${var.tag_names.tenant}/${each.key}"].id
|
||||
}
|
||||
iam = merge(
|
||||
{
|
||||
"roles/cloudasset.owner" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/compute.xpnAdmin" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/logging.admin" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/resourcemanager.folderAdmin" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/resourcemanager.projectCreator" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/resourcemanager.tagUser" = [module.tenant-core-sa[each.key].iam_email]
|
||||
},
|
||||
{
|
||||
for k in var.tenants_config.top_folder_roles :
|
||||
k => local.tenant_iam[each.key]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module "tenant-core-folder" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
parent = module.tenant-top-folder[each.key].id
|
||||
name = "${each.value.descriptive_name} - Core"
|
||||
}
|
||||
|
||||
module "tenant-core-folder-iam" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
id = module.tenant-core-folder[each.key].id
|
||||
folder_create = false
|
||||
iam = merge(
|
||||
{
|
||||
"roles/owner" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/viewer" = local.tenant_iam[each.key]
|
||||
},
|
||||
{
|
||||
for k in var.tenants_config.core_folder_roles :
|
||||
k => local.tenant_iam[each.key]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module "tenant-self-folder" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
parent = module.tenant-top-folder[each.key].id
|
||||
name = "${each.value.descriptive_name} - Tenant"
|
||||
}
|
||||
|
||||
module "tenant-self-folder-iam" {
|
||||
source = "../../../modules/folder"
|
||||
for_each = var.tenants
|
||||
id = module.tenant-self-folder[each.key].id
|
||||
folder_create = false
|
||||
iam = merge(
|
||||
{
|
||||
"roles/cloudasset.owner" = local.tenant_iam[each.key]
|
||||
"roles/compute.xpnAdmin" = local.tenant_iam[each.key]
|
||||
"roles/resourcemanager.folderAdmin" = local.tenant_iam[each.key]
|
||||
"roles/resourcemanager.projectCreator" = local.tenant_iam[each.key]
|
||||
"roles/resourcemanager.tagUser" = local.tenant_iam[each.key]
|
||||
"roles/owner" = local.tenant_iam[each.key]
|
||||
},
|
||||
{
|
||||
for k in var.tenants_config.tenant_folder_roles :
|
||||
k => local.tenant_iam[each.key]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Tenant IaC resources (core)
|
||||
|
||||
module "tenant-core-sa" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
for_each = var.tenants
|
||||
project_id = var.automation.project_id
|
||||
name = "tn-${each.key}-0"
|
||||
description = "Terraform service account for tenant ${each.key}."
|
||||
prefix = var.prefix
|
||||
}
|
||||
|
||||
module "tenant-core-gcs" {
|
||||
source = "../../../modules/gcs"
|
||||
for_each = var.tenants
|
||||
project_id = var.automation.project_id
|
||||
name = "tn-${each.key}-0"
|
||||
prefix = var.prefix
|
||||
versioning = true
|
||||
location = var.locations.gcs
|
||||
storage_class = local.gcs_storage_class
|
||||
iam = {
|
||||
"roles/storage.objectAdmin" = [module.tenant-core-sa[each.key].iam_email]
|
||||
}
|
||||
}
|
||||
|
||||
# Tenant IaC project and resources (self)
|
||||
|
||||
module "tenant-self-iac-project" {
|
||||
source = "../../../modules/project"
|
||||
for_each = var.tenants
|
||||
billing_account = (
|
||||
each.value.billing_account != null
|
||||
? each.value.billing_account
|
||||
: var.billing_account.id
|
||||
)
|
||||
name = "${each.key}-iac-core-0"
|
||||
parent = module.tenant-self-folder[each.key].id
|
||||
prefix = var.prefix
|
||||
group_iam = {
|
||||
(each.value.admin_group_email) = [
|
||||
"roles/iam.serviceAccountAdmin",
|
||||
"roles/iam.serviceAccountTokenCreator",
|
||||
"roles/iam.workloadIdentityPoolAdmin"
|
||||
]
|
||||
}
|
||||
services = [
|
||||
"accesscontextmanager.googleapis.com",
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
"bigquerystorage.googleapis.com",
|
||||
"billingbudgets.googleapis.com",
|
||||
"cloudbilling.googleapis.com",
|
||||
"cloudbuild.googleapis.com",
|
||||
"cloudkms.googleapis.com",
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
"essentialcontacts.googleapis.com",
|
||||
"iam.googleapis.com",
|
||||
"iamcredentials.googleapis.com",
|
||||
"orgpolicy.googleapis.com",
|
||||
"pubsub.googleapis.com",
|
||||
"servicenetworking.googleapis.com",
|
||||
"serviceusage.googleapis.com",
|
||||
"sourcerepo.googleapis.com",
|
||||
"stackdriver.googleapis.com",
|
||||
"storage-component.googleapis.com",
|
||||
"storage.googleapis.com",
|
||||
"sts.googleapis.com"
|
||||
]
|
||||
}
|
||||
|
||||
module "tenant-self-iac-gcs-outputs" {
|
||||
source = "../../../modules/gcs"
|
||||
for_each = var.tenants
|
||||
project_id = module.tenant-self-iac-project[each.key].project_id
|
||||
location = var.locations.gcs
|
||||
storage_class = local.gcs_storage_class
|
||||
name = "${each.key}-iac-outputs-0"
|
||||
prefix = var.prefix
|
||||
versioning = true
|
||||
iam = {
|
||||
"roles/storage.objectAdmin" = [module.tenant-core-sa[each.key].iam_email]
|
||||
}
|
||||
}
|
||||
|
||||
module "tenant-self-iac-gcs-state" {
|
||||
source = "../../../modules/gcs"
|
||||
for_each = var.tenants
|
||||
project_id = module.tenant-self-iac-project[each.key].project_id
|
||||
location = var.locations.gcs
|
||||
storage_class = local.gcs_storage_class
|
||||
name = "${each.key}-iac-0"
|
||||
prefix = var.prefix
|
||||
versioning = true
|
||||
}
|
||||
|
||||
module "tenant-self-iac-sa" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
for_each = var.tenants
|
||||
project_id = module.tenant-self-iac-project[each.key].project_id
|
||||
name = "${each.key}-iac-0"
|
||||
description = "Terraform automation service account."
|
||||
prefix = var.prefix
|
||||
iam_storage_roles = {
|
||||
(module.tenant-self-iac-gcs-outputs[each.key].name) = [
|
||||
"roles/storage.admin"
|
||||
]
|
||||
(module.tenant-self-iac-gcs-state[each.key].name) = [
|
||||
"roles/storage.admin"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
locals {
|
||||
all_drs_domains = concat(
|
||||
[var.organization.customer_id],
|
||||
try(local.policy_configs.allowed_policy_member_domains, [])
|
||||
try(local.policy_configs.allowed_policy_member_domains, []),
|
||||
compact([for k, v in var.tenants : try(v.organization.customer_id, "")])
|
||||
)
|
||||
policy_configs = (
|
||||
var.organization_policy_configs == null
|
||||
|
@ -81,7 +82,6 @@ module "organization" {
|
|||
)
|
||||
} : {}
|
||||
)
|
||||
|
||||
# sample subset of useful organization policies, edit to suit requirements
|
||||
org_policies = {
|
||||
"iam.allowedPolicyMemberDomains" = {
|
||||
|
@ -101,7 +101,6 @@ module "organization" {
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
#"gcp.resourceLocations" = {
|
||||
# allow = { values = local.allowed_regions }
|
||||
# }
|
||||
|
@ -115,10 +114,8 @@ module "organization" {
|
|||
# }
|
||||
}
|
||||
org_policies_data_path = "${var.data_dir}/org-policies"
|
||||
|
||||
# do not assign tagViewer or tagUser roles here on tag keys and values as
|
||||
# they are managed authoritatively and will break multitenant stages
|
||||
|
||||
tags = merge(
|
||||
local.tags,
|
||||
{
|
||||
|
@ -132,6 +129,7 @@ module "organization" {
|
|||
sandbox = null
|
||||
security = null
|
||||
teams = null
|
||||
tenant = null
|
||||
}
|
||||
}
|
||||
(var.tag_names.environment) = {
|
||||
|
@ -154,6 +152,14 @@ module "organization" {
|
|||
}
|
||||
(var.tag_names.tenant) = {
|
||||
description = "Organization tenant."
|
||||
values = {
|
||||
for k, v in var.tenants : k => {
|
||||
description = v.descriptive_name
|
||||
iam = {
|
||||
"roles/resourcemanager.tagViewer" = local.tenant_iam[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
locals {
|
||||
tenant_core_providers = {
|
||||
for k, v in var.tenants :
|
||||
k => templatefile("${path.module}/templates/providers.tf.tpl", {
|
||||
bucket = module.tenant-core-gcs[k].name
|
||||
name = k
|
||||
sa = module.tenant-core-sa[k].email
|
||||
backend_extra = null
|
||||
})
|
||||
}
|
||||
tenant_self_providers = {
|
||||
for k, v in var.tenants :
|
||||
k => templatefile("${path.module}/templates/providers.tf.tpl", {
|
||||
bucket = module.tenant-self-iac-gcs-state[k].name
|
||||
name = k
|
||||
sa = module.tenant-self-iac-sa[k].email
|
||||
backend_extra = null
|
||||
})
|
||||
}
|
||||
tenant_tfvars = {
|
||||
for k, v in var.tenants : k => merge(v, {
|
||||
automation = {
|
||||
core_bucket = module.tenant-core-gcs[k].name
|
||||
core_sa = module.tenant-core-sa[k].email
|
||||
outputs_bucket = module.tenant-self-iac-gcs-outputs[k].name
|
||||
project_id = module.tenant-self-iac-project[k].project_id
|
||||
sa = module.tenant-self-iac-sa[k].email
|
||||
state_bucket = module.tenant-self-iac-gcs-state[k].name
|
||||
}
|
||||
core = {
|
||||
billing_account = var.billing_account
|
||||
organization = var.organization
|
||||
}
|
||||
folder_ids = {
|
||||
core = module.tenant-core-folder[k].id
|
||||
self = module.tenant-self-folder[k].id
|
||||
top = module.tenant-top-folder[k].id
|
||||
}
|
||||
shortname = k
|
||||
prefix = "${var.prefix}-${k}"
|
||||
tag_values = { for k, v in module.organization.tag_values : k => v.id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
# core tfvars and providers
|
||||
|
||||
resource "local_file" "tenant-core-tfvars" {
|
||||
for_each = var.outputs_location == null ? {} : local.tenant_tfvars
|
||||
file_permission = "0644"
|
||||
filename = (
|
||||
"${pathexpand(var.outputs_location)}/tfvars/tenant/${each.key}.auto.tfvars.json"
|
||||
)
|
||||
content = jsonencode(each.value)
|
||||
}
|
||||
|
||||
resource "local_file" "tenant-core-providers" {
|
||||
for_each = (
|
||||
var.outputs_location == null ? {} : local.tenant_core_providers
|
||||
)
|
||||
file_permission = "0644"
|
||||
filename = (
|
||||
"${pathexpand(var.outputs_location)}/providers/tenant/${each.key}-providers.tf"
|
||||
)
|
||||
content = each.value
|
||||
}
|
||||
|
||||
resource "google_storage_bucket_object" "tenant-core-tfvars" {
|
||||
for_each = local.tenant_tfvars
|
||||
bucket = var.automation.outputs_bucket
|
||||
name = "tfvars/tenant/${each.key}.auto.tfvars.json"
|
||||
content = jsonencode(each.value)
|
||||
}
|
||||
|
||||
resource "google_storage_bucket_object" "tenant-core-providers" {
|
||||
for_each = local.tenant_core_providers
|
||||
bucket = var.automation.outputs_bucket
|
||||
name = "providers/tenant/${each.key}-providers.tf"
|
||||
content = each.value
|
||||
}
|
||||
|
||||
# tenant tfvars and providers
|
||||
|
||||
resource "local_file" "tenant-self-providers" {
|
||||
for_each = (
|
||||
var.outputs_location == null ? {} : local.tenant_self_providers
|
||||
)
|
||||
file_permission = "0644"
|
||||
filename = (
|
||||
"${pathexpand(var.outputs_location)}/providers/tenant/${each.key}-self-providers.tf"
|
||||
)
|
||||
content = each.value
|
||||
}
|
||||
|
||||
resource "google_storage_bucket_object" "tenant-self-tfvars" {
|
||||
for_each = local.tenant_tfvars
|
||||
bucket = module.tenant-self-iac-gcs-outputs[each.key].name
|
||||
name = "tfvars/${each.key}.auto.tfvars.json"
|
||||
content = jsonencode(each.value)
|
||||
}
|
||||
|
||||
resource "google_storage_bucket_object" "tenant-self-providers" {
|
||||
for_each = local.tenant_self_providers
|
||||
bucket = module.tenant-self-iac-gcs-outputs[each.key].name
|
||||
name = "providers/${each.key}-providers.tf"
|
||||
content = each.value
|
||||
}
|
|
@ -274,3 +274,30 @@ variable "team_folders" {
|
|||
}))
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "tenants" {
|
||||
description = "Lightweight tenant definitions."
|
||||
type = map(object({
|
||||
admin_group_email = string
|
||||
descriptive_name = string
|
||||
billing_account = optional(string)
|
||||
organization = optional(object({
|
||||
customer_id = string
|
||||
domain = string
|
||||
id = number
|
||||
}))
|
||||
}))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "tenants_config" {
|
||||
description = "Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts."
|
||||
type = object({
|
||||
core_folder_roles = optional(list(string), [])
|
||||
tenant_folder_roles = optional(list(string), [])
|
||||
top_folder_roles = optional(list(string), [])
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue