diff --git a/blueprints/README.md b/blueprints/README.md index b99441868..37a6ae979 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -5,7 +5,7 @@ This section provides **[networking blueprints](./networking/)** that implement Currently available blueprints: - **apigee** - [Apigee X foundations](./apigee/apigee-x-foundations/). [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/) -- **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Network Quota Monitoring](./cloud-operations/network-quota-monitoring), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/compute-quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) +- **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Network Quota Monitoring](./cloud-operations/network-quota-monitoring), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/compute-quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation with Terraform Cloud/Enterprise workflows](./cloud-operations/terraform-cloud-dynamic-credentials), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation) - **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Minimal Data Platform](./data-solutions/data-platform-minimal), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder), [BigQuery ML and Vertex AI Pipeline](./data-solutions/bq-ml) - **factories** - [Fabric resource factories](./factories) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant](../fast/stages/3-gke-dev), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [GKE Autopilot](./gke/autopilot) diff --git a/blueprints/cloud-operations/README.md b/blueprints/cloud-operations/README.md index e4421f7be..7bba31e3c 100644 --- a/blueprints/cloud-operations/README.md +++ b/blueprints/cloud-operations/README.md @@ -46,12 +46,6 @@ The blueprint's feed tracks changes to Google Compute instances, and the Cloud F
-## On-prem Service Account key management - -This [blueprint](./onprem-sa-key-management) shows how to manage IAM Service Account Keys by manually generating a key pair and uploading the public part of the key to GCP. - -
- ## Packer image builder This [blueprint](./packer-image-builder) shows how to deploy infrastructure for a Compute Engine image builder based on [Hashicorp's Packer tool](https://www.packer.io). diff --git a/blueprints/cloud-operations/onprem-sa-key-management/OWNERS b/blueprints/cloud-operations/onprem-sa-key-management/OWNERS deleted file mode 100644 index c7db5ffd7..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/OWNERS +++ /dev/null @@ -1 +0,0 @@ -averbuks diff --git a/blueprints/cloud-operations/onprem-sa-key-management/README.md b/blueprints/cloud-operations/onprem-sa-key-management/README.md deleted file mode 100644 index bcbf661e1..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# Managing on-prem service account keys by uploading public keys - -When managing GCP Service Accounts with terraform, it's often a question on **how to avoid Service Account Key in the terraform state?** - -This blueprint shows how to manage IAM Service Account Keys by manually generating a key pair and uploading the public part of the key to GCP. It has the following benefits: - - - no [passing keys between users](https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys#pass-between-users) or systems - - no private keys stored in the terraform state (only public part of the key is in the state) - - let keys [expire automatically](https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys#key-expiryhaving) - - -## Running the blueprint - -Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2Fcloud-operations%2Fonprem-sa-key-management&cloudshell_open_in_editor=cloudshell_open%2Fcloud-foundation-fabric%2Fblueprints%2Fcloud-operations%2Fonprem-sa-key-management%2Fvariables.tf), then go through the following steps to create resources: - -Cleaning up blueprint keys -```bash -rm -f /public-keys/data-uploader/ -rm -f /public-keys/prisma-security/ -``` - -Generate keys for service accounts -```bash -mkdir keys && cd keys -openssl req -x509 -nodes -newkey rsa:2048 -days 30 \ - -keyout data_uploader_private_key.pem \ - -out ../public-keys/data-uploader/public_key.pem \ - -subj "/CN=unused" -openssl req -x509 -nodes -newkey rsa:2048 -days 30 \ - -keyout prisma_security_private_key.pem \ - -out ../public-keys/prisma-security/public_key.pem \ - -subj "/CN=unused" -``` - -Deploy service accounts and keys -```bash -cd .. -terraform init -terraform apply -var project_id=$GOOGLE_CLOUD_PROJECT - -``` - -Extract JSON credentials templates from terraform output and put the private part of the keys into templates -```bash -terraform show -json | jq '.values.outputs."sa-credentials".value."data-uploader"."public_key.pem" | fromjson' > data-uploader.json -terraform show -json | jq '.values.outputs."sa-credentials".value."prisma-security"."public_key.pem" | fromjson' > prisma-security.json - -contents=$(jq --arg key "$(cat keys/data_uploader_private_key.pem)" '.private_key=$key' data-uploader.json) && echo "$contents" > data-uploader.json -contents=$(jq --arg key "$(cat keys/prisma_security_private_key.pem)" '.private_key=$key' prisma-security.json) && echo "$contents" > prisma-security.json -``` - -## Testing the blueprint -Validate that service accounts json credentials are valid -```bash -gcloud auth activate-service-account --key-file prisma-security.json -gcloud auth activate-service-account --key-file data-uploader.json -``` - -## Cleaning up -```bash -terraform destroy -var project_id=$GOOGLE_CLOUD_PROJECT -``` - - -## Variables - -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L23) | Project id. | string | ✓ | | -| [project_create](variables.tf#L17) | Create project instead of using an existing one. | bool | | false | -| [service_accounts](variables.tf#L28) | List of service accounts. | list(object({…})) | | […] | -| [services](variables.tf#L56) | Service APIs to enable. | list(string) | | [] | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| [sa-credentials](outputs.tf#L17) | SA json key templates. | | - - - -## Test - -```hcl -module "test" { - source = "./fabric/blueprints/cloud-operations/onprem-sa-key-management" - project_create = true - project_id = "test" -} -# tftest modules=4 resources=7 -``` diff --git a/blueprints/cloud-operations/onprem-sa-key-management/backend.tf.sample b/blueprints/cloud-operations/onprem-sa-key-management/backend.tf.sample deleted file mode 100644 index 4ef7e4435..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/backend.tf.sample +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023 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 -# -# https://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. - -# set a valid bucket below and rename this file to backend.tf - -terraform { - backend "gcs" { - bucket = "" - prefix = "fabric/operations/onprem-sa-key-management" - } -} - diff --git a/blueprints/cloud-operations/onprem-sa-key-management/cloud-shell-readme.txt b/blueprints/cloud-operations/onprem-sa-key-management/cloud-shell-readme.txt deleted file mode 100644 index ff75626a6..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/cloud-shell-readme.txt +++ /dev/null @@ -1,46 +0,0 @@ - - -################################# Quickstart ################################# - -# cleaning up example keys - -- rm -f /public-keys/data-uploader/ -- rm -f /public-keys/prisma-security/ - -# generate keys for service accounts - -- mkdir keys && cd keys -- openssl req -x509 -nodes -newkey rsa:2048 -days 30 \ - -keyout data_uploader_private_key.pem \ - -out ../public-keys/data-uploader/public_key.pem \ - -subj "/CN=unused" -- openssl req -x509 -nodes -newkey rsa:2048 -days 30 \ - -keyout prisma_security_private_key.pem \ - -out ../public-keys/prisma-security/public_key.pem \ - -subj "/CN=unused" - -# deploy service accounts and keys - -- cd .. -- terraform init -- terraform apply -var project_id=$GOOGLE_CLOUD_PROJECT - - -# extract JSON credentials templates from terraform output and put the private part of the keys into templates - -- terraform show -json | jq '.values.outputs."sa-credentials".value."data-uploader"."public_key.pem" | fromjson' > data-uploader.json -- terraform show -json | jq '.values.outputs."sa-credentials".value."prisma-security"."public_key.pem" | fromjson' > prisma-security.json - -- contents=$(jq --arg key "$(cat keys/data_uploader_private_key.pem)" '.private_key=$key' data-uploader.json) && echo "$contents" > data-uploader.json -- contents=$(jq --arg key "$(cat keys/prisma_security_private_key.pem)" '.private_key=$key' prisma-security.json) && echo "$contents" > prisma-security.json - - - -# validate that service accounts json credentials are valid - -- gcloud auth activate-service-account --key-file prisma-security.json -- gcloud auth activate-service-account --key-file data-uploader.json - - -# cleaning up -- terraform destroy -var project_id=$GOOGLE_CLOUD_PROJECT diff --git a/blueprints/cloud-operations/onprem-sa-key-management/main.tf b/blueprints/cloud-operations/onprem-sa-key-management/main.tf deleted file mode 100644 index 1eec60332..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/main.tf +++ /dev/null @@ -1,37 +0,0 @@ -/** - * 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. - */ - -locals { - service_accounts = { for sa in var.service_accounts : sa.name => sa } -} - -module "project" { - source = "../../../modules/project" - name = var.project_id - project_reuse = var.project_create != true ? {} : null - services = var.services -} - -module "integration-sa" { - source = "../../../modules/iam-service-account" - for_each = local.service_accounts - project_id = module.project.project_id - name = each.value.name - iam_project_roles = { - (module.project.project_id) = each.value.iam_project_roles - } - public_keys_directory = each.value.public_keys_path -} diff --git a/blueprints/cloud-operations/onprem-sa-key-management/outputs.tf b/blueprints/cloud-operations/onprem-sa-key-management/outputs.tf deleted file mode 100644 index 9174474cd..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/outputs.tf +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ - -output "sa-credentials" { - description = "SA json key templates." - value = { for key, value in module.integration-sa : key => value.service_account_credentials } -} diff --git a/blueprints/cloud-operations/onprem-sa-key-management/public-keys/data-uploader/public_key.pem b/blueprints/cloud-operations/onprem-sa-key-management/public-keys/data-uploader/public_key.pem deleted file mode 100644 index ad5bc9fe5..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/public-keys/data-uploader/public_key.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnjCCAYYCCQDhgw8htVCGmTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZ1 -bnVzZWQwHhcNMjExMjA2MDgwNTAyWhcNMzExMjA0MDgwNTAyWjARMQ8wDQYDVQQD -DAZ1bnVzZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0xlwdjkBS -1ovANJ1RXKpFdbPQWYlqKUUo+/KLClNYC9KxRqrc+u5FtPIdCPv5WRH5sz+z8gcf -3zJMht0dO7fOwJ9wSDKzvHkMUdXTGBPbm2i9PNA6f+YEwJQjWJlAHFH4Lp3x6ddT -4KO4FRQEkN/5V1+sfmyGGFaSXaoi+PcDcQHvfUUlp5iyX4I+8tqwh1kdg1M5orkE -7iBG0wHWzfOSmZq5in6t9+lWzOZeYapi8bVBm7Vz+dmHZPKS6EGmAXS1wpLCSKHB -uv23KXY4gAXOPHiDI70JpeNiSJBE9WgXs+nL78vNjLTvDhpC10b9nOxLjRc6wA5b -3q2Am0dW1DPRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJqyTIibNZM/q30Fn+vR -V9q++19CIervZig1uCarH1M86cpPYRfKcYHOi6tnoCTL9VG8Ky8pbmkZNkET7vnN -OQirpsPmqu3d+FBoqXUt8w1mT1JVr0YiTo3i07zTH8rvQKHjEfPxR73IAyYNvJ3D -k3SdUvU3xXOa+otOQcBKIxX6mJPLhzXgZd144KCfD95qOvpoQOsNW4UWXZ3sPC0k -VcMlN5O8/+D65y63nNtyECXvLicLdn/cdpA2H7Tqhz2ZZR+6tLcDW1kSsA8b6+rQ -1IaKpF+TYo0jMD+WLatRrOHXOWije8871zooAXq9MLVJrT889TdsmEIYT7YPWIeJ -Jcg= ------END CERTIFICATE----- diff --git a/blueprints/cloud-operations/onprem-sa-key-management/public-keys/prisma-security/public_key.pem b/blueprints/cloud-operations/onprem-sa-key-management/public-keys/prisma-security/public_key.pem deleted file mode 100644 index 5da20f0b1..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/public-keys/prisma-security/public_key.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnjCCAYYCCQDXMv59IiZqfTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZ1 -bnVzZWQwHhcNMjExMjA2MDgwNjE3WhcNMzExMjA0MDgwNjE3WjARMQ8wDQYDVQQD -DAZ1bnVzZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOK6XwgTzL -icSITBrQBmhnYNOuggDhQr40j8/pIuTOiFZbd+ne3MhcFxpE58T9cOXgR0i/S4ok -+kcGE74H2U7RsRpNi7fJhi62T9e2CXpibURQNJD6y0lXBQkfx6kCrhyvXqHbTxm5 -J0f5mpLlze+w7ATikmYI0mrU9XjtnRJOdxtGfiIaQ/suGTaZ0z4tZgAXy9RnwUAb -LPXn0BD1+GYpCs82+1q7HpMIf343VRH0AdsQJteQSj5LKfaZZTNUF9NIgKtMylck -z0Pt8TmBU0GtJX/XkSWCwMUdqdedXkvhY1XoAZPjaEBSSwq6P15PmdpDq9q2TYjD -8U3kuCX0AlWjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADAmjI1sg150MK97DCSl -d5OpEShCypaEZSLb/mFONW6mTX2OSdF9ipd9B07BQ2DrL8Xou2/V1aDtQZOWPIGu -Hlm1LKw8sZY2rWX0Rq/v/NxY5iGRlwPMh7Rn9fnpHgaC1PktoDJEcvNMpzBjtfKn -beKP9MNSChAFTTbJVWO5xT/ljE/yoPL3jyJKzKHH7y7AfbonrbQjAENbX/WCRYh3 -zOEWZG/fusRcKkZ/cO7wFFP1gzJFE9wFRu7LOA/FntCixtVSnclsOnunQfqQEVmp -Y0IjfceIerJysCTo0I5HfRw0DOFfZimallOa4Mv5BDmzMWWyX9TvppHCnmqvM2El -ISY= ------END CERTIFICATE----- diff --git a/blueprints/cloud-operations/onprem-sa-key-management/variables.tf b/blueprints/cloud-operations/onprem-sa-key-management/variables.tf deleted file mode 100644 index 329c5debd..000000000 --- a/blueprints/cloud-operations/onprem-sa-key-management/variables.tf +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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. - */ - -variable "project_create" { - description = "Create project instead of using an existing one." - type = bool - default = false -} - -variable "project_id" { - description = "Project id." - type = string -} - -variable "service_accounts" { - description = "List of service accounts." - type = list(object({ - name = string - iam_project_roles = list(string) - public_keys_path = string - })) - default = [ - { - name = "data-uploader" - iam_project_roles = [ - "roles/bigquery.dataOwner", - "roles/bigquery.jobUser", - "roles/storage.objectAdmin" - ] - public_keys_path = "public-keys/data-uploader/" - }, - { - name = "prisma-security" - iam_project_roles = [ - "roles/iam.securityReviewer" - ] - public_keys_path = "public-keys/prisma-security/" - }, - ] - -} - -variable "services" { - description = "Service APIs to enable." - type = list(string) - default = [] -} diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md index e039fc87f..60796a7d4 100644 --- a/modules/iam-service-account/README.md +++ b/modules/iam-service-account/README.md @@ -2,11 +2,20 @@ This module allows simplified creation and management of one a service account and its IAM bindings. -The Service Account `key` can be generated with `openssl` library and only the public part uploaded to the Service Account, for more refer to the [Onprem SA Key Management](../../blueprints/cloud-operations/onprem-sa-key-management/) example. - Note that outputs have no dependencies on IAM bindings to prevent resource cycles. -## Example +## TOC + + +- [TOC](#toc) +- [Simple Example](#simple-example) +- [Tag Bindings](#tag-bindings) +- [Files](#files) +- [Variables](#variables) +- [Outputs](#outputs) + + +## Simple Example ```hcl module "myproject-default-service-accounts" { @@ -27,6 +36,25 @@ module "myproject-default-service-accounts" { } # tftest modules=1 resources=4 inventory=basic.yaml e2e ``` + +## Tag Bindings + +Use the `tag_bindings` variable to attach tags to the service account. Provide `project_number` to prevent potential permadiffs with the tag binding resource. + +```hcl +module "service-account-with-tags" { + source = "./fabric/modules/iam-service-account" + project_id = var.project_id + name = "test-service-account" + project_number = var.project_number + tag_bindings = { + foo = "tagValues/123456789" + } +} +# tftest modules=1 resources=2 inventory=tags.yaml +``` + + ## Files @@ -34,7 +62,7 @@ module "myproject-default-service-accounts" { | name | description | resources | |---|---|---| | [iam.tf](./iam.tf) | IAM bindings. | google_billing_account_iam_member · google_folder_iam_member · google_organization_iam_member · google_project_iam_member · google_service_account_iam_binding · google_service_account_iam_member · google_storage_bucket_iam_member | -| [main.tf](./main.tf) | Module-level locals and resources. | google_service_account · google_service_account_key | +| [main.tf](./main.tf) | Module-level locals and resources. | google_service_account · google_tags_tag_binding | | [outputs.tf](./outputs.tf) | Module outputs. | | | [variables.tf](./variables.tf) | Module variables. | | | [versions.tf](./versions.tf) | Version pins. | | @@ -43,22 +71,24 @@ module "myproject-default-service-accounts" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L108) | Name of the service account to create. | string | ✓ | | -| [project_id](variables.tf#L123) | Project id where service account will be created. | string | ✓ | | -| [description](variables.tf#L17) | Optional description. | string | | null | -| [display_name](variables.tf#L23) | Display name of the service account to create. | string | | "Terraform-managed." | -| [iam](variables.tf#L29) | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_billing_roles](variables.tf#L36) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | map(list(string)) | | {} | -| [iam_bindings](variables.tf#L43) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | -| [iam_bindings_additive](variables.tf#L58) | Individual additive IAM bindings on the service account. Keys are arbitrary. | map(object({…})) | | {} | -| [iam_folder_roles](variables.tf#L73) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string)) | | {} | -| [iam_organization_roles](variables.tf#L80) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | -| [iam_project_roles](variables.tf#L87) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | -| [iam_sa_roles](variables.tf#L94) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | -| [iam_storage_roles](variables.tf#L101) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | -| [prefix](variables.tf#L113) | Prefix applied to service account names. | string | | null | -| [public_keys_directory](variables.tf#L128) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string | | "" | -| [service_account_create](variables.tf#L134) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | +| [name](variables.tf#L118) | Name of the service account to create. | string | ✓ | | +| [project_id](variables.tf#L133) | Project id where service account will be created. | string | ✓ | | +| [create_ignore_already_exists](variables.tf#L17) | If set to true, skip service account creation if a service account with the same email already exists. | bool | | null | +| [description](variables.tf#L27) | Optional description. | string | | null | +| [display_name](variables.tf#L33) | Display name of the service account to create. | string | | "Terraform-managed." | +| [iam](variables.tf#L39) | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_billing_roles](variables.tf#L46) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | map(list(string)) | | {} | +| [iam_bindings](variables.tf#L53) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables.tf#L68) | Individual additive IAM bindings on the service account. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_folder_roles](variables.tf#L83) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string)) | | {} | +| [iam_organization_roles](variables.tf#L90) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | +| [iam_project_roles](variables.tf#L97) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | +| [iam_sa_roles](variables.tf#L104) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | +| [iam_storage_roles](variables.tf#L111) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | +| [prefix](variables.tf#L123) | Prefix applied to service account names. | string | | null | +| [project_number](variables.tf#L138) | Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings. | string | | null | +| [service_account_create](variables.tf#L144) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | +| [tag_bindings](variables.tf#L151) | Tag bindings for this service accounts, in key => tag value id format. | map(string) | | {} | ## Outputs @@ -69,5 +99,4 @@ module "myproject-default-service-accounts" { | [id](outputs.tf#L33) | Fully qualified service account id. | | | [name](outputs.tf#L41) | Service account name. | | | [service_account](outputs.tf#L49) | Service account resource. | | -| [service_account_credentials](outputs.tf#L54) | Service account json credential templates for uploaded public keys data. | | diff --git a/modules/iam-service-account/main.tf b/modules/iam-service-account/main.tf index 9bf50f05c..b73d49126 100644 --- a/modules/iam-service-account/main.tf +++ b/modules/iam-service-account/main.tf @@ -30,36 +30,13 @@ locals { ? try(google_service_account.service_account[0], null) : try(data.google_service_account.service_account[0], null) ) - service_account_credential_templates = { - for file, _ in local.public_keys_data : file => jsonencode( - { - type : "service_account", - project_id : var.project_id, - private_key_id : split("/", google_service_account_key.upload_key[file].id)[5] - private_key : "REPLACE_ME_WITH_PRIVATE_KEY_DATA" - client_email : local.resource_email_static - client_id : local.service_account.unique_id, - auth_uri : "https://accounts.google.com/o/oauth2/auth", - token_uri : "https://oauth2.googleapis.com/token", - auth_provider_x509_cert_url : "https://www.googleapis.com/oauth2/v1/certs", - client_x509_cert_url : "https://www.googleapis.com/robot/v1/metadata/x509/${urlencode(local.resource_email_static)}" - } - ) - } - public_keys_data = ( - var.public_keys_directory != "" - ? { - for file in fileset("${path.root}/${var.public_keys_directory}", "*.pem") - : file => filebase64("${path.root}/${var.public_keys_directory}/${file}") } - : {} - ) + # universe-related locals universe = try(regex("^([^:]*):[a-z]", var.project_id)[0], "") project_id_no_universe = element(split(":", var.project_id), 1) sa_domain = join(".", compact([local.project_id_no_universe, local.universe])) } - data "google_service_account" "service_account" { count = var.service_account_create ? 0 : 1 project = var.project_id @@ -67,15 +44,16 @@ data "google_service_account" "service_account" { } resource "google_service_account" "service_account" { - count = var.service_account_create ? 1 : 0 - project = var.project_id - account_id = "${local.prefix}${local.name}" - display_name = var.display_name - description = var.description + count = var.service_account_create ? 1 : 0 + project = var.project_id + account_id = "${local.prefix}${local.name}" + display_name = var.display_name + description = var.description + create_ignore_already_exists = var.create_ignore_already_exists } -resource "google_service_account_key" "upload_key" { - for_each = local.public_keys_data - service_account_id = local.service_account.email - public_key_data = each.value +resource "google_tags_tag_binding" "binding" { + for_each = var.tag_bindings + parent = "//iam.googleapis.com/projects/${coalesce(var.project_number, var.project_id)}/serviceAccounts/${local.service_account.unique_id}" + tag_value = each.value } diff --git a/modules/iam-service-account/outputs.tf b/modules/iam-service-account/outputs.tf index 5f4771013..376262a7a 100644 --- a/modules/iam-service-account/outputs.tf +++ b/modules/iam-service-account/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,8 +50,3 @@ output "service_account" { description = "Service account resource." value = local.service_account } - -output "service_account_credentials" { - description = "Service account json credential templates for uploaded public keys data." - value = local.service_account_credential_templates -} diff --git a/modules/iam-service-account/variables.tf b/modules/iam-service-account/variables.tf index 852c87c45..e8ce36fbc 100644 --- a/modules/iam-service-account/variables.tf +++ b/modules/iam-service-account/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,16 @@ * limitations under the License. */ +variable "create_ignore_already_exists" { + description = "If set to true, skip service account creation if a service account with the same email already exists." + type = bool + default = null + validation { + condition = !(var.create_ignore_already_exists == true && var.service_account_create == false) + error_message = "Cannot set create_ignore_already_exists when service_account_create is false." + } +} + variable "description" { description = "Optional description." type = string @@ -125,14 +135,22 @@ variable "project_id" { type = string } -variable "public_keys_directory" { - description = "Path to public keys data files to upload to the service account (should have `.pem` extension)." +variable "project_number" { + description = "Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings." type = string - default = "" + default = null } variable "service_account_create" { description = "Create service account. When set to false, uses a data source to reference an existing service account." type = bool default = true + nullable = false +} + +variable "tag_bindings" { + description = "Tag bindings for this service accounts, in key => tag value id format." + type = map(string) + nullable = false + default = {} } diff --git a/tests/modules/iam_service_account/examples/tags.yaml b/tests/modules/iam_service_account/examples/tags.yaml new file mode 100644 index 000000000..65bc1314e --- /dev/null +++ b/tests/modules/iam_service_account/examples/tags.yaml @@ -0,0 +1,36 @@ +# Copyright 2025 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. + +values: + module.service-account-with-tags.google_service_account.service_account[0]: + account_id: test-service-account + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: test-service-account@project-id.iam.gserviceaccount.com + member: serviceAccount:test-service-account@project-id.iam.gserviceaccount.com + project: project-id + timeouts: null + module.service-account-with-tags.google_tags_tag_binding.binding["foo"]: + tag_value: tagValues/123456789 + timeouts: null + +counts: + google_service_account: 1 + google_tags_tag_binding: 1 + modules: 1 + resources: 2 + +outputs: {}