Skip to content

Commit 712fc54

Browse files
authored
feat: Enables an existing GSA to be used when setting up Workload Identity (terraform-google-modules#955)
1 parent e53a949 commit 712fc54

File tree

5 files changed

+97
-41
lines changed

5 files changed

+97
-41
lines changed

CONTRIBUTING.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,32 @@ Six test-kitchen instances are defined:
5959
The test-kitchen instances in `test/fixtures/` wrap identically-named examples in the `examples/` directory.`
6060

6161
### Test Environment
62-
The easiest way to test the module is in an isolated test project. The setup for such a project is defined in [test/setup](./test/setup/) directory.
62+
The easiest way to test the module is in an isolated test project. The
63+
setup for such a project is defined in [test/setup](./test/setup/)
64+
directory.
6365

64-
To use this setup, you need a service account with Project Creator access on a folder. Export the Service Account credentials to your environment like so:
66+
To use this setup, you need a service account with Project Creator access
67+
on a folder; the Billing Account User role is also required. Export the
68+
Service Account credentials to your environment like so:
6569

6670
```
6771
export SERVICE_ACCOUNT_JSON=$(< credentials.json)
6872
```
6973

74+
Note that `SERVICE_ACCOUNT_JSON` holds the _contents_ of the credentials
75+
file; if you see errors pertaining to credential type, ensure this variable
76+
contains valid JSON, and not, for example, a path.
77+
7078
You will also need to set a few environment variables:
79+
7180
```
7281
export TF_VAR_org_id="your_org_id"
7382
export TF_VAR_folder_id="your_folder_id"
7483
export TF_VAR_billing_account="your_billing_account_id"
7584
```
7685

7786
With these settings in place, you can prepare a test project using Docker:
87+
7888
```
7989
make docker_test_prepare
8090
```

modules/workload-identity/README.md

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
This module creates:
66

7-
* GCP Service Account
87
* IAM Service Account binding to `roles/iam.workloadIdentityUser`
8+
* Optionally, a Google Service Account
99
* Optionally, a Kubernetes Service Account
1010

1111
## Usage
1212

13-
The `terraform-google-workload-identity` can create a kubernetes service account for you, or use an existing kubernetes service account.
13+
The `terraform-google-workload-identity` can create service accounts for you,
14+
or you can use existing accounts; this applies for both the Google and
15+
Kubernetes accounts.
1416

1517
### Creating a Workload Identity
1618

@@ -20,17 +22,17 @@ module "my-app-workload-identity" {
2022
name = "my-application-name"
2123
namespace = "default"
2224
project_id = "my-gcp-project-name"
23-
roles = ["roles/storage.Admin", "roles/compute.Admin"]
25+
roles = ["roles/storage.Admin", "roles/compute.Admin"]
2426
}
2527
```
2628

2729
This will create:
2830

29-
* GCP Service Account named: `[email protected]`
31+
* Google Service Account named: `[email protected]`
3032
* Kubernetes Service Account named: `my-application-name` in the `default` namespace
3133
* IAM Binding (`roles/iam.workloadIdentityUser`) between the service accounts
3234

33-
Usage from a kubernetes deployment:
35+
Usage from a Kubernetes deployment:
3436

3537
```yaml
3638
metadata:
@@ -43,27 +45,47 @@ spec:
4345
serviceAccountName: my-application-name
4446
```
4547
48+
### Using an existing Google Service Account
49+
50+
An existing Google service account can optionally be used.
51+
52+
```hcl
53+
resource "google_service_account" "preexisting" {
54+
account_id = "preexisting-sa"
55+
}
56+
57+
module "my-app-workload-identity" {
58+
source = "terraform-google-modules/kubernetes-engine/google//modules/workload-identity"
59+
use_existing_gcp_sa = true
60+
name = google_service_account.preexisting.account_id
61+
project_id = var.project_id
62+
}
63+
```
64+
4665
### Using an existing Kubernetes Service Account
4766

48-
An existing kubernetes service account can optionally be used. When using an existing k8s servicea account the annotation `"iam.gke.io/gcp-service-account"` must be set.
67+
An existing Kubernetes service account can optionally be used.
4968

5069
```hcl
5170
resource "kubernetes_service_account" "preexisting" {
5271
metadata {
53-
name = "preexisting-sa"
72+
name = "preexisting-sa"
5473
namespace = "prod"
5574
}
5675
}
5776
5877
module "my-app-workload-identity" {
59-
source = "terraform-google-modules/kubernetes-engine/google//modules/workload-identity"
78+
source = "terraform-google-modules/kubernetes-engine/google//modules/workload-identity"
6079
use_existing_k8s_sa = true
61-
name = "preexisting-sa"
62-
namespace = "prod"
80+
name = kubernetes_service_account.preexisting.metadata[0].name
81+
namespace = kubernetes_service_account.preexisting.metadata[0].namespace
6382
project_id = var.project_id
6483
}
6584
```
6685

86+
If annotation is disabled (via `annotate_k8s_sa = false`), the existing Kubernetes service account must
87+
already bear the `"iam.gke.io/gcp-service-account"` annotation.
88+
6789
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
6890
## Inputs
6991

@@ -72,13 +94,15 @@ module "my-app-workload-identity" {
7294
| annotate\_k8s\_sa | Annotate the kubernetes service account with 'iam.gke.io/gcp-service-account' annotation. Valid in cases when an existing SA is used. | `bool` | `true` | no |
7395
| automount\_service\_account\_token | Enable automatic mounting of the service account token | `bool` | `false` | no |
7496
| cluster\_name | Cluster name. Required if using existing KSA. | `string` | `""` | no |
97+
| gcp\_sa\_name | Name for the Google service account; overrides `var.name`. | `string` | `null` | no |
7598
| impersonate\_service\_account | An optional service account to impersonate for gcloud commands. If this service account is not specified, the module will use Application Default Credentials. | `string` | `""` | no |
76-
| k8s\_sa\_name | Name for the existing Kubernetes service account | `string` | `null` | no |
99+
| k8s\_sa\_name | Name for the Kubernetes service account; overrides `var.name`. | `string` | `null` | no |
77100
| location | Cluster location (region if regional cluster, zone if zonal cluster). Required if using existing KSA. | `string` | `""` | no |
78101
| name | Name for both service accounts. The GCP SA will be truncated to the first 30 chars if necessary. | `string` | n/a | yes |
79-
| namespace | Namespace for k8s service account | `string` | `"default"` | no |
102+
| namespace | Namespace for the Kubernetes service account | `string` | `"default"` | no |
80103
| project\_id | GCP project ID | `string` | n/a | yes |
81-
| roles | (optional) A list of roles to be added to the created Service account | `list(string)` | `[]` | no |
104+
| roles | A list of roles to be added to the created service account | `list(string)` | `[]` | no |
105+
| use\_existing\_gcp\_sa | Use an existing Google service account instead of creating one | `bool` | `false` | no |
82106
| use\_existing\_k8s\_sa | Use an existing kubernetes service account instead of creating one | `bool` | `false` | no |
83107

84108
## Outputs

modules/workload-identity/main.tf

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,30 @@
1515
*/
1616

1717
locals {
18-
k8s_sa_gcp_derived_name = "serviceAccount:${var.project_id}.svc.id.goog[${var.namespace}/${local.output_k8s_name}]"
19-
gcp_sa_email = google_service_account.cluster_service_account.email
18+
# GCP service account ids must be < 30 chars matching regex ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$
19+
# KSAs do not have this naming restriction.
20+
gcp_given_name = var.gcp_sa_name != null ? var.gcp_sa_name : substr(var.name, 0, 30)
21+
gcp_sa_email = data.google_service_account.cluster_service_account.email
22+
gcp_sa_fqn = "serviceAccount:${local.gcp_sa_email}"
2023

21-
# This will cause terraform to block returning outputs until the service account is created
24+
# This will cause Terraform to block returning outputs until the service account is created
2225
k8s_given_name = var.k8s_sa_name != null ? var.k8s_sa_name : var.name
2326
output_k8s_name = var.use_existing_k8s_sa ? local.k8s_given_name : kubernetes_service_account.main[0].metadata[0].name
2427
output_k8s_namespace = var.use_existing_k8s_sa ? var.namespace : kubernetes_service_account.main[0].metadata[0].namespace
28+
29+
k8s_sa_gcp_derived_name = "serviceAccount:${var.project_id}.svc.id.goog[${var.namespace}/${local.output_k8s_name}]"
2530
}
2631

27-
resource "google_service_account" "cluster_service_account" {
28-
# GCP service account ids must be < 30 chars matching regex ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$
29-
# KSA do not have this naming restriction.
30-
account_id = substr(var.name, 0, 30)
32+
data "google_service_account" "cluster_service_account" {
33+
# This will cause Terraform to block looking up details until the service account is created
34+
account_id = var.use_existing_gcp_sa ? local.gcp_given_name : google_service_account.main[0].account_id
35+
project = var.project_id
36+
}
37+
38+
resource "google_service_account" "main" {
39+
count = var.use_existing_gcp_sa ? 0 : 1
40+
41+
account_id = local.gcp_given_name
3142
display_name = substr("GCP SA bound to K8S SA ${local.k8s_given_name}", 0, 100)
3243
project = var.project_id
3344
}
@@ -40,7 +51,7 @@ resource "kubernetes_service_account" "main" {
4051
name = var.name
4152
namespace = var.namespace
4253
annotations = {
43-
"iam.gke.io/gcp-service-account" = google_service_account.cluster_service_account.email
54+
"iam.gke.io/gcp-service-account" = local.gcp_sa_email
4455
}
4556
}
4657
}
@@ -61,16 +72,15 @@ module "annotate-sa" {
6172
}
6273

6374
resource "google_service_account_iam_member" "main" {
64-
service_account_id = google_service_account.cluster_service_account.name
75+
service_account_id = data.google_service_account.cluster_service_account.name
6576
role = "roles/iam.workloadIdentityUser"
6677
member = local.k8s_sa_gcp_derived_name
6778
}
6879

69-
7080
resource "google_project_iam_member" "workload_identity_sa_bindings" {
7181
for_each = toset(var.roles)
7282

7383
project = var.project_id
7484
role = each.value
75-
member = "serviceAccount:${google_service_account.cluster_service_account.email}"
85+
member = local.gcp_sa_fqn
7686
}

modules/workload-identity/output.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ output "gcp_service_account_email" {
3131

3232
output "gcp_service_account_fqn" {
3333
description = "FQN of GCP service account."
34-
value = "serviceAccount:${google_service_account.cluster_service_account.email}"
34+
value = local.gcp_sa_fqn
3535
}
3636

3737
output "gcp_service_account_name" {
@@ -41,5 +41,5 @@ output "gcp_service_account_name" {
4141

4242
output "gcp_service_account" {
4343
description = "GCP service account."
44-
value = google_service_account.cluster_service_account
44+
value = data.google_service_account.cluster_service_account
4545
}

modules/workload-identity/variables.tf

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,23 @@ variable "name" {
1919
type = string
2020
}
2121

22+
variable "project_id" {
23+
description = "GCP project ID"
24+
type = string
25+
}
26+
27+
variable "gcp_sa_name" {
28+
description = "Name for the Google service account; overrides `var.name`."
29+
type = string
30+
default = null
31+
}
32+
33+
variable "use_existing_gcp_sa" {
34+
description = "Use an existing Google service account instead of creating one"
35+
type = bool
36+
default = false
37+
}
38+
2239
variable "cluster_name" {
2340
description = "Cluster name. Required if using existing KSA."
2441
type = string
@@ -32,48 +49,43 @@ variable "location" {
3249
}
3350

3451
variable "k8s_sa_name" {
35-
description = "Name for the existing Kubernetes service account"
52+
description = "Name for the Kubernetes service account; overrides `var.name`."
3653
type = string
3754
default = null
3855
}
3956

4057
variable "namespace" {
41-
description = "Namespace for k8s service account"
42-
default = "default"
43-
type = string
44-
}
45-
46-
variable "project_id" {
47-
description = "GCP project ID"
58+
description = "Namespace for the Kubernetes service account"
4859
type = string
60+
default = "default"
4961
}
5062

5163
variable "use_existing_k8s_sa" {
5264
description = "Use an existing kubernetes service account instead of creating one"
53-
default = false
5465
type = bool
66+
default = false
5567
}
5668

5769
variable "annotate_k8s_sa" {
5870
description = "Annotate the kubernetes service account with 'iam.gke.io/gcp-service-account' annotation. Valid in cases when an existing SA is used."
59-
default = true
6071
type = bool
72+
default = true
6173
}
6274

6375
variable "automount_service_account_token" {
6476
description = "Enable automatic mounting of the service account token"
65-
default = false
6677
type = bool
78+
default = false
6779
}
6880

6981
variable "roles" {
82+
description = "A list of roles to be added to the created service account"
7083
type = list(string)
7184
default = []
72-
description = "(optional) A list of roles to be added to the created Service account"
7385
}
7486

7587
variable "impersonate_service_account" {
76-
type = string
7788
description = "An optional service account to impersonate for gcloud commands. If this service account is not specified, the module will use Application Default Credentials."
89+
type = string
7890
default = ""
7991
}

0 commit comments

Comments
 (0)