diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 88bdceb3fb..20ed998f2c 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -69,8 +69,8 @@ One other design choice worth mentioning here is using a single automation proje We support three use cases in regards to billing: - the billing account is part of this same organization, IAM bindings will be set at the organization level -- the billing account is part of a different organization, billing IAM bindings will be set at the organization level in the billing account owning organization - the billing account is not considered part of an organization (even though it might be), billing IAM bindings are set on the billing account itself +- billing IAM is managed separately, and no bindings should (or can) be set via Terraform, this requires a few extra steps and is definitely not recommended and mainly used for development purposes For same-organization billing, we configure a custom organization role that can set IAM bindings, via a delegated role grant to limit its scope to the relevant roles. @@ -171,6 +171,18 @@ gcloud beta billing accounts add-iam-policy-binding $FAST_BILLING_ACCOUNT_ID \ --member user:$FAST_BU --role roles/billing.admin ``` +#### Preventing creation of billing-related IAM bindings + +This configuration is possible but unsupported and only present for development purposes, use at your own risk: + +- configure `billing_account.id` as `null` and `billing.no_iam` to `true` in your `tfvars` file +- apply with `terraform apply -target 'module.automation-project.google_project.project[0]'` in addition to the initial user variable +- once Terraform raises an error run `terraform untaint 'module.automation-project.google_project.project[0]'` +- repeat the two steps above for `'module.log-export-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- configure `billing_account.id` with the real billing account id +- resume applying normally + #### Groups Before the first run, the following IAM groups must exist to allow IAM bindings to be created (actual names are flexible, see the [Customization](#customizations) section): @@ -469,7 +481,16 @@ The remaining configuration is manual, as it regards the repositories themselves | name | description | modules | resources | |---|---|---|---| | [automation.tf](./automation.tf) | Automation project and resources. | gcs · iam-service-account · project | | -| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | + ) +} + +# billing account in same org (IAM is in the organization.tf file) + +module · ? local.billing_ext_admins : [] + ) + billing_account_id = var.billing_account.id + role = · google_billing_account_iam_member | | [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account · source-repository | | | [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider | | [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset · gcs · logging-bucket · project · pubsub | | @@ -484,21 +505,21 @@ 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. | object({…}) | ✓ | | | -| [organization](variables.tf#L196) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L211) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | -| [bootstrap_user](variables.tf#L29) | Email of the nominal user running this stage for the first time. | string | | null | | -| [cicd_repositories](variables.tf#L35) | 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. | object({…}) | | null | | -| [custom_role_names](variables.tf#L81) | Names of custom roles defined at the org level. | object({…}) | | {…} | | -| [fast_features](variables.tf#L95) | Selective control for top-level FAST features. | object({…}) | | {} | | -| [federated_identity_providers](variables.tf#L108) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [groups](variables.tf#L122) | Group names to grant organization-level permissions. | map(string) | | {…} | | -| [iam](variables.tf#L140) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_additive](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | -| [locations](variables.tf#L152) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | | -| [log_sinks](variables.tf#L171) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [outputs_location](variables.tf#L205) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L221) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | +| [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`. | object({…}) | ✓ | | | +| [organization](variables.tf#L194) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L209) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | string | | null | | +| [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. | object({…}) | | null | | +| [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | object({…}) | | {…} | | +| [fast_features](variables.tf#L93) | Selective control for top-level FAST features. | object({…}) | | {} | | +| [federated_identity_providers](variables.tf#L106) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [groups](variables.tf#L120) | Group names to grant organization-level permissions. | map(string) | | {…} | | +| [iam](variables.tf#L138) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_additive](variables.tf#L144) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | | +| [locations](variables.tf#L150) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | | +| [log_sinks](variables.tf#L169) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [outputs_location](variables.tf#L203) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L219) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | ## Outputs diff --git a/fast/stages/0-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf index aee033bd8a..203ecbe865 100644 --- a/fast/stages/0-bootstrap/billing.tf +++ b/fast/stages/0-bootstrap/billing.tf @@ -24,13 +24,18 @@ locals { module.automation-tf-bootstrap-sa.iam_email, module.automation-tf-resman-sa.iam_email ] + billing_mode = ( + var.billing_account.no_iam + ? null + : var.billing_account.is_org_level ? "org" : "resource" + ) } # billing account in same org (IAM is in the organization.tf file) module "billing-export-project" { source = "../../../modules/project" - count = var.billing_account.is_org_level ? 1 : 0 + count = local.billing_mode == "org" ? 1 : 0 billing_account = var.billing_account.id name = "billing-exp-0" parent = coalesce( @@ -52,7 +57,7 @@ module "billing-export-project" { module "billing-export-dataset" { source = "../../../modules/bigquery-dataset" - count = var.billing_account.is_org_level ? 1 : 0 + count = local.billing_mode == "org" ? 1 : 0 project_id = module.billing-export-project.0.project_id id = "billing_export" friendly_name = "Billing export." @@ -63,7 +68,7 @@ module "billing-export-dataset" { resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_admins : [] + local.billing_mode == "resource" ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.admin" @@ -72,7 +77,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_cost_manager" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_admins : [] + local.billing_mode == "resource" ? local.billing_ext_admins : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index 154b37fe59..d75a25f2ff 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -85,7 +85,7 @@ locals { # "domain:${var.organization.domain}" # ] }, - var.billing_account.is_org_level ? { + local.billing_mode == "org" ? { "roles/billing.admin" = [ local.groups_iam.gcp-billing-admins, local.groups_iam.gcp-organization-admins, @@ -222,7 +222,7 @@ resource "google_organization_iam_binding" "org_admin_delegated" { "roles/resourcemanager.organizationViewer", module.organization.custom_role_id[var.custom_role_names.tenant_network_admin] ], - var.billing_account.is_org_level ? [ + local.billing_mode == "org" ? [ "roles/billing.admin", "roles/billing.costsManager", "roles/billing.user", diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 4ab90debac..7aec437c7a 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -15,15 +15,13 @@ */ variable "billing_account" { - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "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`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "bootstrap_user" { diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 971c69633d..781dc14d30 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -174,7 +174,18 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | modules | resources | |---|---|---|---| -| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member | +| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | + ) +} + +# billing account in same org (resources is in the organization.tf file) + +# standalone billing account + +resource · ? local.billing_ext_users : [] + ) + billing_account_id = var.billing_account.id + role = · google_billing_account_iam_member | | [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | google_organization_iam_member | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | | @@ -199,19 +210,19 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | -| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L193) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [cicd_repositories](variables.tf#L51) | 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. | object({…}) | | null | | -| [custom_roles](variables.tf#L133) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [data_dir](variables.tf#L142) | Relative path for the folder storing configuration data. | string | | "data" | | -| [fast_features](variables.tf#L148) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | -| [groups](variables.tf#L162) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | -| [locations](variables.tf#L175) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | -| [organization_policy_configs](variables.tf#L203) | Organization policies customization. | object({…}) | | null | | -| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [tag_names](variables.tf#L228) | Customized names for resource management tags. | object({…}) | | {…} | | -| [team_folders](variables.tf#L247) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [billing_account](variables.tf#L38) | 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`. | object({…}) | ✓ | | 0-bootstrap | +| [organization](variables.tf#L191) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L215) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [cicd_repositories](variables.tf#L49) | 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. | object({…}) | | null | | +| [custom_roles](variables.tf#L131) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [data_dir](variables.tf#L140) | Relative path for the folder storing configuration data. | string | | "data" | | +| [fast_features](variables.tf#L146) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | +| [groups](variables.tf#L160) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap | +| [locations](variables.tf#L173) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | +| [organization_policy_configs](variables.tf#L201) | Organization policies customization. | object({…}) | | null | | +| [outputs_location](variables.tf#L209) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [tag_names](variables.tf#L226) | Customized names for resource management tags. | object({…}) | | {…} | | +| [team_folders](variables.tf#L245) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | ## Outputs diff --git a/fast/stages/1-resman/billing.tf b/fast/stages/1-resman/billing.tf index ba20ab0534..c18708424a 100644 --- a/fast/stages/1-resman/billing.tf +++ b/fast/stages/1-resman/billing.tf @@ -30,6 +30,11 @@ locals { local.branch_optional_sa_lists.pf-dev, local.branch_optional_sa_lists.pf-prod, ) + billing_mode = ( + var.billing_account.no_iam + ? null + : var.billing_account.is_org_level ? "org" : "resource" + ) } # billing account in same org (resources is in the organization.tf file) @@ -38,7 +43,7 @@ locals { resource "google_billing_account_iam_member" "billing_ext_admin" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_users : [] + local.billing_mode == "resource" ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.user" @@ -47,7 +52,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" { resource "google_billing_account_iam_member" "billing_ext_costsmanager" { for_each = toset( - !var.billing_account.is_org_level ? local.billing_ext_users : [] + local.billing_mode == "resource" ? local.billing_ext_users : [] ) billing_account_id = var.billing_account.id role = "roles/billing.costsManager" diff --git a/fast/stages/1-resman/organization.tf b/fast/stages/1-resman/organization.tf index a3bc2f0de0..4d5e3fa57d 100644 --- a/fast/stages/1-resman/organization.tf +++ b/fast/stages/1-resman/organization.tf @@ -46,7 +46,7 @@ module "organization" { module.branch-network-sa.iam_email ] }, - var.billing_account.is_org_level ? { + local.billing_mode == "org" ? { "roles/billing.costsManager" = concat( local.branch_optional_sa_lists.pf-dev, local.branch_optional_sa_lists.pf-prod diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index 16c65ee83e..f2e413c981 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -37,15 +37,13 @@ variable "automation" { variable "billing_account" { # tfdoc:variable:source 0-bootstrap - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + description = "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`." type = object({ id = string is_org_level = optional(bool, true) + no_iam = optional(bool, false) }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } + nullable = false } variable "cicd_repositories" { diff --git a/fast/stages/2-networking-a-peering/README.md b/fast/stages/2-networking-a-peering/README.md index c066423cdd..ac282c8d32 100644 --- a/fast/stages/2-networking-a-peering/README.md +++ b/fast/stages/2-networking-a-peering/README.md @@ -250,6 +250,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-b-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md index 7a8983d81b..a34b41355d 100644 --- a/fast/stages/2-networking-b-vpn/README.md +++ b/fast/stages/2-networking-b-vpn/README.md @@ -264,6 +264,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-c-nva/README.md b/fast/stages/2-networking-c-nva/README.md index d0e62fd62a..33501984d9 100644 --- a/fast/stages/2-networking-c-nva/README.md +++ b/fast/stages/2-networking-c-nva/README.md @@ -323,6 +323,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `landing-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.landing-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.landing-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-networking-d-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md index dfc199cd0f..3a3b70e7ec 100644 --- a/fast/stages/2-networking-d-separate-envs/README.md +++ b/fast/stages/2-networking-d-separate-envs/README.md @@ -212,6 +212,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-spoke-project`, `prod-spoke-project`) + - apply using `-target`, for example + `terraform apply -target 'module.prod-spoke-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.prod-spoke-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md index 6486cd7418..ced74136ad 100644 --- a/fast/stages/2-security/README.md +++ b/fast/stages/2-security/README.md @@ -108,6 +108,20 @@ Variables in this stage -- like most other FAST stages -- are broadly divided in The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. +### Using delayed billing association for projects + +This configuration is possible but unsupported and only exists for development purposes, use at your own risk: + +- temporarily switch `billing_account.id` to `null` in `globals.auto.tfvars.json` +- for each project resources in the project modules used in this stage (`dev-sec-project`, `prod-sec-project`) + - apply using `-target`, for example + `terraform apply -target 'module.prod-sec-project.google_project.project[0]'` + - untaint the project resource after applying, for example + `terraform untaint 'module.prod-sec-project.google_project.project[0]'` +- go through the process to associate the billing account with the two projects +- switch `billing_account.id` back to the real billing account id +- resume applying normally + ### Running the stage Once provider and variable values are in place and the correct user is configured, the stage can be run: