From 7ce4dff6ef1a774c7bf8b25e2fbf9fccbd3c9176 Mon Sep 17 00:00:00 2001 From: Yvonne Yao Date: Sun, 30 Aug 2026 20:17:08 +1000 Subject: [PATCH] infra(ST-30): allow WIF repository transition --- infra/terraform/bootstrap/identity/main.tf | 15 ++++----- .../modules/base/github_oidc_provider/main.tf | 4 +-- .../base/github_oidc_provider/outputs.tf | 1 + .../base/github_oidc_provider/variables.tf | 2 +- .../foundations/bootstrap_identity/main.tf | 18 +++++++---- .../foundations/bootstrap_identity/outputs.tf | 1 + .../tests/bootstrap_identity.tftest.hcl | 31 +++++++++++++++---- .../bootstrap_identity/variables.tf | 1 + 8 files changed, 51 insertions(+), 22 deletions(-) diff --git a/infra/terraform/bootstrap/identity/main.tf b/infra/terraform/bootstrap/identity/main.tf index 9d596b2..72563a9 100644 --- a/infra/terraform/bootstrap/identity/main.tf +++ b/infra/terraform/bootstrap/identity/main.tf @@ -20,11 +20,12 @@ provider "google" { project = local.project.project_id } module "bootstrap_identity" { source = "../../modules/foundations/bootstrap_identity" - project_id = local.project.project_id - github_owner = split("/", local.project.github_repository)[0] - github_repo = split("/", local.project.github_repository)[1] - allowed_ref = "refs/heads/main" - environment = local.environment.environment - service_account_id = "github-terraform-sandbox" - project_roles = var.project_roles + project_id = local.project.project_id + github_owner = split("/", local.project.github_repository)[0] + github_repo = split("/", local.project.github_repository)[1] + github_repositories = toset([local.project.github_repository, "sailing-together/MemoryDirector"]) + allowed_ref = "refs/heads/main" + environment = local.environment.environment + service_account_id = "github-terraform-sandbox" + project_roles = var.project_roles } diff --git a/infra/terraform/modules/base/github_oidc_provider/main.tf b/infra/terraform/modules/base/github_oidc_provider/main.tf index 02e3407..f9eb695 100644 --- a/infra/terraform/modules/base/github_oidc_provider/main.tf +++ b/infra/terraform/modules/base/github_oidc_provider/main.tf @@ -7,7 +7,7 @@ resource "google_iam_workload_identity_pool" "github" { project = var.project_id workload_identity_pool_id = var.pool_id display_name = "GitHub Actions" - description = "GitHub Actions OIDC identities for ${var.github_repository}." + description = "GitHub Actions OIDC identities for ${join(", ", tolist(var.github_repositories))}." } resource "google_iam_workload_identity_pool_provider" "github" { @@ -15,7 +15,7 @@ resource "google_iam_workload_identity_pool_provider" "github" { workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id workload_identity_pool_provider_id = "github" display_name = "GitHub Actions OIDC" - attribute_condition = "assertion.repository == '${var.github_repository}' && assertion.ref == '${var.allowed_ref}' && assertion.environment == '${var.environment}'" + attribute_condition = "assertion.repository in ${jsonencode(tolist(var.github_repositories))} && assertion.ref == '${var.allowed_ref}' && assertion.environment == '${var.environment}'" attribute_mapping = { "google.subject" = "assertion.sub" "attribute.repository" = "assertion.repository" diff --git a/infra/terraform/modules/base/github_oidc_provider/outputs.tf b/infra/terraform/modules/base/github_oidc_provider/outputs.tf index bf51846..fc23722 100644 --- a/infra/terraform/modules/base/github_oidc_provider/outputs.tf +++ b/infra/terraform/modules/base/github_oidc_provider/outputs.tf @@ -1,2 +1,3 @@ output "name" { value = google_iam_workload_identity_pool.github.name } output "provider_name" { value = google_iam_workload_identity_pool_provider.github.name } +output "attribute_condition" { value = google_iam_workload_identity_pool_provider.github.attribute_condition } diff --git a/infra/terraform/modules/base/github_oidc_provider/variables.tf b/infra/terraform/modules/base/github_oidc_provider/variables.tf index 6b4450e..76e69cf 100644 --- a/infra/terraform/modules/base/github_oidc_provider/variables.tf +++ b/infra/terraform/modules/base/github_oidc_provider/variables.tf @@ -1,5 +1,5 @@ variable "project_id" { type = string } variable "pool_id" { type = string } -variable "github_repository" { type = string } +variable "github_repositories" { type = set(string) } variable "allowed_ref" { type = string } variable "environment" { type = string } diff --git a/infra/terraform/modules/foundations/bootstrap_identity/main.tf b/infra/terraform/modules/foundations/bootstrap_identity/main.tf index bcc3ba8..2e684d5 100644 --- a/infra/terraform/modules/foundations/bootstrap_identity/main.tf +++ b/infra/terraform/modules/foundations/bootstrap_identity/main.tf @@ -28,11 +28,11 @@ resource "google_project_service" "identity" { module "github_oidc" { source = "../../base/github_oidc_provider" - project_id = var.project_id - pool_id = "github-actions" - github_repository = "${var.github_owner}/${var.github_repo}" - allowed_ref = var.allowed_ref - environment = var.environment + project_id = var.project_id + pool_id = "github-actions" + github_repositories = var.github_repositories + allowed_ref = var.allowed_ref + environment = var.environment depends_on = [google_project_service.identity] } @@ -49,9 +49,15 @@ module "terraform_deployer" { } resource "google_service_account_iam_member" "github_workload_identity_user" { + for_each = var.github_repositories service_account_id = module.terraform_deployer.name role = "roles/iam.workloadIdentityUser" - member = "principalSet://iam.googleapis.com/${module.github_oidc.name}/attribute.repository/${var.github_owner}/${var.github_repo}" + member = "principalSet://iam.googleapis.com/${module.github_oidc.name}/attribute.repository/${each.value}" +} + +moved { + from = google_service_account_iam_member.github_workload_identity_user + to = google_service_account_iam_member.github_workload_identity_user["afaryy/MemoryDirector"] } resource "google_project_iam_member" "terraform_deployer" { diff --git a/infra/terraform/modules/foundations/bootstrap_identity/outputs.tf b/infra/terraform/modules/foundations/bootstrap_identity/outputs.tf index f004092..f1d0181 100644 --- a/infra/terraform/modules/foundations/bootstrap_identity/outputs.tf +++ b/infra/terraform/modules/foundations/bootstrap_identity/outputs.tf @@ -1,4 +1,5 @@ output "github_repository" { value = "${var.github_owner}/${var.github_repo}" } +output "github_repositories" { value = tolist(var.github_repositories) } output "allowed_ref" { value = var.allowed_ref } output "environment" { value = var.environment } output "service_account_email" { diff --git a/infra/terraform/modules/foundations/bootstrap_identity/tests/bootstrap_identity.tftest.hcl b/infra/terraform/modules/foundations/bootstrap_identity/tests/bootstrap_identity.tftest.hcl index 1a0ac5b..fe3b55b 100644 --- a/infra/terraform/modules/foundations/bootstrap_identity/tests/bootstrap_identity.tftest.hcl +++ b/infra/terraform/modules/foundations/bootstrap_identity/tests/bootstrap_identity.tftest.hcl @@ -4,12 +4,13 @@ run "restricts_github_federation_to_the_sandbox_repository_and_main" { command = plan variables { - project_id = "memory-director-sandbox" - github_owner = "afaryy" - github_repo = "MemoryDirector" - allowed_ref = "refs/heads/main" - environment = "sandbox" - service_account_id = "github-terraform-sandbox" + project_id = "memory-director-sandbox" + github_owner = "afaryy" + github_repo = "MemoryDirector" + github_repositories = ["afaryy/MemoryDirector", "sailing-together/MemoryDirector"] + allowed_ref = "refs/heads/main" + environment = "sandbox" + service_account_id = "github-terraform-sandbox" } assert { @@ -17,6 +18,24 @@ run "restricts_github_federation_to_the_sandbox_repository_and_main" { error_message = "The deployment identity must be restricted to the Memory Director repository." } + assert { + condition = toset(output.github_repositories) == toset(["afaryy/MemoryDirector", "sailing-together/MemoryDirector"]) + error_message = "The transitional WIF policy must trust exactly the current and target Memory Director repositories." + } + + assert { + condition = module.github_oidc.attribute_condition == "assertion.repository in [\"afaryy/MemoryDirector\",\"sailing-together/MemoryDirector\"] && assertion.ref == 'refs/heads/main' && assertion.environment == 'sandbox'" + error_message = "WIF must restrict both allowed repositories to the main ref and sandbox environment." + } + + assert { + condition = toset(keys(google_service_account_iam_member.github_workload_identity_user)) == toset([ + "afaryy/MemoryDirector", + "sailing-together/MemoryDirector", + ]) + error_message = "Only the current and target repositories may impersonate the Terraform service account during the transition." + } + assert { condition = output.allowed_ref == "refs/heads/main" error_message = "The deployment identity must be restricted to the approved main ref." diff --git a/infra/terraform/modules/foundations/bootstrap_identity/variables.tf b/infra/terraform/modules/foundations/bootstrap_identity/variables.tf index 79294db..5a03339 100644 --- a/infra/terraform/modules/foundations/bootstrap_identity/variables.tf +++ b/infra/terraform/modules/foundations/bootstrap_identity/variables.tf @@ -1,6 +1,7 @@ variable "project_id" { type = string } variable "github_owner" { type = string } variable "github_repo" { type = string } +variable "github_repositories" { type = set(string) } variable "allowed_ref" { type = string } variable "environment" { type = string } variable "service_account_id" { type = string }