Enable team CI/CD impersonation (#1579)

This commit is contained in:
Matt 2023-08-09 08:46:24 -04:00 committed by GitHub
parent 79373721df
commit 9600047a32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 19 deletions

View File

@ -352,6 +352,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-teams.tf](./cicd-teams.tf) | CI/CD resources for individual teams. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [main.tf](./main.tf) | Module-level locals and resources. | | |
| [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> |
@ -378,22 +379,23 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [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> | |
| [tag_names](variables.tf#L227) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; org-policies &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10; org-policies &#61; &#34;org-policies&#34;&#10; tenant &#61; &#34;tenant&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
| [tenants](variables.tf#L279) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [tenants_config](variables.tf#L295) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10; cicd &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
| [tenants](variables.tf#L285) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [tenants_config](variables.tf#L301) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
| [cicd_repositories](outputs.tf#L213) | WIF configuration for CI/CD repositories. | | |
| [dataplatform](outputs.tf#L227) | Data for the Data Platform stage. | | |
| [gke_multitenant](outputs.tf#L243) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
| [networking](outputs.tf#L264) | Data for the networking stage. | | |
| [project_factories](outputs.tf#L273) | Data for the project factories stage. | | |
| [providers](outputs.tf#L288) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
| [sandbox](outputs.tf#L295) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
| [security](outputs.tf#L309) | Data for the networking stage. | | <code>02-security</code> |
| [teams](outputs.tf#L319) | Data for the teams stage. | | |
| [tfvars](outputs.tf#L331) | Terraform variable files for the following stages. | ✓ | |
| [cicd_repositories](outputs.tf#L232) | WIF configuration for CI/CD repositories. | | |
| [dataplatform](outputs.tf#L246) | Data for the Data Platform stage. | | |
| [gke_multitenant](outputs.tf#L262) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
| [networking](outputs.tf#L283) | Data for the networking stage. | | |
| [project_factories](outputs.tf#L292) | Data for the project factories stage. | | |
| [providers](outputs.tf#L307) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
| [sandbox](outputs.tf#L314) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
| [security](outputs.tf#L328) | Data for the networking stage. | | <code>02-security</code> |
| [team_cicd_repositories](outputs.tf#L338) | WIF configuration for Team CI/CD repositories. | | |
| [teams](outputs.tf#L352) | Data for the teams stage. | | |
| [tfvars](outputs.tf#L364) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC -->

View File

@ -90,10 +90,13 @@ module "branch-teams-team-sa" {
display_name = "Terraform team ${each.key} service account."
prefix = var.prefix
iam = {
"roles/iam.serviceAccountTokenCreator" = (
each.value.impersonation_groups == null
? []
: [for g in each.value.impersonation_groups : "group:${g}"]
"roles/iam.serviceAccountTokenCreator" = concat(
compact([try(module.branch-teams-team-sa-cicd[each.key].iam_email, null)]),
(
each.value.impersonation_groups == null
? []
: [for g in each.value.impersonation_groups : "group:${g}"]
)
)
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright 2022 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 CI/CD resources for individual teams.
# source repository
module "branch-teams-team-cicd-repo" {
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/source-repository?ref=v24.0.0"
for_each = {
for k, v in coalesce(local.team_cicd_repositories, {}) : k => v
if v.cicd.type == "sourcerepo"
}
project_id = var.automation.project_id
name = each.value.cicd.name
iam = {
"roles/source.admin" = [module.branch-teams-team-sa[each.key].iam_email]
"roles/source.reader" = [module.branch-teams-team-sa-cicd[each.key].iam_email]
}
triggers = {
"fast-03-team-${each.key}" = {
filename = ".cloudbuild/workflow.yaml"
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
service_account = module.branch-teams-team-sa-cicd[each.key].id
substitutions = {}
template = {
project_id = null
branch_name = each.value.cicd.branch
repo_name = each.value.cicd.name
tag_name = null
}
}
}
depends_on = [module.branch-teams-team-sa-cicd]
}
# SA used by CI/CD workflows to impersonate automation SAs
module "branch-teams-team-sa-cicd" {
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/iam-service-account?ref=v24.0.0"
for_each = (
try(local.team_cicd_repositories, null) != null
? local.team_cicd_repositories
: {}
)
project_id = var.automation.project_id
name = "prod-teams-${each.key}-1"
display_name = "Terraform CI/CD team ${each.key} service account."
prefix = var.prefix
iam = (
each.value.cicd.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
"roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
"roles/iam.workloadIdentityUser" = [
each.value.cicd.branch == null
? format(
local.identity_providers[each.value.cicd.identity_provider].principalset_tpl,
var.automation.federated_identity_pool,
each.value.cicd.name
)
: format(
local.identity_providers[each.value.cicd.identity_provider].principal_tpl,
var.automation.federated_identity_pool,
each.value.cicd.name,
each.value.cicd.branch
)
]
}
)
iam_project_roles = {
(var.automation.project_id) = ["roles/logging.logWriter"]
}
iam_storage_roles = {
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
}
}

View File

@ -47,6 +47,21 @@ locals {
fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
)
}
team_cicd_repositories = {
for k, v in coalesce(var.team_folders, {}) : k => v
if(
v != null &&
(
try(v.cicd.type, null) == "sourcerepo"
||
contains(
keys(local.identity_providers),
coalesce(try(v.cicd.identity_provider, null), ":")
)
) &&
fileexists("${path.module}/templates/workflow-${try(v.cicd.type, "")}.yaml")
)
}
cicd_workflow_var_files = {
stage_2 = [
"0-bootstrap.auto.tfvars.json",

View File

@ -35,7 +35,7 @@ resource "local_file" "tfvars" {
}
resource "local_file" "workflows" {
for_each = var.outputs_location == null ? {} : local.cicd_workflows
for_each = var.outputs_location == null ? {} : merge(local.cicd_workflows, local.team_cicd_workflows)
file_permission = "0644"
filename = "${local.outputs_location}/workflows/${replace(each.key, "_", "-")}-workflow.yaml"
content = try(each.value, null)

View File

@ -30,7 +30,7 @@ resource "google_storage_bucket_object" "tfvars" {
}
resource "google_storage_bucket_object" "workflows" {
for_each = local.cicd_workflows
for_each = merge(local.cicd_workflows, local.team_cicd_workflows)
bucket = var.automation.outputs_bucket
name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml"
content = each.value

View File

@ -201,6 +201,25 @@ locals {
for k, v in module.branch-teams-team-sa : "team-${k}" => v.email
},
)
team_cicd_workflows = {
for k, v in local.team_cicd_repositories : k => templatefile(
"${path.module}/templates/workflow-${v.cicd.type}.yaml",
merge(local.team_cicd_workflow_attrs[k], {
identity_provider = try(
local.identity_providers[v.cicd.identity_provider].name, null
)
outputs_bucket = var.automation.outputs_bucket
stage_name = k
})
)
}
team_cicd_workflow_attrs = {
for k, v in local.team_cicd_repositories : k => {
service_account = try(module.branch-teams-team-sa-cicd[k].email, null)
tf_providers_file = "3-teams-${k}-providers.tf"
tf_var_files = local.cicd_workflow_var_files.stage_3
}
}
tfvars = {
folder_ids = local.folder_ids
service_accounts = local.service_accounts
@ -316,6 +335,20 @@ output "security" {
}
}
output "team_cicd_repositories" {
description = "WIF configuration for Team CI/CD repositories."
value = {
for k, v in local.team_cicd_repositories : k => {
branch = v.cicd.branch
name = v.cicd.name
provider = try(
local.identity_providers[v.cicd.identity_provider].name, null
)
service_account = local.team_cicd_workflow_attrs[k].service_account
} if v.cicd != null
}
}
output "teams" {
description = "Data for the teams stage."
value = {

View File

@ -272,6 +272,12 @@ variable "team_folders" {
descriptive_name = string
group_iam = map(list(string))
impersonation_groups = list(string)
cicd = optional(object({
branch = string
identity_provider = string
name = string
type = string
}))
}))
default = null
}