From 4c0472eb94b8f410ef9092490ef626babf5bb8a7 Mon Sep 17 00:00:00 2001 From: Awais Malik Date: Wed, 19 May 2021 09:16:27 -0700 Subject: [PATCH] feat: extended the postgresql module to accept IAM users and services accounts (#218) * feat: extended the postgresql module to accept IAM users and services accounts. * feat: extended the postgresql module to accept IAM users and services accounts. --- examples/postgresql-public-iam/README.md | 41 +++++ examples/postgresql-public-iam/main.tf | 77 ++++++++++ examples/postgresql-public-iam/outputs.tf | 40 +++++ examples/postgresql-public-iam/variables.tf | 39 +++++ examples/postgresql-public-iam/versions.tf | 19 +++ kitchen.yml | 5 + modules/postgresql/README.md | 1 + modules/postgresql/main.tf | 38 +++++ modules/postgresql/variables.tf | 6 + test/fixtures/postgresql-public-iam/main.tf | 24 +++ .../fixtures/postgresql-public-iam/outputs.tf | 40 +++++ .../postgresql-public-iam/variables.tf | 39 +++++ .../postgresql-public-iam/versions.tf | 20 +++ .../postgresql-public-iam/controls/pg.rb | 141 ++++++++++++++++++ .../postgresql-public-iam/inspec.yml | 28 ++++ test/setup/iam.tf | 4 +- test/setup/main.tf | 6 + test/setup/outputs.tf | 4 + 18 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 examples/postgresql-public-iam/README.md create mode 100644 examples/postgresql-public-iam/main.tf create mode 100644 examples/postgresql-public-iam/outputs.tf create mode 100644 examples/postgresql-public-iam/variables.tf create mode 100644 examples/postgresql-public-iam/versions.tf create mode 100644 test/fixtures/postgresql-public-iam/main.tf create mode 100644 test/fixtures/postgresql-public-iam/outputs.tf create mode 100644 test/fixtures/postgresql-public-iam/variables.tf create mode 100644 test/fixtures/postgresql-public-iam/versions.tf create mode 100644 test/integration/postgresql-public-iam/controls/pg.rb create mode 100644 test/integration/postgresql-public-iam/inspec.yml diff --git a/examples/postgresql-public-iam/README.md b/examples/postgresql-public-iam/README.md new file mode 100644 index 00000000..c7c642ca --- /dev/null +++ b/examples/postgresql-public-iam/README.md @@ -0,0 +1,41 @@ +# Cloud SQL Database Example + +This example shows how to create the public Postgres Cloud SQL database using the Terraform module with IAM accounts. + +## Run Terraform + +Create resources with terraform: + +```bash +terraform init +terraform plan +terraform apply +``` + +To remove all resources created by terraform: + +```bash +terraform destroy +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| authorized\_networks | List of mapped public networks authorized to access to the instances. Default - short range of GCP health-checkers IPs | `list(map(string))` |
[
{
"name": "sample-gcp-health-checkers-range",
"value": "130.211.0.0/28"
}
]
| no | +| cloudsql\_pg\_sa | IAM service account user created for Cloud SQL. | `string` | n/a | yes | +| db\_name | The name of the SQL Database instance | `string` | `"example-postgres-public"` | no | +| project\_id | The ID of the project in which resources will be provisioned. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| name | The name for Cloud SQL instance | +| project\_id | The project to run tests against | +| psql\_conn | The connection name of the master instance to be used in connection strings | +| psql\_user\_pass | The password for the default user. If not set, a random one will be generated and available in the generated\_user\_password output variable. | +| public\_ip\_address | The first public (PRIMARY) IPv4 address assigned for the master instance | + + diff --git a/examples/postgresql-public-iam/main.tf b/examples/postgresql-public-iam/main.tf new file mode 100644 index 00000000..c20e0986 --- /dev/null +++ b/examples/postgresql-public-iam/main.tf @@ -0,0 +1,77 @@ +/** + * Copyright 2019 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. + */ + +provider "google" { + version = "~> 3.22" +} + +provider "google-beta" { + version = "~> 3.5" +} + +provider "null" { + version = "~> 2.1" +} + +provider "random" { + version = "~> 2.2" +} + +module "postgresql-db" { + source = "../../modules/postgresql" + name = var.db_name + random_instance_name = true + database_version = "POSTGRES_9_6" + project_id = var.project_id + zone = "us-central1-c" + region = "us-central1" + tier = "db-f1-micro" + + deletion_protection = false + + ip_configuration = { + ipv4_enabled = true + private_network = null + require_ssl = true + authorized_networks = var.authorized_networks + } + + database_flags = [ + { + name = "cloudsql.iam_authentication" + value = "On" + }, + ] + + additional_users = [ + { + name = "tftest2" + password = "abcdefg" + host = "localhost" + }, + { + name = "tftest3" + password = "abcdefg" + host = "localhost" + }, + ] + + # Supports creation of both IAM Users and IAM Service Accounts with provided emails + iam_user_emails = [ + var.cloudsql_pg_sa, + "dbadmin@goosecorp.org", + ] +} diff --git a/examples/postgresql-public-iam/outputs.tf b/examples/postgresql-public-iam/outputs.tf new file mode 100644 index 00000000..0b745baf --- /dev/null +++ b/examples/postgresql-public-iam/outputs.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2019 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 "project_id" { + value = var.project_id + description = "The project to run tests against" +} + +output "name" { + description = "The name for Cloud SQL instance" + value = module.postgresql-db.instance_name +} + +output "psql_conn" { + value = module.postgresql-db.instance_connection_name + description = "The connection name of the master instance to be used in connection strings" +} + +output "psql_user_pass" { + value = module.postgresql-db.generated_user_password + description = "The password for the default user. If not set, a random one will be generated and available in the generated_user_password output variable." +} + +output "public_ip_address" { + description = "The first public (PRIMARY) IPv4 address assigned for the master instance" + value = module.postgresql-db.public_ip_address +} diff --git a/examples/postgresql-public-iam/variables.tf b/examples/postgresql-public-iam/variables.tf new file mode 100644 index 00000000..9996041d --- /dev/null +++ b/examples/postgresql-public-iam/variables.tf @@ -0,0 +1,39 @@ +/** + * Copyright 2019 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_id" { + description = "The ID of the project in which resources will be provisioned." + type = string +} + +variable "authorized_networks" { + default = [{ + name = "sample-gcp-health-checkers-range" + value = "130.211.0.0/28" + }] + type = list(map(string)) + description = "List of mapped public networks authorized to access to the instances. Default - short range of GCP health-checkers IPs" +} + +variable "db_name" { + description = "The name of the SQL Database instance" + default = "example-postgres-public" +} + +variable "cloudsql_pg_sa" { + type = string + description = "IAM service account user created for Cloud SQL." +} diff --git a/examples/postgresql-public-iam/versions.tf b/examples/postgresql-public-iam/versions.tf new file mode 100644 index 00000000..9b45f9d2 --- /dev/null +++ b/examples/postgresql-public-iam/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2019 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. + */ + +terraform { + required_version = ">=0.12.6" +} diff --git a/kitchen.yml b/kitchen.yml index b557a839..510905ee 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -40,6 +40,11 @@ suites: name: terraform root_module_directory: test/fixtures/postgresql-public command_timeout: 1800 + - name: postgresql-public-iam + driver: + name: terraform + root_module_directory: test/fixtures/postgresql-public-iam + command_timeout: 1800 - name: mysql-private driver: name: terraform diff --git a/modules/postgresql/README.md b/modules/postgresql/README.md index 87a64a09..dae0a89d 100644 --- a/modules/postgresql/README.md +++ b/modules/postgresql/README.md @@ -26,6 +26,7 @@ Note: CloudSQL provides [disk autoresize](https://cloud.google.com/sql/docs/mysq | enable\_default\_db | Enable or disable the creation of the default database | `bool` | `true` | no | | enable\_default\_user | Enable or disable the creation of the default user | `bool` | `true` | no | | encryption\_key\_name | The full path to the encryption key used for the CMEK disk encryption | `string` | `null` | no | +| iam\_user\_emails | A list of IAM users to be created in your cluster | `list(string)` | `[]` | no | | insights\_config | The insights\_config settings for the database. |
object({
query_string_length = number
record_application_tags = bool
record_client_address = bool
})
| `null` | no | | ip\_configuration | The ip configuration for the master instances. |
object({
authorized_networks = list(map(string))
ipv4_enabled = bool
private_network = string
require_ssl = bool
})
|
{
"authorized_networks": [],
"ipv4_enabled": true,
"private_network": null,
"require_ssl": null
}
| no | | maintenance\_window\_day | The day of week (1-7) for the master instance maintenance. | `number` | `1` | no | diff --git a/modules/postgresql/main.tf b/modules/postgresql/main.tf index a469f4fb..521c8795 100644 --- a/modules/postgresql/main.tf +++ b/modules/postgresql/main.tf @@ -26,6 +26,10 @@ locals { databases = { for db in var.additional_databases : db.name => db } users = { for u in var.additional_users : u.name => u } + iam_users = [for iu in var.iam_user_emails : { + email = iu, + is_account_sa = trimsuffix(iu, "gserviceaccount.com") == iu ? false : true + }] } resource "random_id" "suffix" { @@ -173,6 +177,40 @@ resource "google_sql_user" "additional_users" { depends_on = [null_resource.module_depends_on, google_sql_database_instance.default] } +resource "google_project_iam_member" "iam_binding" { + for_each = { + for iu in local.iam_users : + "${iu.email} ${iu.is_account_sa}" => iu + } + project = var.project_id + role = "roles/cloudsql.instanceUser" + member = each.value.is_account_sa ? ( + "serviceAccount:${each.value.email}" + ) : ( + "user:${each.value.email}" + ) +} + +resource "google_sql_user" "iam_account" { + for_each = { + for iu in local.iam_users : + "${iu.email} ${iu.is_account_sa}" => iu + } + project = var.project_id + name = each.value.is_account_sa ? ( + trimsuffix(each.value.email, ".gserviceaccount.com") + ) : ( + each.value.email + ) + instance = google_sql_database_instance.default.name + type = each.value.is_account_sa ? "CLOUD_IAM_SERVICE_ACCOUNT" : "CLOUD_IAM_USER" + + depends_on = [ + null_resource.module_depends_on, + google_project_iam_member.iam_binding, + ] +} + resource "null_resource" "module_depends_on" { triggers = { value = length(var.module_depends_on) diff --git a/modules/postgresql/variables.tf b/modules/postgresql/variables.tf index 8957db7d..dcb0dde4 100644 --- a/modules/postgresql/variables.tf +++ b/modules/postgresql/variables.tf @@ -244,6 +244,12 @@ variable "additional_users" { default = [] } +variable "iam_user_emails" { + description = "A list of IAM users to be created in your cluster" + type = list(string) + default = [] +} + variable "create_timeout" { description = "The optional timout that is applied to limit long database creates." type = string diff --git a/test/fixtures/postgresql-public-iam/main.tf b/test/fixtures/postgresql-public-iam/main.tf new file mode 100644 index 00000000..80d4b41f --- /dev/null +++ b/test/fixtures/postgresql-public-iam/main.tf @@ -0,0 +1,24 @@ +/** + * Copyright 2019 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. + */ + +module "example" { + source = "../../../examples/postgresql-public-iam" + + project_id = var.project_id + authorized_networks = var.authorized_networks + db_name = var.db_name + cloudsql_pg_sa = var.cloudsql_pg_sa +} diff --git a/test/fixtures/postgresql-public-iam/outputs.tf b/test/fixtures/postgresql-public-iam/outputs.tf new file mode 100644 index 00000000..d1e557b2 --- /dev/null +++ b/test/fixtures/postgresql-public-iam/outputs.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2019 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 "project_id" { + value = module.example.project_id + description = "The project to run tests against" +} + +output "name" { + value = module.example.name + description = "The name for Cloud SQL instance" +} + +output "psql_conn" { + value = module.example.psql_conn + description = "The connection name of the master instance to be used in connection strings" +} + +output "psql_user_pass" { + value = module.example.psql_user_pass + description = "The password for the default user. If not set, a random one will be generated and available in the generated_user_password output variable." +} + +output "public_ip_address" { + description = "The first public (PRIMARY) IPv4 address assigned for the master instance" + value = module.example.public_ip_address +} diff --git a/test/fixtures/postgresql-public-iam/variables.tf b/test/fixtures/postgresql-public-iam/variables.tf new file mode 100644 index 00000000..174e84b7 --- /dev/null +++ b/test/fixtures/postgresql-public-iam/variables.tf @@ -0,0 +1,39 @@ +/** + * Copyright 2019 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_id" { + description = "The ID of the project in which resources will be provisioned." + type = string +} + +variable "authorized_networks" { + default = [{ + name = "sample-gcp-health-checkers-range" + value = "130.211.0.0/28" + }] + type = list(map(string)) + description = "List of mapped public networks authorized to access to the instances. Default - short range of GCP health-checkers IPs" +} + +variable "db_name" { + description = "The name of the SQL Database instance" + default = "example-postgres-public-iam" +} + +variable "cloudsql_pg_sa" { + type = string + description = "IAM service account user created for Cloud SQL." +} diff --git a/test/fixtures/postgresql-public-iam/versions.tf b/test/fixtures/postgresql-public-iam/versions.tf new file mode 100644 index 00000000..de0b42a8 --- /dev/null +++ b/test/fixtures/postgresql-public-iam/versions.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2019 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. + */ + +terraform { + required_version = ">=0.12.6" +} + diff --git a/test/integration/postgresql-public-iam/controls/pg.rb b/test/integration/postgresql-public-iam/controls/pg.rb new file mode 100644 index 00000000..80a60762 --- /dev/null +++ b/test/integration/postgresql-public-iam/controls/pg.rb @@ -0,0 +1,141 @@ +# Copyright 2019 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. + +project_id = attribute('project_id') +basename = attribute('name') +db_version = "POSTGRES_9_6" +region = "us-central1" +tier = "db-f1-micro" +public_ip_address = attribute('public_ip_address') + +activation_policy = "ALWAYS" +data_disk_size_gb = 10 +data_disk_type = "PD_SSD" +kind = "sql#settings" +pricing_plan = "PER_USE" +replication_type = "SYNCHRONOUS" +storage_auto_resize = true +storage_auto_resize_limit = 0 + +cloudsql_iam_user = "dbadmin@goosecorp.org" +cloudsql_iam_sa = "cloudsql-pg-sa-01@#{project_id}.iam" + +describe command("gcloud --project='#{project_id}' sql instances describe #{basename} --format=json") do + its(:exit_status) { should eq 0 } + its(:stderr) { should eq '' } + + let!(:data) do + if subject.exit_status == 0 + JSON.parse(subject.stdout) + else + {} + end + end + + describe "postgresql_public_database" do + it "global settings are valid" do + expect(data['settings']['activationPolicy']).to eq "#{activation_policy}" + expect(data['settings']['dataDiskSizeGb']).to eq "#{data_disk_size_gb}" + expect(data['settings']['dataDiskType']).to eq "#{data_disk_type}" + expect(data['settings']['kind']).to eq "#{kind}" + expect(data['settings']['pricingPlan']).to eq "#{pricing_plan}" + expect(data['settings']['replicationType']).to eq "#{replication_type}" + expect(data['settings']['storageAutoResize']).to eq storage_auto_resize + expect(data['settings']['storageAutoResizeLimit']).to eq "#{storage_auto_resize_limit}" + expect(data['settings']['tier']).to eq "#{tier}" + end + + it "backend type is valid" do + expect(data['backendType']).to eq 'SECOND_GEN' + end + + it "database versions is valid" do + expect(data['databaseVersion']).to eq db_version + end + + it "state is valid" do + expect(data['state']).to eq 'RUNNABLE' + end + + it "region is valid" do + expect(data['region']).to eq region + end + + it "gce zone is valid" do + expect(data['gceZone']).to eq "#{region}-c" + end + + it "location preference is valid" do + expect(data['settings']['locationPreference']).to include( + "kind" => "sql#locationPreference", + "zone" => "#{region}-c") + end + + it "maintenance window is valid" do + expect(data['settings']['maintenanceWindow']).to include( + "kind" => "sql#maintenanceWindow", + "day" => 1, + "hour" => 23, + "updateTrack" => "canary") + end + + it "database flags are set" do + expect(data['settings']['databaseFlags']).to include({ + "name" => "cloudsql.iam_authentication", + "value" => "on"}) + end + end + + describe "Postgres SQL pubic instance" do + it "has just one assigned IP address" do + expect(data["ipAddresses"].count).to eq(1) + end + + it "has expected external IP address" do + expect(data["ipAddresses"][0]).to eq( + { + "type" => "PRIMARY", + "ipAddress" => "#{public_ip_address}" + } + ) + end + end +end + +describe command("gcloud --project='#{project_id}' sql users list --instance #{basename} --format=json") do + its(:exit_status) { should eq 0 } + its(:stderr) { should eq '' } + + let!(:data) do + if subject.exit_status == 0 + JSON.parse(subject.stdout) + else + {} + end + end + + describe "postgresql_public_database" do + it "has 1 IAM user" do + expect(data.select {|k,v| k['type'] == "CLOUD_IAM_USER"}.size).to eq 1 + expect(data.select {|k,v| k['name'] == "#{cloudsql_iam_user}"}.size).to eq 1 + end + end + + describe "postgresql_public_database" do + it "has 1 IAM service account user" do + expect(data.select {|k,v| k['type'] == "CLOUD_IAM_SERVICE_ACCOUNT"}.size).to eq 1 + expect(data.select {|k,v| k['name'] == "#{cloudsql_iam_sa}"}.size).to eq 1 + end + end +end diff --git a/test/integration/postgresql-public-iam/inspec.yml b/test/integration/postgresql-public-iam/inspec.yml new file mode 100644 index 00000000..cc3ca618 --- /dev/null +++ b/test/integration/postgresql-public-iam/inspec.yml @@ -0,0 +1,28 @@ +# Copyright 2019 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. + +name: cloud-sql +title: Google Cloud SQL +version: 0.1.0 +attributes: + - name: project_id + type: string + required: true + - name: name + type: string + required: true + - name: public_ip_address + type: string + required: true + diff --git a/test/setup/iam.tf b/test/setup/iam.tf index 4e22fb9c..753ebbac 100644 --- a/test/setup/iam.tf +++ b/test/setup/iam.tf @@ -17,7 +17,9 @@ locals { int_required_roles = [ "roles/cloudsql.admin", - "roles/compute.networkAdmin" + "roles/compute.networkAdmin", + "roles/iam.serviceAccountAdmin", + "roles/resourcemanager.projectIamAdmin", ] } diff --git a/test/setup/main.tf b/test/setup/main.tf index d10efd93..5ffb655a 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -45,5 +45,11 @@ module "project" { "compute.googleapis.com", "servicenetworking.googleapis.com", "sqladmin.googleapis.com", + "iam.googleapis.com", ] } + +resource "google_service_account" "cloudsql_pg_sa" { + project = module.project.project_id + account_id = "cloudsql-pg-sa-01" +} diff --git a/test/setup/outputs.tf b/test/setup/outputs.tf index d7b35403..9ac8cb3f 100644 --- a/test/setup/outputs.tf +++ b/test/setup/outputs.tf @@ -23,3 +23,7 @@ output "sa_key" { sensitive = true } +output "cloudsql_pg_sa" { + value = google_service_account.cloudsql_pg_sa.email + description = "IAM service account user created for Cloud SQL." +}