From 6eeda3da7a859b7efec9356abadb66d7d08d6097 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Wed, 13 Apr 2022 11:09:34 +0200 Subject: [PATCH] Add KMS support --- .gitignore | 1 + .../cloudsql-multiregion/kms.tf | 38 +++++ .../cloudsql-multiregion/main.tf | 140 ++++++++++++++++-- .../cloudsql-multiregion/variables.tf | 39 +++-- 4 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 examples/data-solutions/cloudsql-multiregion/kms.tf diff --git a/.gitignore b/.gitignore index 373948e7..4fb44601 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ fast/stages/**/terraform-*.auto.tfvars.json fast/stages/**/0*.auto.tfvars* **/node_modules fast/stages/**/globals.auto.tfvars.json +cloud_sql_proxy \ No newline at end of file diff --git a/examples/data-solutions/cloudsql-multiregion/kms.tf b/examples/data-solutions/cloudsql-multiregion/kms.tf new file mode 100644 index 00000000..34987f9c --- /dev/null +++ b/examples/data-solutions/cloudsql-multiregion/kms.tf @@ -0,0 +1,38 @@ + + +# 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 +# +# 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. + +module "kms" { + for_each = toset(distinct(values(var.regions))) + source = "../../../modules/kms" + project_id = module.project.project_id + + keyring = { + name = "${var.prefix}-keyring-${each.value}" + location = each.value + } + + keys = { + key = null + } + key_iam = { + key = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}", + "serviceAccount:${module.project.service_accounts.robots.storage}" + ] + } + } +} diff --git a/examples/data-solutions/cloudsql-multiregion/main.tf b/examples/data-solutions/cloudsql-multiregion/main.tf index 9df7303a..6c893753 100644 --- a/examples/data-solutions/cloudsql-multiregion/main.tf +++ b/examples/data-solutions/cloudsql-multiregion/main.tf @@ -14,6 +14,63 @@ * limitations under the License. */ +locals { + data_eng_principals_iam = [ + for k in var.data_eng_principals : + "user:${k}" + ] + + iam = { + # GCS roles + "roles/storage.objectAdmin" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}", + module.service-account-gcs.iam_email, + ] + # CloudSQL + "roles/cloudsql.instanceUser" = concat( + local.data_eng_principals_iam, + [module.service-account-sql.iam_email] + ) + # common roles + "roles/logging.admin" = local.data_eng_principals_iam + "roles/iam.serviceAccountUser" = concat( + local.data_eng_principals_iam + ) + "roles/iam.serviceAccountTokenCreator" = concat( + local.data_eng_principals_iam + ) + # network roles + "roles/compute.networkUser" = [ + "serviceAccount:${module.project.service_accounts.robots.sql}" + ] + } + + # # VPC / Shared VPC variables + # network_subnet_selflink = try( + # module.vpc[0].subnets["${var.region}/subnet"].self_link, + # var.network_config.subnet_self_link + # ) + # shared_vpc_bindings = { + # "roles/compute.networkUser" = [ + # "robot-df", "sa-df-worker" + # ] + # } + # # reassemble in a format suitable for for_each + # shared_vpc_bindings_map = { + # for binding in flatten([ + # for role, members in local.shared_vpc_bindings : [ + # for member in members : { role = role, member = member } + # ] + # ]) : "${binding.role}-${binding.member}" => binding + # } + # shared_vpc_project = try(var.network_config.host_project, null) + # shared_vpc_role_members = { + # robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}" + # sa-df-worker = module.service-account-df.iam_email + # } + # use_shared_vpc = var.network_config != null +} + module "project" { source = "../../../modules/project" name = var.project_id @@ -21,9 +78,19 @@ module "project" { billing_account = try(var.project_create.billing_account_id, null) project_create = var.project_create != null prefix = var.project_create == null ? null : var.prefix + iam = var.project_create != null ? local.iam : {} + iam_additive = var.project_create == null ? local.iam : {} services = [ + "cloudkms.googleapis.com", "servicenetworking.googleapis.com", + "sqladmin.googleapis.com", + "sql-component.googleapis.com", + "storage.googleapis.com", + "storage-component.googleapis.com", ] + service_config = { + disable_on_destroy = false, disable_dependent_services = false + } } module "vpc" { @@ -31,23 +98,72 @@ module "vpc" { project_id = module.project.project_id name = "vpc" psa_config = { - ranges = { cloud-sql = var.cloudsql_psa_range } + ranges = { cloud-sql = var.sql_configuration.psa_range } routes = null } } module "db" { - source = "../../../modules/cloudsql-instance" - project_id = module.project.project_id - network = module.vpc.self_link - name = "${var.prefix}-db" - region = var.regions.primary - database_version = var.database_version - tier = var.tier - + source = "../../../modules/cloudsql-instance" + project_id = module.project.project_id + availability_type = var.sql_configuration.availability_type + encryption_key_name = var.cmek_encryption ? module.kms[var.regions.primary].keys.key.id : null + network = module.vpc.self_link + name = "${var.prefix}-db-04" + region = var.regions.primary + database_version = var.sql_configuration.database_version + tier = var.sql_configuration.tier + flags = { + "cloudsql.iam_authentication" = "on" + } replicas = { - for name, region in var.regions : - name => region - if name != "primary" + for k, v in var.regions : + k => { + region = v, + encryption_key_name = var.cmek_encryption ? module.kms[v].keys.key.id : null + } if k != "primary" + } + users = { + postgres = var.postgres_user_password } } + +resource "google_sql_user" "users" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + name = each.value + instance = module.db.name + type = "CLOUD_IAM_USER" +} + +resource "google_sql_user" "service-account" { + for_each = toset(var.data_eng_principals) + project = module.project.project_id + # Omit the .gserviceaccount.com suffix in the email + name = regex("(.+)(gserviceaccount)", module.service-account-sql.email)[0] + instance = module.db.name + type = "CLOUD_IAM_SERVICE_ACCOUNT" +} + +module "gcs" { + source = "../../../modules/gcs" + project_id = module.project.project_id + prefix = var.prefix + name = "data" + location = var.regions.primary + storage_class = "REGIONAL" + encryption_key = var.cmek_encryption ? module.kms[var.regions.primary].keys["key"].id : null + force_destroy = true +} + +module "service-account-gcs" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-gcs" +} + +module "service-account-sql" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "${var.prefix}-sql" +} diff --git a/examples/data-solutions/cloudsql-multiregion/variables.tf b/examples/data-solutions/cloudsql-multiregion/variables.tf index f4a4a203..b1069e2e 100644 --- a/examples/data-solutions/cloudsql-multiregion/variables.tf +++ b/examples/data-solutions/cloudsql-multiregion/variables.tf @@ -14,18 +14,22 @@ * limitations under the License. */ -variable "cloudsql_psa_range" { - description = "Range used for the Private Service Access." - type = string - default = "10.60.0.0/16" +variable "cmek_encryption" { + description = "Flag to enable CMEK on GCP resources created." + type = bool + default = false } -variable "database_version" { - description = "Database type and version to create." - type = string - default = "POSTGRES_13" +variable "data_eng_principals" { + description = "Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'." + type = list(string) + default = [] } +variable "postgres_user_password" { + description = "`postgres` user password." + type = string +} variable "prefix" { description = "Unique prefix used for resource names. Not used for project if 'project_create' is null." type = string @@ -58,8 +62,19 @@ variable "regions" { } } -variable "tier" { - description = "The machine type to use for the instances. See See https://cloud.google.com/sql/docs/postgres/create-instance#machine-types." - type = string - default = "db-g1-small" + +variable "sql_configuration" { + description = "Cloud SQL configuration" + type = object({ + availability_type = string + database_version = string + psa_range = string + tier = string + }) + default = { + availability_type = "REGIONAL" + database_version = "POSTGRES_13" + psa_range = "10.60.0.0/16" + tier = "db-g1-small" + } }