Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable team CI/CD impersonation #1579

Merged
merged 7 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions fast/stages/1-resman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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> |
Expand All @@ -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 -->
11 changes: 7 additions & 4 deletions fast/stages/1-resman/branch-teams.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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}"]
)
)
}
}
Expand Down
93 changes: 93 additions & 0 deletions fast/stages/1-resman/cicd-teams.tf
Original file line number Diff line number Diff line change
@@ -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"]
}
}
15 changes: 15 additions & 0 deletions fast/stages/1-resman/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion fast/stages/1-resman/outputs-files.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion fast/stages/1-resman/outputs-gcs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions fast/stages/1-resman/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down
6 changes: 6 additions & 0 deletions fast/stages/1-resman/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down