Enable team CI/CD impersonation (#1579)
This commit is contained in:
parent
79373721df
commit
9600047a32
|
@ -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({ 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#L248) | 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#L269) | 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#L279) | 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#L295) | 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> | |
|
||||
| [team_folders](variables.tf#L269) | 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) cicd = optional(object({ branch = string identity_provider = string name = string type = string })) }))">map(object({…}))</code> | | <code>null</code> | |
|
||||
| [tenants](variables.tf#L285) | 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#L301) | 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
|
||||
|
||||
| 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 -->
|
||||
|
|
|
@ -90,11 +90,14 @@ module "branch-teams-team-sa" {
|
|||
display_name = "Terraform team ${each.key} service account."
|
||||
prefix = var.prefix
|
||||
iam = {
|
||||
"roles/iam.serviceAccountTokenCreator" = (
|
||||
"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}"]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue