From c8214434b77621693734431306aca1264e6647d9 Mon Sep 17 00:00:00 2001 From: Ludo Date: Tue, 21 May 2024 09:36:19 +0200 Subject: [PATCH 1/2] add support for tenant factory CI/CD --- fast/stages/0-bootstrap/README.md | 56 ++++++++++----------- fast/stages/0-bootstrap/automation.tf | 22 ++++++-- fast/stages/0-bootstrap/cicd.tf | 6 +++ fast/stages/0-bootstrap/organization-iam.tf | 2 +- fast/stages/0-bootstrap/outputs.tf | 14 ++---- fast/stages/0-bootstrap/variables.tf | 6 +++ 6 files changed, 64 insertions(+), 42 deletions(-) diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 50d0a2853d..8ac0b9f6b4 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -631,38 +631,38 @@ 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`. | object({…}) | ✓ | | | -| [organization](variables.tf#L228) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L243) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [organization](variables.tf#L234) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L249) | 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_roles](variables.tf#L79) | Map of role names => list of permissions to additionally create at the organization level. | map(list(string)) | | {} | | -| [essential_contacts](variables.tf#L86) | Email used for essential contacts, unset if null. | string | | null | | -| [factories_config](variables.tf#L92) | Configuration for the resource factories or external data. | object({…}) | | {} | | -| [groups](variables.tf#L104) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | -| [iam](variables.tf#L120) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_bindings_additive](variables.tf#L127) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | -| [iam_by_principals](variables.tf#L142) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | -| [locations](variables.tf#L149) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | -| [log_sinks](variables.tf#L163) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [org_policies_config](variables.tf#L211) | Organization policies customization. | object({…}) | | {} | | -| [outputs_location](variables.tf#L237) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L252) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | -| [workforce_identity_providers](variables.tf#L263) | Workforce Identity Federation pools. | map(object({…})) | | {} | | -| [workload_identity_providers](variables.tf#L279) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [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_roles](variables.tf#L85) | Map of role names => list of permissions to additionally create at the organization level. | map(list(string)) | | {} | | +| [essential_contacts](variables.tf#L92) | Email used for essential contacts, unset if null. | string | | null | | +| [factories_config](variables.tf#L98) | Configuration for the resource factories or external data. | object({…}) | | {} | | +| [groups](variables.tf#L110) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | +| [iam](variables.tf#L126) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_bindings_additive](variables.tf#L133) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | +| [iam_by_principals](variables.tf#L148) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | +| [locations](variables.tf#L155) | 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({…})) | | {…} | | +| [org_policies_config](variables.tf#L217) | Organization policies customization. | object({…}) | | {} | | +| [outputs_location](variables.tf#L243) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L258) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | +| [workforce_identity_providers](variables.tf#L269) | Workforce Identity Federation pools. | map(object({…})) | | {} | | +| [workload_identity_providers](variables.tf#L285) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [automation](outputs.tf#L129) | Automation resources. | | | -| [billing_dataset](outputs.tf#L134) | BigQuery dataset prepared for billing export. | | | -| [cicd_repositories](outputs.tf#L139) | CI/CD repository configurations. | | | -| [custom_roles](outputs.tf#L151) | Organization-level custom roles. | | | -| [outputs_bucket](outputs.tf#L156) | GCS bucket where generated output files are stored. | | | -| [project_ids](outputs.tf#L161) | Projects created by this stage. | | | -| [providers](outputs.tf#L171) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | -| [service_accounts](outputs.tf#L178) | Automation service accounts created by this stage. | | | -| [tfvars](outputs.tf#L196) | Terraform variable files for the following stages. | ✓ | | -| [workforce_identity_pool](outputs.tf#L202) | Workforce Identity Federation pool. | | | -| [workload_identity_pool](outputs.tf#L211) | Workload Identity Federation pool and providers. | | | +| [automation](outputs.tf#L125) | Automation resources. | | | +| [billing_dataset](outputs.tf#L130) | BigQuery dataset prepared for billing export. | | | +| [cicd_repositories](outputs.tf#L135) | CI/CD repository configurations. | | | +| [custom_roles](outputs.tf#L147) | Organization-level custom roles. | | | +| [outputs_bucket](outputs.tf#L152) | GCS bucket where generated output files are stored. | | | +| [project_ids](outputs.tf#L157) | Projects created by this stage. | | | +| [providers](outputs.tf#L167) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | +| [service_accounts](outputs.tf#L174) | Automation service accounts created by this stage. | | | +| [tfvars](outputs.tf#L192) | Terraform variable files for the following stages. | ✓ | | +| [workforce_identity_pool](outputs.tf#L198) | Workforce Identity Federation pool. | | | +| [workload_identity_pool](outputs.tf#L207) | Workload Identity Federation pool and providers. | | | diff --git a/fast/stages/0-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf index 8463ff16b6..a4648a9e35 100644 --- a/fast/stages/0-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -17,8 +17,10 @@ # tfdoc:file:description Automation project and resources. locals { - cicd_resman_sa = try(module.automation-tf-cicd-sa["resman"].iam_email, "") - cicd_resman_r_sa = try(module.automation-tf-cicd-r-sa["resman"].iam_email, "") + cicd_resman_sa = try(module.automation-tf-cicd-sa["resman"].iam_email, "") + cicd_resman_r_sa = try(module.automation-tf-cicd-r-sa["resman"].iam_email, "") + cicd_tenants_sa = try(module.automation-tf-cicd-sa["tenants"].iam_email, "") + cicd_tenants_r_sa = try(module.automation-tf-cicd-r-sa["tenants"].iam_email, "") } module "automation-project" { @@ -269,12 +271,18 @@ module "automation-tf-resman-sa" { prefix = local.prefix # allow SA used by CI/CD workflow to impersonate this SA # we use additive IAM to allow tenant CI/CD SAs to impersonate it - iam_bindings_additive = ( + iam_bindings_additive = merge( local.cicd_resman_sa == "" ? {} : { cicd_token_creator = { member = local.cicd_resman_sa role = "roles/iam.serviceAccountTokenCreator" } + }, + local.cicd_tenants_sa == "" ? {} : { + cicd_token_creator = { + member = local.cicd_tenants_sa + role = "roles/iam.serviceAccountTokenCreator" + } } ) iam_storage_roles = { @@ -290,12 +298,18 @@ module "automation-tf-resman-r-sa" { prefix = local.prefix # allow SA used by CI/CD workflow to impersonate this SA # we use additive IAM to allow tenant CI/CD SAs to impersonate it - iam_bindings_additive = ( + iam_bindings_additive = merge( local.cicd_resman_r_sa == "" ? {} : { cicd_token_creator = { member = local.cicd_resman_r_sa role = "roles/iam.serviceAccountTokenCreator" } + }, + local.cicd_tenants_r_sa == "" ? {} : { + cicd_token_creator = { + member = local.cicd_tenants_r_sa + role = "roles/iam.serviceAccountTokenCreator" + } } ) # we grant organization roles here as IAM bindings have precedence over diff --git a/fast/stages/0-bootstrap/cicd.tf b/fast/stages/0-bootstrap/cicd.tf index abd857f343..c276171ab5 100644 --- a/fast/stages/0-bootstrap/cicd.tf +++ b/fast/stages/0-bootstrap/cicd.tf @@ -55,6 +55,8 @@ locals { bootstrap_r = "0-bootstrap-r-providers.tf" resman = "1-resman-providers.tf" resman_r = "1-resman-r-providers.tf" + tenants = "1-tenant-factory-providers.tf" + tenants_r = "1-tenant-factory-r-providers.tf" } cicd_workflow_var_files = { bootstrap = [] @@ -62,6 +64,10 @@ locals { "0-bootstrap.auto.tfvars.json", "0-globals.auto.tfvars.json" ] + tenants = [ + "0-bootstrap.auto.tfvars.json", + "0-globals.auto.tfvars.json" + ] } } diff --git a/fast/stages/0-bootstrap/organization-iam.tf b/fast/stages/0-bootstrap/organization-iam.tf index 2b22c75fb9..be2d507e5a 100644 --- a/fast/stages/0-bootstrap/organization-iam.tf +++ b/fast/stages/0-bootstrap/organization-iam.tf @@ -99,6 +99,7 @@ locals { (module.automation-tf-bootstrap-sa.iam_email) = { authoritative = [ "roles/essentialcontacts.admin", + "roles/iam.workforcePoolAdmin", "roles/logging.admin", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator", @@ -108,7 +109,6 @@ locals { additive = concat( [ "roles/iam.organizationRoleAdmin", - "roles/iam.workforcePoolAdmin", "roles/orgpolicy.policyAdmin" ], local.billing_mode != "org" ? [] : [ diff --git a/fast/stages/0-bootstrap/outputs.tf b/fast/stages/0-bootstrap/outputs.tf index 6adaef294b..4e124a3a49 100644 --- a/fast/stages/0-bootstrap/outputs.tf +++ b/fast/stages/0-bootstrap/outputs.tf @@ -73,15 +73,11 @@ locals { name = "tenant-factory" sa = module.automation-tf-resman-sa.email }) - "0-bootstrap-tenant" = templatefile(local._tpl_providers, { - backend_extra = join("\n", [ - "# remove the newline between quotes and set the tenant name as prefix", - "prefix = \"", - "\"" - ]) - bucket = module.automation-tf-resman-gcs.name - name = "bootstrap-tenant" - sa = module.automation-tf-resman-sa.email + "1-tenant-factory-r" = templatefile(local._tpl_providers, { + backend_extra = "prefix = \"tenant-factory\"" + bucket = module.automation-tf-resman-gcs.name + name = "tenant-factory" + sa = module.automation-tf-resman-r-sa.email }) } tfvars = { diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index e90992c3ee..b4b698a315 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -45,6 +45,12 @@ variable "cicd_repositories" { branch = optional(string) identity_provider = optional(string) })) + tenants = optional(object({ + name = string + type = string + branch = optional(string) + identity_provider = optional(string) + })) }) default = null validation { From c2cefd93def58087e634f24da5ee71646dcdde6c Mon Sep 17 00:00:00 2001 From: Ludo Date: Tue, 21 May 2024 09:47:43 +0200 Subject: [PATCH 2/2] fix inventories --- tests/fast/stages/s0_bootstrap/checklist.yaml | 10 +++------- tests/fast/stages/s0_bootstrap/simple.yaml | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index ffa04c1757..0afc1ba30c 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -96,6 +96,7 @@ values: condition: [ ] members: - group:gcp-organization-admins@fast.example.com + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com org_id: '123456789012' role: roles/iam.workforcePoolAdmin module.organization.google_organization_iam_binding.authoritative["roles/logging.admin"]: @@ -301,11 +302,6 @@ values: member: serviceAccount:fast-prod-bootstrap-0r@fast-prod-iac-core-0.iam.gserviceaccount.com org_id: '123456789012' role: roles/iam.organizationRoleViewer - ? module.organization.google_organization_iam_member.bindings["roles/iam.workforcePoolAdmin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"] - : condition: [ ] - member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/iam.workforcePoolAdmin ? module.organization.google_organization_iam_member.bindings["roles/iam.workforcePoolViewer-serviceAccount:fast-prod-bootstrap-0r@fast-prod-iac-core-0.iam.gserviceaccount.com"] : condition: [ ] member: serviceAccount:fast-prod-bootstrap-0r@fast-prod-iac-core-0.iam.gserviceaccount.com @@ -382,7 +378,7 @@ counts: google_org_policy_policy: 22 google_organization_iam_binding: 28 google_organization_iam_custom_role: 7 - google_organization_iam_member: 38 + google_organization_iam_member: 37 google_project: 3 google_project_iam_audit_config: 1 google_project_iam_binding: 19 @@ -399,4 +395,4 @@ counts: google_tags_tag_key: 1 google_tags_tag_value: 1 modules: 18 - resources: 207 + resources: 206 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index 89de01c2db..d7b4ddbac6 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -45,7 +45,7 @@ counts: google_org_policy_policy: 22 google_organization_iam_binding: 28 google_organization_iam_custom_role: 7 - google_organization_iam_member: 25 + google_organization_iam_member: 24 google_project: 3 google_project_iam_audit_config: 1 google_project_iam_binding: 19 @@ -63,7 +63,7 @@ counts: google_tags_tag_value: 1 local_file: 8 modules: 17 - resources: 199 + resources: 198 outputs: custom_roles: