From b208d5cbd2ffd10e7889150428bb17bc1486c686 Mon Sep 17 00:00:00 2001 From: Bharath KKB Date: Sat, 14 Aug 2021 14:50:39 -0500 Subject: [PATCH] fix: WI conditionally invoke data source if using external GSA (#974) * fix: conditionally invoke datasource for custom gsa * add an example for custom gsa * refactor tests * fmt --- examples/workload_identity/README.md | 8 +++-- examples/workload_identity/main.tf | 16 ++++++++- examples/workload_identity/outputs.tf | 33 +++++++++++++++--- modules/workload-identity/main.tf | 9 ++--- test/fixtures/workload_identity/outputs.tf | 34 ++++++++++++++++--- .../workload_identity/controls/gcloud.rb | 32 +++++++++-------- test/integration/workload_identity/inspec.yml | 16 +++++++-- 7 files changed, 115 insertions(+), 33 deletions(-) diff --git a/examples/workload_identity/README.md b/examples/workload_identity/README.md index 1fa005c077..bb13f2b61d 100644 --- a/examples/workload_identity/README.md +++ b/examples/workload_identity/README.md @@ -24,8 +24,12 @@ Read more about [workload identity in the docs](https://cloud.google.com/kuberne | ca\_certificate | n/a | | client\_token | n/a | | cluster\_name | Cluster name | -| k8s\_service\_account\_email | K8S GCP service account. | -| k8s\_service\_account\_name | K8S GCP service name | +| default\_wi\_email | GCP service account. | +| default\_wi\_ksa\_name | K8S SA name | +| existing\_gsa\_email | GCP service account. | +| existing\_gsa\_name | K8S SA name | +| existing\_ksa\_email | GCP service account. | +| existing\_ksa\_name | K8S SA name | | kubernetes\_endpoint | n/a | | location | Cluster location (zones) | | project\_id | Project id where GKE cluster is created. | diff --git a/examples/workload_identity/main.tf b/examples/workload_identity/main.tf index 02ba5c7900..be11dd5323 100644 --- a/examples/workload_identity/main.tf +++ b/examples/workload_identity/main.tf @@ -62,7 +62,6 @@ module "workload_identity" { use_existing_k8s_sa = false } - # example with existing KSA resource "kubernetes_service_account" "test" { metadata { @@ -83,3 +82,18 @@ module "workload_identity_existing_ksa" { use_existing_k8s_sa = true k8s_sa_name = kubernetes_service_account.test.metadata.0.name } + +# example with existing GSA +resource "google_service_account" "custom" { + account_id = "custom-gsa" + project = var.project_id +} + +module "workload_identity_existing_gsa" { + source = "../../modules/workload-identity" + project_id = var.project_id + name = google_service_account.custom.account_id + use_existing_gcp_sa = true + # wait till custom GSA is created to force module data source read during apply + depends_on = [google_service_account.custom] +} diff --git a/examples/workload_identity/outputs.tf b/examples/workload_identity/outputs.tf index e96dfa4185..8d97afabfe 100644 --- a/examples/workload_identity/outputs.tf +++ b/examples/workload_identity/outputs.tf @@ -53,12 +53,35 @@ output "cluster_name" { value = module.gke.name } -output "k8s_service_account_email" { - description = "K8S GCP service account." +# Default instantiation of WI module +output "default_wi_email" { + description = "GCP service account." value = module.workload_identity.gcp_service_account_email } -output "k8s_service_account_name" { - description = "K8S GCP service name" - value = module.workload_identity.gcp_service_account_name +output "default_wi_ksa_name" { + description = "K8S SA name" + value = module.workload_identity.k8s_service_account_name +} + +# Existing KSA instantiation of WI module +output "existing_ksa_email" { + description = "GCP service account." + value = module.workload_identity_existing_ksa.gcp_service_account_email +} + +output "existing_ksa_name" { + description = "K8S SA name" + value = module.workload_identity_existing_ksa.k8s_service_account_name +} + +# Existing GSA instantiation of WI module +output "existing_gsa_email" { + description = "GCP service account." + value = google_service_account.custom.email +} + +output "existing_gsa_name" { + description = "K8S SA name" + value = module.workload_identity_existing_gsa.k8s_service_account_name } diff --git a/modules/workload-identity/main.tf b/modules/workload-identity/main.tf index eacaacaf9d..f267e4a272 100644 --- a/modules/workload-identity/main.tf +++ b/modules/workload-identity/main.tf @@ -18,7 +18,7 @@ locals { # GCP service account ids must be < 30 chars matching regex ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$ # KSAs do not have this naming restriction. gcp_given_name = var.gcp_sa_name != null ? var.gcp_sa_name : substr(var.name, 0, 30) - gcp_sa_email = data.google_service_account.cluster_service_account.email + gcp_sa_email = var.use_existing_gcp_sa ? data.google_service_account.cluster_service_account[0].email : google_service_account.cluster_service_account[0].email gcp_sa_fqn = "serviceAccount:${local.gcp_sa_email}" # This will cause Terraform to block returning outputs until the service account is created @@ -30,8 +30,9 @@ locals { } data "google_service_account" "cluster_service_account" { - # This will cause Terraform to block looking up details until the service account is created - account_id = var.use_existing_gcp_sa ? local.gcp_given_name : google_service_account.cluster_service_account[0].account_id + count = var.use_existing_gcp_sa ? 1 : 0 + + account_id = var.name project = var.project_id } @@ -72,7 +73,7 @@ module "annotate-sa" { } resource "google_service_account_iam_member" "main" { - service_account_id = data.google_service_account.cluster_service_account.name + service_account_id = var.use_existing_gcp_sa ? data.google_service_account.cluster_service_account[0].name : google_service_account.cluster_service_account[0].name role = "roles/iam.workloadIdentityUser" member = local.k8s_sa_gcp_derived_name } diff --git a/test/fixtures/workload_identity/outputs.tf b/test/fixtures/workload_identity/outputs.tf index 924df1ddee..cb67987ac4 100644 --- a/test/fixtures/workload_identity/outputs.tf +++ b/test/fixtures/workload_identity/outputs.tf @@ -75,12 +75,36 @@ output "cluster_name" { value = module.example.cluster_name } -output "k8s_service_account_email" { - description = "K8S GCP service account." - value = module.example.k8s_service_account_email +# Default instantiation of WI module +output "default_wi_email" { + description = "GCP service account." + value = module.example.default_wi_email } -output "k8s_service_account_name" { +output "default_wi_ksa_name" { description = "K8S GCP service account name." - value = module.example.k8s_service_account_name + value = module.example.default_wi_ksa_name } + +# Existing KSA instantiation of WI module +output "existing_ksa_email" { + description = "GCP service account." + value = module.example.existing_ksa_email +} + +output "existing_ksa_name" { + description = "K8S GCP service name" + value = module.example.existing_ksa_name +} + +# Existing GSA instantiation of WI module +output "existing_gsa_email" { + description = "GCP service account." + value = module.example.existing_gsa_email +} + +output "existing_gsa_name" { + description = "K8S GCP service name" + value = module.example.existing_gsa_name +} + diff --git a/test/integration/workload_identity/controls/gcloud.rb b/test/integration/workload_identity/controls/gcloud.rb index 7d6b84ebdc..1c956052eb 100644 --- a/test/integration/workload_identity/controls/gcloud.rb +++ b/test/integration/workload_identity/controls/gcloud.rb @@ -15,8 +15,11 @@ project_id = attribute('project_id') location = attribute('location') cluster_name = attribute('cluster_name') -k8s_service_account_email = attribute('k8s_service_account_email') -k8s_service_account_name = attribute('k8s_service_account_name') +wi_gsa_to_k8s_sa = { + attribute('default_wi_email') => attribute('default_wi_ksa_name'), + attribute('existing_ksa_email') => attribute('existing_ksa_name'), + attribute('existing_gsa_email') => attribute('existing_gsa_name') +} control "gcloud" do title "Google Compute Engine GKE configuration" @@ -57,20 +60,21 @@ end end end + wi_gsa_to_k8s_sa.each do |gsa_email,ksa_name| + describe command("gcloud iam service-accounts get-iam-policy #{gsa_email} --format=json") do + its(:exit_status) { should eq 0 } + its(:stderr) { should eq '' } - describe command("gcloud iam service-accounts get-iam-policy #{k8s_service_account_email} --format=json") do - its(:exit_status) { should eq 0 } - its(:stderr) { should eq '' } - - let!(:iam) do - if subject.exit_status == 0 - JSON.parse(subject.stdout) - else - {} + let!(:iam) do + if subject.exit_status == 0 + JSON.parse(subject.stdout) + else + {} + end + end + it "has expected workload identity user roles" do + expect(iam['bindings'][0]).to include("members" => ["serviceAccount:#{project_id}.svc.id.goog[default/#{ksa_name}]"], "role" => "roles/iam.workloadIdentityUser") end - end - it "has expected workload identity user roles" do - expect(iam['bindings'][0]).to include("members" => [k8s_service_account_name], "role" => "roles/iam.workloadIdentityUser") end end end diff --git a/test/integration/workload_identity/inspec.yml b/test/integration/workload_identity/inspec.yml index 651a654ca0..61f2f306aa 100644 --- a/test/integration/workload_identity/inspec.yml +++ b/test/integration/workload_identity/inspec.yml @@ -23,9 +23,21 @@ attributes: - name: project_id required: true type: string - - name: k8s_service_account_email + - name: default_wi_email required: true type: string - - name: k8s_service_account_name + - name: default_wi_ksa_name + required: true + type: string + - name: existing_ksa_email + required: true + type: string + - name: existing_ksa_name + required: true + type: string + - name: existing_gsa_email + required: true + type: string + - name: existing_gsa_name required: true type: string