From da883bab8c5e193e38191abeac93f0a09bff7d67 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 15 Sep 2023 16:05:36 +0200 Subject: [PATCH 1/9] Update kms module key-level IAM --- .../cmek-via-centralized-kms/main.tf | 7 +- modules/kms/README.md | 71 ++++++------ modules/kms/iam.tf | 43 +++++--- modules/kms/main.tf | 27 ++--- modules/kms/tags.tf | 4 +- modules/kms/variables.tf | 103 ++++++------------ tests/modules/kms/examples/basic.yaml | 37 +------ tests/modules/kms/examples/purpose.yaml | 12 +- 8 files changed, 124 insertions(+), 180 deletions(-) diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf index 27fbe99b..fb446e71 100644 --- a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf +++ b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# 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. @@ -106,7 +106,10 @@ module "kms" { name = "${var.prefix}-${var.region}", location = var.region } - keys = { key-gce = null, key-gcs = null } + keys = { + key-gce = {} + key-gcs = {} + } } ############################################################################### diff --git a/modules/kms/README.md b/modules/kms/README.md index a3b2c902..75b49250 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -31,7 +31,7 @@ module "kms" { } keyring = { location = "europe-west1", name = "test" } keyring_create = false - keys = { key-a = null, key-b = null, key-c = null } + keys = { key-a = {}, key-b = {}, key-c = {} } } # tftest skip (uses data sources) ``` @@ -42,26 +42,34 @@ module "kms" { module "kms" { source = "./fabric/modules/kms" project_id = "my-project" - key_iam = { - key-a = { - "roles/cloudkms.admin" = ["user:user3@example.com"] - } + keyring = { + location = "europe-west1" + name = "test" } - key_iam_bindings_additive = { - key-b-am1 = { - key = "key-b" - member = "user:am1@example.com" - role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" - } - } - keyring = { location = "europe-west1", name = "test" } keys = { - key-a = null - key-b = { rotation_period = "604800s", labels = null } - key-c = { rotation_period = null, labels = { env = "test" } } + key-a = { + iam = { + "roles/cloudkms.admin" = ["user:user3@example.com"] + } + } + key-b = { + rotation_period = "604800s" + iam_bindings_additive = { + key-b-iam1 = { + key = "key-b" + member = "user:am1@example.com" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + } + } + } + key-c = { + labels = { + env = "test" + } + } } } -# tftest modules=1 resources=6 +# tftest modules=1 resources=6 inventory=basic.yaml ``` ### Crypto key purpose @@ -70,38 +78,35 @@ module "kms" { module "kms" { source = "./fabric/modules/kms" project_id = "my-project" - key_purpose = { - key-c = { + keyring = { + location = "europe-west1" + name = "test" + } + keys = { + key-a = { purpose = "ASYMMETRIC_SIGN" version_template = { algorithm = "EC_SIGN_P384_SHA384" - protection_level = null + protection_level = "HSM" } } } - keyring = { location = "europe-west1", name = "test" } - keys = { key-a = null, key-b = null, key-c = null } } -# tftest modules=1 resources=4 +# tftest modules=1 resources=2 inventory=purpose.yaml ``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [keyring](variables.tf#L119) | Keyring attributes. | object({…}) | ✓ | | -| [project_id](variables.tf#L142) | Project id where the keyring will be created. | string | ✓ | | +| [keyring](variables.tf#L53) | Keyring attributes. | object({…}) | ✓ | | +| [project_id](variables.tf#L102) | Project id where the keyring will be created. | string | ✓ | | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables.tf#L23) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables.tf#L38) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | -| [key_iam](variables.tf#L53) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [key_iam_bindings](variables.tf#L59) | Key authoritative IAM bindings in {KEY => {BINDING_KEY => {role = ROLE, members = [], condition = {}}}}. | map(object({…})) | | {} | -| [key_iam_bindings_additive](variables.tf#L74) | Key individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | -| [key_purpose](variables.tf#L90) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | map(object({…})) | | {} | -| [key_purpose_defaults](variables.tf#L102) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | object({…}) | | {…} | -| [keyring_create](variables.tf#L127) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | -| [keys](variables.tf#L133) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L147) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | null | +| [keyring_create](variables.tf#L61) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | +| [keys](variables.tf#L67) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L107) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | {} | ## Outputs diff --git a/modules/kms/iam.tf b/modules/kms/iam.tf index ff8279a2..8ac17a56 100644 --- a/modules/kms/iam.tf +++ b/modules/kms/iam.tf @@ -16,25 +16,36 @@ locals { key_iam = flatten([ - for key, roles in var.key_iam : [ - for role, members in roles : { - key = key + for k, v in var.keys : [ + for role, members in v.iam : { + key = k role = role members = members } ] ]) - key_iam_bindings = flatten([ - for key, bindings in var.key_iam_bindings : [ - for binding_key, binding_data in bindings : { - key = key - binding_key = "${key}.${binding_key}" - role = binding_data.role - members = binding_data.members - condition = binding_data.condition + key_iam_bindings = merge([ + for k, v in var.keys : { + for binding_key, data in v.iam_bindings : + binding_key => { + key = k + role = data.role + members = data.members + condition = data.condition } - ] - ]) + } + ]...) + key_iam_bindings_additive = merge([ + for k, v in var.keys : { + for binding_key, data in v.iam_bindings_additive : + binding_key => { + key = k + role = data.role + member = data.member + condition = data.condition + } + } + ]...) } resource "google_kms_key_ring_iam_binding" "authoritative" { @@ -85,9 +96,7 @@ resource "google_kms_crypto_key_iam_binding" "authoritative" { } resource "google_kms_crypto_key_iam_binding" "bindings" { - for_each = { - for binding in local.key_iam_bindings : binding.binding_key => binding - } + for_each = local.key_iam_bindings role = each.value.role crypto_key_id = google_kms_crypto_key.default[each.value.key].id members = each.value.members @@ -102,7 +111,7 @@ resource "google_kms_crypto_key_iam_binding" "bindings" { } resource "google_kms_crypto_key_iam_member" "members" { - for_each = var.key_iam_bindings_additive + for_each = local.key_iam_bindings_additive crypto_key_id = google_kms_crypto_key.default[each.value.key].id role = each.value.role member = each.value.member diff --git a/modules/kms/main.tf b/modules/kms/main.tf index 26624f15..6be7c812 100644 --- a/modules/kms/main.tf +++ b/modules/kms/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -15,11 +15,6 @@ */ locals { - key_purpose = { - for key, attrs in var.keys : key => try( - var.key_purpose[key], var.key_purpose_defaults - ) - } keyring = ( var.keyring_create ? google_kms_key_ring.default.0 @@ -42,17 +37,19 @@ resource "google_kms_key_ring" "default" { } resource "google_kms_crypto_key" "default" { - for_each = var.keys - key_ring = local.keyring.id - name = each.key - rotation_period = try(each.value.rotation_period, null) - labels = try(each.value.labels, null) - purpose = try(local.key_purpose[each.key].purpose, null) + for_each = var.keys + key_ring = local.keyring.id + name = each.key + rotation_period = each.value.rotation_period + labels = each.value.labels + purpose = each.value.purpose + skip_initial_version_creation = each.value.skip_initial_version_creation + dynamic "version_template" { - for_each = local.key_purpose[each.key].version_template == null ? [] : [""] + for_each = each.value.version_template == null ? [] : [""] content { - algorithm = local.key_purpose[each.key].version_template.algorithm - protection_level = local.key_purpose[each.key].version_template.protection_level + algorithm = each.value.version_template.algorithm + protection_level = each.value.version_template.protection_level } } } diff --git a/modules/kms/tags.tf b/modules/kms/tags.tf index 894c28aa..c0955c62 100644 --- a/modules/kms/tags.tf +++ b/modules/kms/tags.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -15,7 +15,7 @@ */ resource "google_tags_tag_binding" "binding" { - for_each = coalesce(var.tag_bindings, {}) + for_each = var.tag_bindings parent = "//cloudresourcemanager.googleapis.com/${local.keyring.id}" tag_value = each.value } diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index 93e42dc4..e5fc59e2 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -50,72 +50,6 @@ variable "iam_bindings_additive" { default = {} } -variable "key_iam" { - description = "Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format." - type = map(map(list(string))) - default = {} -} - -variable "key_iam_bindings" { - description = "Key authoritative IAM bindings in {KEY => {BINDING_KEY => {role = ROLE, members = [], condition = {}}}}." - type = map(object({ - members = list(string) - role = string - condition = optional(object({ - expression = string - title = string - description = optional(string) - })) - })) - nullable = false - default = {} -} - -variable "key_iam_bindings_additive" { - description = "Key individual additive IAM bindings. Keys are arbitrary." - type = map(object({ - key = string - member = string - role = string - condition = optional(object({ - expression = string - title = string - description = optional(string) - })) - })) - nullable = false - default = {} -} - -variable "key_purpose" { - description = "Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required." - type = map(object({ - purpose = string - version_template = object({ - algorithm = string - protection_level = string - }) - })) - default = {} -} - -variable "key_purpose_defaults" { - description = "Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required." - type = object({ - purpose = string - version_template = object({ - algorithm = string - protection_level = string - }) - }) - default = { - purpose = null - version_template = null - } -} - -# cf https://cloud.google.com/kms/docs/locations - variable "keyring" { description = "Keyring attributes." type = object({ @@ -133,10 +67,36 @@ variable "keyring_create" { variable "keys" { description = "Key names and base attributes. Set attributes to null if not needed." type = map(object({ - rotation_period = string - labels = map(string) + rotation_period = optional(string) + labels = optional(map(string)) + purpose = optional(string, "ENCRYPT_DECRYPT") + skip_initial_version_creation = optional(bool, false) + version_template = optional(object({ + algorithm = string + protection_level = optional(string, "SOFTWARE") + })) + + iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + members = list(string) + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) })) - default = {} + default = {} + nullable = false } variable "project_id" { @@ -147,5 +107,6 @@ variable "project_id" { variable "tag_bindings" { description = "Tag bindings for this keyring, in key => tag value id format." type = map(string) - default = null + default = {} + nullable = false } diff --git a/tests/modules/kms/examples/basic.yaml b/tests/modules/kms/examples/basic.yaml index e29297a1..30f40627 100644 --- a/tests/modules/kms/examples/basic.yaml +++ b/tests/modules/kms/examples/basic.yaml @@ -18,37 +18,26 @@ values: name: key-a purpose: ENCRYPT_DECRYPT rotation_period: null - skip_initial_version_creation: null - timeouts: null + skip_initial_version_creation: false module.kms.google_kms_crypto_key.default["key-b"]: labels: null name: key-b purpose: ENCRYPT_DECRYPT rotation_period: 604800s - skip_initial_version_creation: null - timeouts: null + skip_initial_version_creation: false module.kms.google_kms_crypto_key.default["key-c"]: labels: env: test name: key-c purpose: ENCRYPT_DECRYPT rotation_period: null - skip_initial_version_creation: null - timeouts: null - module.kms.google_kms_crypto_key_iam_binding.default["key-a.roles/cloudkms.admin"]: + skip_initial_version_creation: false + module.kms.google_kms_crypto_key_iam_binding.authoritative["key-a.roles/cloudkms.admin"]: condition: [] members: - user:user3@example.com role: roles/cloudkms.admin - ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user4@example.com"] - : condition: [] - member: user:user4@example.com - role: roles/cloudkms.cryptoKeyEncrypterDecrypter - ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user5@example.com"] - : condition: [] - member: user:user5@example.com - role: roles/cloudkms.cryptoKeyEncrypterDecrypter - module.kms.google_kms_crypto_key_iam_member.members["key-b-am1"]: + module.kms.google_kms_crypto_key_iam_member.members["key-b-iam1"]: condition: [] member: user:am1@example.com role: roles/cloudkms.cryptoKeyEncrypterDecrypter @@ -56,23 +45,9 @@ values: location: europe-west1 name: test project: my-project - timeouts: null - module.kms.google_kms_key_ring_iam_member.default["roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user1@example.com"]: - condition: [] - member: user:user1@example.com - role: roles/cloudkms.cryptoKeyEncrypterDecrypter - module.kms.google_kms_key_ring_iam_member.default["roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user2@example.com"]: - condition: [] - member: user:user2@example.com - role: roles/cloudkms.cryptoKeyEncrypterDecrypter counts: google_kms_crypto_key: 3 google_kms_crypto_key_iam_binding: 1 - google_kms_crypto_key_iam_member: 3 + google_kms_crypto_key_iam_member: 1 google_kms_key_ring: 1 - google_kms_key_ring_iam_member: 2 - modules: 1 - resources: 10 - -outputs: {} diff --git a/tests/modules/kms/examples/purpose.yaml b/tests/modules/kms/examples/purpose.yaml index c08779b2..9f97ad52 100644 --- a/tests/modules/kms/examples/purpose.yaml +++ b/tests/modules/kms/examples/purpose.yaml @@ -15,25 +15,19 @@ values: module.kms.google_kms_crypto_key.default["key-a"]: name: key-a - purpose: ENCRYPT_DECRYPT - module.kms.google_kms_crypto_key.default["key-b"]: - name: key-b - purpose: ENCRYPT_DECRYPT - module.kms.google_kms_crypto_key.default["key-c"]: - name: key-c purpose: ASYMMETRIC_SIGN version_template: - algorithm: EC_SIGN_P384_SHA384 - protection_level: SOFTWARE + protection_level: HSM module.kms.google_kms_key_ring.default[0]: location: europe-west1 name: test project: my-project counts: - google_kms_crypto_key: 3 + google_kms_crypto_key: 1 google_kms_key_ring: 1 modules: 1 - resources: 4 + resources: 2 outputs: {} From 9c878dc9cf95dc6e7d16e67e785412bd60aa7552 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 18:58:07 +0200 Subject: [PATCH 2/9] Fix tests for new KMS IAM interface --- .../gcs-to-bq-with-least-privileges/kms.tf | 41 +++++++-------- .../data-solutions/shielded-folder/kms.tf | 18 +++---- .../shielded-folder/variables.tf | 41 +++++++++++---- blueprints/gke/binauthz/main.tf | 18 +++---- fast/stages/2-security/core-dev.tf | 6 +-- fast/stages/2-security/core-prod.tf | 6 +-- fast/stages/2-security/main.tf | 29 ++++------- fast/stages/2-security/variables.tf | 51 ++++++++++++------- modules/cloudsql-instance/README.md | 11 ++-- 9 files changed, 113 insertions(+), 108 deletions(-) diff --git a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf index 5e616630..722016b7 100644 --- a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf +++ b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# 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. @@ -21,26 +21,27 @@ module "kms" { location = var.region } keys = { - key-df = null - key-gcs = null - key-bq = null - } - key_iam = { - key-gcs = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.storage}" - ] - }, - key-bq = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.bq}" - ] - }, key-df = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.dataflow}", - "serviceAccount:${module.project.service_accounts.robots.compute}", - ] + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.dataflow}", + "serviceAccount:${module.project.service_accounts.robots.compute}", + ] + } + } + key-gcs = { + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.storage}" + ] + } + } + key-bq = { + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.bq}" + ] + } } } } diff --git a/blueprints/data-solutions/shielded-folder/kms.tf b/blueprints/data-solutions/shielded-folder/kms.tf index 9953d458..4a634fcc 100644 --- a/blueprints/data-solutions/shielded-folder/kms.tf +++ b/blueprints/data-solutions/shielded-folder/kms.tf @@ -17,12 +17,17 @@ # tfdoc:file:description Security project, Cloud KMS and Secret Manager resources. locals { + # list of locations with keys kms_locations = distinct(flatten([ for k, v in var.kms_keys : v.locations ])) + # map { location -> { key_name -> key_details } } kms_locations_keys = { - for loc in local.kms_locations : loc => { - for k, v in var.kms_keys : k => v if contains(v.locations, loc) + for loc in local.kms_locations : + loc => { + for k, v in var.kms_keys : + k => v + if contains(v.locations, loc) } } kms_log_locations = distinct(flatten([ @@ -30,17 +35,14 @@ locals { ])) kms_log_sink_keys = { "storage" = { - labels = {} locations = [var.log_locations.storage] rotation_period = "7776000s" } "bq" = { - labels = {} locations = [var.log_locations.bq] rotation_period = "7776000s" } "pubsub" = { - labels = {} locations = [var.log_locations.pubsub] rotation_period = "7776000s" } @@ -88,12 +90,6 @@ module "sec-kms" { location = each.key name = "sec-${each.key}" } - key_iam = { - for k, v in local.kms_locations_keys[each.key] : k => v.iam - } - key_iam_bindings_additive = { - for k, v in local.kms_locations_keys[each.key] : k => v.iam_bindings_additive - } keys = local.kms_locations_keys[each.key] } diff --git a/blueprints/data-solutions/shielded-folder/variables.tf b/blueprints/data-solutions/shielded-folder/variables.tf index 5bb80d57..03fea7c4 100644 --- a/blueprints/data-solutions/shielded-folder/variables.tf +++ b/blueprints/data-solutions/shielded-folder/variables.tf @@ -75,11 +75,35 @@ variable "groups" { variable "kms_keys" { description = "KMS keys to create, keyed by name." type = map(object({ - iam = optional(map(list(string)), {}) - iam_bindings_additive = optional(map(map(any)), {}) - labels = optional(map(string), {}) - locations = optional(list(string), ["global", "europe", "europe-west1"]) - rotation_period = optional(string, "7776000s") + labels = optional(map(string)) + locations = optional(list(string), ["global", "europe", "europe-west1"]) + rotation_period = optional(string, "7776000s") + purpose = optional(string, "ENCRYPT_DECRYPT") + skip_initial_version_creation = optional(bool, false) + version_template = optional(object({ + algorithm = string + protection_level = optional(string, "SOFTWARE") + })) + + iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + members = list(string) + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + })) default = {} } @@ -92,12 +116,7 @@ variable "log_locations" { logging = optional(string, "global") pubsub = optional(string, "global") }) - default = { - bq = "europe" - storage = "europe" - logging = "global" - pubsub = null - } + default = {} nullable = false } diff --git a/blueprints/gke/binauthz/main.tf b/blueprints/gke/binauthz/main.tf index 2eac7c56..8cff68a0 100644 --- a/blueprints/gke/binauthz/main.tf +++ b/blueprints/gke/binauthz/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -115,20 +115,16 @@ module "kms" { project_id = module.project.project_id keyring = { location = var.region, name = "test-keyring" } keyring_create = true - keys = { test-key = null } - key_purpose = { + keys = { test-key = { purpose = "ASYMMETRIC_SIGN" version_template = { - algorithm = "RSA_SIGN_PKCS1_4096_SHA512" - protection_level = null + algorithm = "RSA_SIGN_PKCS1_4096_SHA512" + } + iam = { + "roles/cloudkms.publicKeyViewer" = [module.image_cb_sa.iam_email] + "roles/cloudkms.signer" = [module.image_cb_sa.iam_email] } - } - } - key_iam = { - test-key = { - "roles/cloudkms.publicKeyViewer" = [module.image_cb_sa.iam_email] - "roles/cloudkms.signer" = [module.image_cb_sa.iam_email] } } } diff --git a/fast/stages/2-security/core-dev.tf b/fast/stages/2-security/core-dev.tf index 1b494947..2197a2ca 100644 --- a/fast/stages/2-security/core-dev.tf +++ b/fast/stages/2-security/core-dev.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -45,10 +45,6 @@ module "dev-sec-kms" { location = each.key name = "dev-${each.key}" } - # rename to `key_iam` to switch to authoritative bindings - key_iam = { - for k, v in local.kms_locations_keys[each.key] : k => v.iam - } keys = local.kms_locations_keys[each.key] } diff --git a/fast/stages/2-security/core-prod.tf b/fast/stages/2-security/core-prod.tf index 559ff32f..276cb432 100644 --- a/fast/stages/2-security/core-prod.tf +++ b/fast/stages/2-security/core-prod.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -44,10 +44,6 @@ module "prod-sec-kms" { location = each.key name = "prod-${each.key}" } - # rename to `key_iam` to switch to authoritative bindings - key_iam = { - for k, v in local.kms_locations_keys[each.key] : k => v.iam - } keys = local.kms_locations_keys[each.key] } diff --git a/fast/stages/2-security/main.tf b/fast/stages/2-security/main.tf index 13078d12..7d334476 100644 --- a/fast/stages/2-security/main.tf +++ b/fast/stages/2-security/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -15,28 +15,17 @@ */ locals { - kms_keys = { - for k, v in var.kms_keys : k => { - iam = coalesce(v.iam, {}) - labels = coalesce(v.labels, {}) - locations = ( - v.locations == null - ? var.kms_defaults.locations - : v.locations - ) - rotation_period = ( - v.rotation_period == null - ? var.kms_defaults.rotation_period - : v.rotation_period - ) - } - } + # list of locations with keys kms_locations = distinct(flatten([ - for k, v in local.kms_keys : v.locations + for k, v in var.kms_keys : v.locations ])) + # map { location -> { key_name -> key_details } } kms_locations_keys = { - for loc in local.kms_locations : loc => { - for k, v in local.kms_keys : k => v if contains(v.locations, loc) + for loc in local.kms_locations : + loc => { + for k, v in var.kms_keys : + k => v + if contains(v.locations, loc) } } project_services = [ diff --git a/fast/stages/2-security/variables.tf b/fast/stages/2-security/variables.tf index f798de78..fa439c8c 100644 --- a/fast/stages/2-security/variables.tf +++ b/fast/stages/2-security/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -58,27 +58,40 @@ variable "groups" { } } -variable "kms_defaults" { - description = "Defaults used for KMS keys." - type = object({ - locations = list(string) - rotation_period = string - }) - default = { - locations = ["europe", "europe-west1", "europe-west3", "global"] - rotation_period = "7776000s" - } -} - variable "kms_keys" { - description = "KMS keys to create, keyed by name. Null attributes will be interpolated with defaults." + description = "KMS keys to create, keyed by name." type = map(object({ - iam = map(list(string)) - labels = map(string) - locations = list(string) - rotation_period = string + rotation_period = optional(string, "7776000s") + labels = optional(map(string)) + locations = optional(list(string), ["europe", "europe-west1", "europe-west3", "global"]) + purpose = optional(string, "ENCRYPT_DECRYPT") + skip_initial_version_creation = optional(bool, false) + version_template = optional(object({ + algorithm = string + protection_level = optional(string, "SOFTWARE") + })) + + iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + members = list(string) + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) })) - default = {} + default = {} + nullable = false } variable "organization" { diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index 00cf5ded..74afa419 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -116,13 +116,12 @@ module "kms" { location = var.region } keys = { - key-sql = null - } - key_iam = { key-sql = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.sqladmin}" - ] + iam = { + "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ + "serviceAccount:${module.project.service_accounts.robots.sqladmin}" + ] + } } } } From 121598dbea2e72f9b4df57e5f0ca60c8bd7d0990 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 19:55:37 +0200 Subject: [PATCH 3/9] Move FAST security delegated admins to iam_bindings_additive --- fast/stages/2-security/core-dev.tf | 27 ++++++--------------------- fast/stages/2-security/core-prod.tf | 27 ++++++--------------------- fast/stages/2-security/main.tf | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/fast/stages/2-security/core-dev.tf b/fast/stages/2-security/core-dev.tf index 2197a2ca..57f3120b 100644 --- a/fast/stages/2-security/core-dev.tf +++ b/fast/stages/2-security/core-dev.tf @@ -33,6 +33,12 @@ module "dev-sec-project" { iam = { "roles/cloudkms.viewer" = local.dev_kms_restricted_admins } + iam_bindings_additive = { + for member in local.dev_kms_restricted_admins : + "kms_restricted_admin.${member}" => merge(local.kms_restricted_admin_template, { + member = member + }) + } labels = { environment = "dev", team = "security" } services = local.project_services } @@ -47,24 +53,3 @@ module "dev-sec-kms" { } keys = local.kms_locations_keys[each.key] } - -# TODO(ludo): add support for conditions to Fabric modules - -resource "google_project_iam_member" "dev_key_admin_delegated" { - for_each = toset(local.dev_kms_restricted_admins) - project = module.dev-sec-project.project_id - role = "roles/cloudkms.admin" - member = each.key - condition { - title = "kms_sa_delegated_grants" - description = "Automation service account delegated grants." - expression = format( - "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) && resource.type == 'cloudkms.googleapis.com/CryptoKey'", - join(",", formatlist("'%s'", [ - "roles/cloudkms.cryptoKeyEncrypterDecrypter", - "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation" - ])) - ) - } - depends_on = [module.dev-sec-project] -} diff --git a/fast/stages/2-security/core-prod.tf b/fast/stages/2-security/core-prod.tf index 276cb432..c0b35dd6 100644 --- a/fast/stages/2-security/core-prod.tf +++ b/fast/stages/2-security/core-prod.tf @@ -32,6 +32,12 @@ module "prod-sec-project" { iam = { "roles/cloudkms.viewer" = local.prod_kms_restricted_admins } + iam_bindings_additive = { + for member in local.prod_kms_restricted_admins : + "kms_restricted_admin.${member}" => merge(local.kms_restricted_admin_template, { + member = member + }) + } labels = { environment = "prod", team = "security" } services = local.project_services } @@ -46,24 +52,3 @@ module "prod-sec-kms" { } keys = local.kms_locations_keys[each.key] } - -# TODO(ludo): add support for conditions to Fabric modules - -resource "google_project_iam_member" "prod_key_admin_delegated" { - for_each = toset(local.prod_kms_restricted_admins) - project = module.prod-sec-project.project_id - role = "roles/cloudkms.admin" - member = each.key - condition { - title = "kms_sa_delegated_grants" - description = "Automation service account delegated grants." - expression = format( - "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) && resource.type == 'cloudkms.googleapis.com/CryptoKey'", - join(",", formatlist("'%s'", [ - "roles/cloudkms.cryptoKeyEncrypterDecrypter", - "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation" - ])) - ) - } - depends_on = [module.prod-sec-project] -} diff --git a/fast/stages/2-security/main.tf b/fast/stages/2-security/main.tf index 7d334476..70799011 100644 --- a/fast/stages/2-security/main.tf +++ b/fast/stages/2-security/main.tf @@ -15,6 +15,25 @@ */ locals { + # additive IAM binding for delegated KMS admins + kms_restricted_admin_template = { + role = "roles/cloudkms.admin" + condition = { + title = "kms_sa_delegated_grants" + description = "Automation service account delegated grants." + expression = format( + <<-EOT + api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) && + resource.type == 'cloudkms.googleapis.com/CryptoKey' + EOT + , join(",", formatlist("'%s'", [ + "roles/cloudkms.cryptoKeyEncrypterDecrypter", + "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation" + ])) + ) + } + } + # list of locations with keys kms_locations = distinct(flatten([ for k, v in var.kms_keys : v.locations From 960e015b42f411b170915a395ba99f31534927d8 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 20:05:56 +0200 Subject: [PATCH 4/9] Fix FAST tests --- fast/stages/2-security/core-dev.tf | 4 ++-- fast/stages/2-security/core-prod.tf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fast/stages/2-security/core-dev.tf b/fast/stages/2-security/core-dev.tf index 57f3120b..6f71318d 100644 --- a/fast/stages/2-security/core-dev.tf +++ b/fast/stages/2-security/core-dev.tf @@ -16,11 +16,11 @@ locals { dev_kms_restricted_admins = [ - for sa in compact([ + for sa in distinct(compact([ var.service_accounts.data-platform-dev, var.service_accounts.project-factory-dev, var.service_accounts.project-factory-prod - ]) : "serviceAccount:${sa}" + ])) : "serviceAccount:${sa}" ] } diff --git a/fast/stages/2-security/core-prod.tf b/fast/stages/2-security/core-prod.tf index c0b35dd6..1d536249 100644 --- a/fast/stages/2-security/core-prod.tf +++ b/fast/stages/2-security/core-prod.tf @@ -16,10 +16,10 @@ locals { prod_kms_restricted_admins = [ - for sa in compact([ + for sa in distinct(compact([ var.service_accounts.data-platform-prod, var.service_accounts.project-factory-prod - ]) : "serviceAccount:${sa}" + ])) : "serviceAccount:${sa}" ] } From ad14a7d4150d1e6237d64844aab4061d4b0a023f Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 20:07:37 +0200 Subject: [PATCH 5/9] Update READMEs --- .../data-solutions/shielded-folder/README.md | 18 ++++++------- fast/stages/2-security/README.md | 25 ++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/blueprints/data-solutions/shielded-folder/README.md b/blueprints/data-solutions/shielded-folder/README.md index ed177d27..72a6b69f 100644 --- a/blueprints/data-solutions/shielded-folder/README.md +++ b/blueprints/data-solutions/shielded-folder/README.md @@ -159,18 +159,18 @@ terraform apply |---|---|:---:|:---:|:---:| | [access_policy_config](variables.tf#L17) | Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder. | object({…}) | ✓ | | | [folder_config](variables.tf#L49) | Provide 'folder_create' values if folder creation is needed, uses existing 'folder_id' otherwise. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | -| [organization](variables.tf#L129) | Organization details. | object({…}) | ✓ | | -| [prefix](variables.tf#L137) | Prefix used for resources that need unique names. | string | ✓ | | -| [project_config](variables.tf#L142) | Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | +| [organization](variables.tf#L148) | Organization details. | object({…}) | ✓ | | +| [prefix](variables.tf#L156) | Prefix used for resources that need unique names. | string | ✓ | | +| [project_config](variables.tf#L161) | Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | | | [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | string | | "data" | | [enable_features](variables.tf#L35) | Flag to enable features on the solution. | object({…}) | | {…} | | [groups](variables.tf#L65) | User groups. | object({…}) | | {} | -| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | map(object({…})) | | {} | -| [log_locations](variables.tf#L87) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | -| [log_sinks](variables.tf#L104) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | -| [vpc_sc_access_levels](variables.tf#L162) | VPC SC access level definitions. | map(object({…})) | | {} | -| [vpc_sc_egress_policies](variables.tf#L191) | VPC SC egress policy definitions. | map(object({…})) | | {} | -| [vpc_sc_ingress_policies](variables.tf#L211) | VPC SC ingress policy definitions. | map(object({…})) | | {} | +| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | map(object({…})) | | {} | +| [log_locations](variables.tf#L111) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | +| [log_sinks](variables.tf#L123) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | +| [vpc_sc_access_levels](variables.tf#L181) | VPC SC access level definitions. | map(object({…})) | | {} | +| [vpc_sc_egress_policies](variables.tf#L210) | VPC SC egress policy definitions. | map(object({…})) | | {} | +| [vpc_sc_ingress_policies](variables.tf#L230) | VPC SC ingress policy definitions. | map(object({…})) | | {} | ## Outputs diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md index e28aac7b..9d47bdaf 100644 --- a/fast/stages/2-security/README.md +++ b/fast/stages/2-security/README.md @@ -284,13 +284,12 @@ Some references that might be useful in setting up this stage: - ## Files | name | description | modules | resources | |---|---|---|---| -| [core-dev.tf](./core-dev.tf) | None | kms · project | google_project_iam_member | -| [core-prod.tf](./core-prod.tf) | None | kms · project | google_project_iam_member | +| [core-dev.tf](./core-dev.tf) | None | kms · project | | +| [core-prod.tf](./core-prod.tf) | None | kms · project | | | [main.tf](./main.tf) | Module-level locals and resources. | | | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | | [variables.tf](./variables.tf) | Module variables. | | | @@ -303,17 +302,16 @@ Some references that might be useful in setting up this stage: | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | | [folder_ids](variables.tf#L38) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L84) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L100) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [service_accounts](variables.tf#L111) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman | +| [organization](variables.tf#L97) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L113) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [service_accounts](variables.tf#L124) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman | | [groups](variables.tf#L46) | Group names to grant organization-level permissions. | map(string) | | {…} | 0-bootstrap | -| [kms_defaults](variables.tf#L61) | Defaults used for KMS keys. | object({…}) | | {…} | | -| [kms_keys](variables.tf#L73) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…})) | | {} | | -| [outputs_location](variables.tf#L94) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [vpc_sc_access_levels](variables.tf#L122) | VPC SC access level definitions. | map(object({…})) | | {} | | -| [vpc_sc_egress_policies](variables.tf#L151) | VPC SC egress policy definitions. | map(object({…})) | | {} | | -| [vpc_sc_ingress_policies](variables.tf#L171) | VPC SC ingress policy definitions. | map(object({…})) | | {} | | -| [vpc_sc_perimeters](variables.tf#L192) | VPC SC regular perimeter definitions. | object({…}) | | {} | | +| [kms_keys](variables.tf#L61) | KMS keys to create, keyed by name. | map(object({…})) | | {} | | +| [outputs_location](variables.tf#L107) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | +| [vpc_sc_access_levels](variables.tf#L135) | VPC SC access level definitions. | map(object({…})) | | {} | | +| [vpc_sc_egress_policies](variables.tf#L164) | VPC SC egress policy definitions. | map(object({…})) | | {} | | +| [vpc_sc_ingress_policies](variables.tf#L184) | VPC SC ingress policy definitions. | map(object({…})) | | {} | | +| [vpc_sc_perimeters](variables.tf#L205) | VPC SC regular perimeter definitions. | object({…}) | | {} | | ## Outputs @@ -322,5 +320,4 @@ Some references that might be useful in setting up this stage: | [kms_keys](outputs.tf#L59) | KMS key ids. | | | | [stage_perimeter_projects](outputs.tf#L64) | Security project numbers. They can be added to perimeter resources. | | | | [tfvars](outputs.tf#L74) | Terraform variable files for the following stages. | ✓ | | - From 45203fe86ccbf92cf791230a879d9920d0e963fd Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 23:33:56 +0200 Subject: [PATCH 6/9] Make kms iam non-nullable --- modules/kms/README.md | 14 +++++++------- modules/kms/variables.tf | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/kms/README.md b/modules/kms/README.md index 75b49250..ddbf4b5c 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -99,14 +99,14 @@ module "kms" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [keyring](variables.tf#L53) | Keyring attributes. | object({…}) | ✓ | | -| [project_id](variables.tf#L102) | Project id where the keyring will be created. | string | ✓ | | +| [keyring](variables.tf#L54) | Keyring attributes. | object({…}) | ✓ | | +| [project_id](variables.tf#L103) | Project id where the keyring will be created. | string | ✓ | | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_bindings](variables.tf#L23) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | -| [iam_bindings_additive](variables.tf#L38) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | -| [keyring_create](variables.tf#L61) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | -| [keys](variables.tf#L67) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L107) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | {} | +| [iam_bindings](variables.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables.tf#L39) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | +| [keyring_create](variables.tf#L62) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | +| [keys](variables.tf#L68) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L108) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | {} | ## Outputs diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index e5fc59e2..30861764 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -18,6 +18,7 @@ variable "iam" { description = "Keyring IAM bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} + nullable = false } variable "iam_bindings" { From 9d61c6e26d6ffc673bdd4b89e808dcefab6786ba Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 16 Sep 2023 23:52:07 +0200 Subject: [PATCH 7/9] Update IAM for pubsub topics and subscriptions --- modules/pubsub/README.md | 61 +++--- modules/pubsub/iam.tf | 140 ++++++++++++++ modules/pubsub/main.tf | 114 ++++------- modules/pubsub/outputs.tf | 14 +- modules/pubsub/variables.tf | 180 ++++++++++-------- tests/modules/pubsub/examples/simple.yaml | 4 +- .../pubsub/examples/subscription-iam.yaml | 4 +- .../pubsub/examples/subscriptions.yaml | 12 +- 8 files changed, 317 insertions(+), 212 deletions(-) create mode 100644 modules/pubsub/iam.tf diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md index 8dd9e5ff..6e706d9e 100644 --- a/modules/pubsub/README.md +++ b/modules/pubsub/README.md @@ -61,16 +61,10 @@ module "pubsub" { project_id = "my-project" name = "my-topic" subscriptions = { - test-pull = null + test-pull = {} test-pull-override = { - labels = { test = "override" } - options = { - ack_deadline_seconds = null - message_retention_duration = null - retain_acked_messages = true - expiration_policy_ttl = null - filter = null - } + labels = { test = "override" } + retain_acked_messages = true } } } @@ -87,13 +81,10 @@ module "pubsub" { project_id = "my-project" name = "my-topic" subscriptions = { - test-push = null - } - push_configs = { test-push = { - endpoint = "https://example.com/foo" - attributes = null - oidc_token = null + push = { + endpoint = "https://example.com/foo" + } } } } @@ -110,14 +101,13 @@ module "pubsub" { project_id = "my-project" name = "my-topic" subscriptions = { - test-bigquery = null - } - bigquery_subscription_configs = { test-bigquery = { - table = "my_project_id:my_dataset.my_table" - use_topic_schema = true - write_metadata = false - drop_unknown_fields = true + bigquery = { + table = "my_project_id:my_dataset.my_table" + use_topic_schema = true + write_metadata = false + drop_unknown_fields = true + } } } } @@ -134,17 +124,16 @@ module "pubsub" { project_id = "my-project" name = "my-topic" subscriptions = { - test-cloudstorage = null - } - cloud_storage_subscription_configs = { test-cloudstorage = { - bucket = "my-bucket" - filename_prefix = "test_prefix" - filename_suffix = "test_suffix" - max_duration = "100s" - max_bytes = 1000 - avro_config = { - write_metadata = true + cloud_storage = { + bucket = "my-bucket" + filename_prefix = "test_prefix" + filename_suffix = "test_suffix" + max_duration = "100s" + max_bytes = 1000 + avro_config = { + write_metadata = true + } } } } @@ -159,12 +148,10 @@ module "pubsub" { project_id = "my-project" name = "my-topic" subscriptions = { - test-1 = null - test-1 = null - } - subscription_iam = { test-1 = { - "roles/pubsub.subscriber" = ["user:user1@ludomagno.net"] + iam = { + "roles/pubsub.subscriber" = ["user:user1@example.com"] + } } } } diff --git a/modules/pubsub/iam.tf b/modules/pubsub/iam.tf new file mode 100644 index 00000000..4e39b43a --- /dev/null +++ b/modules/pubsub/iam.tf @@ -0,0 +1,140 @@ +/** + * 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 authoritative. + * 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 { + subscription_iam = flatten([ + for k, v in var.subscriptions : [ + for role, members in v.iam : { + subscription = k + role = role + members = members + } + ] + ]) + subscription_iam_bindings = merge([ + for k, v in var.subscriptions : { + for binding_key, data in v.iam_bindings : + binding_key => { + subscription = k + role = data.role + members = data.members + condition = data.condition + } + } + ]...) + subscription_iam_bindings_additive = merge([ + for k, v in var.subscriptions : { + for binding_key, data in v.iam_bindings_additive : + binding_key => { + subscription = k + role = data.role + member = data.member + condition = data.condition + } + } + ]...) +} + +moved { + from = google_pubsub_topic_iam_binding.default + to = google_pubsub_topic_iam_binding.authoritative +} + +resource "google_pubsub_topic_iam_binding" "authoritative" { + for_each = var.iam + project = var.project_id + topic = google_pubsub_topic.default.name + role = each.key + members = each.value +} + +resource "google_pubsub_topic_iam_binding" "bindings" { + for_each = var.iam_bindings + topic = google_pubsub_topic.default.name + role = each.value.role + members = each.value.members + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +resource "google_pubsub_topic_iam_member" "bindings" { + for_each = var.iam_bindings_additive + topic = google_pubsub_topic.default.name + role = each.value.role + member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +moved { + from = google_pubsub_subscription_iam_binding.default + to = google_pubsub_subscription_iam_binding.authoritative +} + +resource "google_pubsub_subscription_iam_binding" "authoritative" { + for_each = { + for binding in local.subscription_iam : + "${binding.subscription}.${binding.role}" => binding + } + project = var.project_id + subscription = google_pubsub_subscription.default[each.value.subscription].name + role = each.value.role + members = each.value.members +} + +resource "google_pubsub_subscription_iam_binding" "bindings" { + for_each = local.subscription_iam_bindings + project = var.project_id + subscription = google_pubsub_subscription.default[each.value.subscription].name + role = each.value.role + members = each.value.members + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +resource "google_pubsub_subscription_iam_member" "members" { + for_each = local.subscription_iam_bindings_additive + project = var.project_id + subscription = google_pubsub_subscription.default[each.value.subscription].name + role = each.value.role + member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} diff --git a/modules/pubsub/main.tf b/modules/pubsub/main.tf index 2a7f873f..de065029 100644 --- a/modules/pubsub/main.tf +++ b/modules/pubsub/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -15,27 +15,6 @@ */ locals { - sub_iam_members = flatten([ - for sub, roles in var.subscription_iam : [ - for role, members in roles : { - sub = sub - role = role - members = members - } - ] - ]) - metadata_config = { - for k, v in var.cloud_storage_subscription_configs : k => v.avro_config - } - oidc_config = { - for k, v in var.push_configs : k => v.oidc_token - } - subscriptions = { - for k, v in var.subscriptions : k => { - labels = try(v.labels, v, null) == null ? var.labels : v.labels - options = try(v.options, v, null) == null ? var.defaults : v.options - } - } topic_id_static = "projects/${var.project_id}/topics/${var.name}" } @@ -70,94 +49,73 @@ resource "google_pubsub_topic" "default" { } } -resource "google_pubsub_topic_iam_binding" "default" { - for_each = var.iam - project = var.project_id - topic = google_pubsub_topic.default.name - role = each.key - members = each.value -} - resource "google_pubsub_subscription" "default" { - for_each = local.subscriptions - project = var.project_id - name = each.key - topic = google_pubsub_topic.default.name - labels = each.value.labels - ack_deadline_seconds = each.value.options.ack_deadline_seconds - message_retention_duration = each.value.options.message_retention_duration - retain_acked_messages = each.value.options.retain_acked_messages - filter = each.value.options.filter + for_each = var.subscriptions + project = var.project_id + name = each.key + topic = google_pubsub_topic.default.name + labels = each.value.labels + ack_deadline_seconds = each.value.ack_deadline_seconds + message_retention_duration = each.value.message_retention_duration + retain_acked_messages = each.value.retain_acked_messages + filter = each.value.filter + enable_message_ordering = each.value.enable_message_ordering + enable_exactly_once_delivery = each.value.enable_exactly_once_delivery dynamic "expiration_policy" { - for_each = each.value.options.expiration_policy_ttl == null ? [] : [""] + for_each = each.value.expiration_policy_ttl == null ? [] : [""] content { - ttl = each.value.options.expiration_policy_ttl + ttl = each.value.expiration_policy_ttl } } dynamic "dead_letter_policy" { - for_each = try(var.dead_letter_configs[each.key], null) == null ? [] : [""] + for_each = each.value.dead_letter_policy == null ? [] : [""] content { - dead_letter_topic = var.dead_letter_configs[each.key].topic - max_delivery_attempts = var.dead_letter_configs[each.key].max_delivery_attempts + dead_letter_topic = each.value.dead_letter_policy.topic + max_delivery_attempts = each.value.dead_letter_policy.max_delivery_attempts } } dynamic "push_config" { - for_each = try(var.push_configs[each.key], null) == null ? [] : [""] + for_each = each.value.push == null ? [] : [""] content { - push_endpoint = var.push_configs[each.key].endpoint - attributes = var.push_configs[each.key].attributes + push_endpoint = each.value.push.endpoint + attributes = each.value.push.attributes dynamic "oidc_token" { - for_each = ( - local.oidc_config[each.key] == null ? [] : [""] - ) + for_each = each.value.push.oidc_token == null ? [] : [""] content { - service_account_email = local.oidc_config[each.key].service_account_email - audience = local.oidc_config[each.key].audience + service_account_email = each.value.push.oidc_token.service_account_email + audience = each.value.push.oidc_token.audience } } } } dynamic "bigquery_config" { - for_each = try(var.bigquery_subscription_configs[each.key], null) == null ? [] : [""] + for_each = each.value.bigquery == null ? [] : [""] content { - table = var.bigquery_subscription_configs[each.key].table - use_topic_schema = var.bigquery_subscription_configs[each.key].use_topic_schema - write_metadata = var.bigquery_subscription_configs[each.key].write_metadata - drop_unknown_fields = var.bigquery_subscription_configs[each.key].drop_unknown_fields + table = each.value.bigquery.table + use_topic_schema = each.value.bigquery.use_topic_schema + write_metadata = each.value.bigquery.write_metadata + drop_unknown_fields = each.value.bigquery.drop_unknown_fields } } dynamic "cloud_storage_config" { - for_each = try(var.cloud_storage_subscription_configs[each.key], null) == null ? [] : [""] + for_each = each.value.cloud_storage == null ? [] : [""] content { - bucket = var.cloud_storage_subscription_configs[each.key].bucket - filename_prefix = var.cloud_storage_subscription_configs[each.key].filename_prefix - filename_suffix = var.cloud_storage_subscription_configs[each.key].filename_suffix - max_duration = var.cloud_storage_subscription_configs[each.key].max_duration - max_bytes = var.cloud_storage_subscription_configs[each.key].max_bytes + bucket = each.value.cloud_storage.bucket + filename_prefix = each.value.cloud_storage.filename_prefix + filename_suffix = each.value.cloud_storage.filename_suffix + max_duration = each.value.cloud_storage.max_duration + max_bytes = each.value.cloud_storage.max_bytes dynamic "avro_config" { - for_each = ( - local.metadata_config[each.key] == null ? [] : [""] - ) + for_each = each.value.cloud_storage.avro_config == null ? [] : [""] content { - write_metadata = local.metadata_config[each.key].write_metadata + write_metadata = each.value.cloud_storage.avro_config.write_metadata } } } } } - -resource "google_pubsub_subscription_iam_binding" "default" { - for_each = { - for binding in local.sub_iam_members : - "${binding.sub}.${binding.role}" => binding - } - project = var.project_id - subscription = google_pubsub_subscription.default[each.value.sub].name - role = each.value.role - members = each.value.members -} diff --git a/modules/pubsub/outputs.tf b/modules/pubsub/outputs.tf index 0d149302..8218e2b3 100644 --- a/modules/pubsub/outputs.tf +++ b/modules/pubsub/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -19,7 +19,8 @@ output "id" { value = local.topic_id_static depends_on = [ google_pubsub_topic.default, - google_pubsub_topic_iam_binding.default + google_pubsub_topic_iam_binding.authoritative, + google_pubsub_topic_iam_binding.bindings ] } @@ -39,7 +40,8 @@ output "subscription_id" { for k, v in google_pubsub_subscription.default : k => v.id } depends_on = [ - google_pubsub_subscription_iam_binding.default + google_pubsub_subscription_iam_binding.authoritative, + google_pubsub_subscription_iam_binding.bindings ] } @@ -47,7 +49,8 @@ output "subscriptions" { description = "Subscription resources." value = google_pubsub_subscription.default depends_on = [ - google_pubsub_subscription_iam_binding.default + google_pubsub_subscription_iam_binding.authoritative, + google_pubsub_subscription_iam_binding.bindings ] } @@ -55,6 +58,7 @@ output "topic" { description = "Topic resource." value = google_pubsub_topic.default depends_on = [ - google_pubsub_topic_iam_binding.default + google_pubsub_topic_iam_binding.authoritative, + google_pubsub_topic_iam_binding.bindings ] } diff --git a/modules/pubsub/variables.tf b/modules/pubsub/variables.tf index 35dfc172..370c42fa 100644 --- a/modules/pubsub/variables.tf +++ b/modules/pubsub/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -14,63 +14,41 @@ * limitations under the License. */ -variable "bigquery_subscription_configs" { - description = "Configuration parameters for BigQuery subscriptions." - type = map(object({ - table = string - use_topic_schema = bool - write_metadata = bool - drop_unknown_fields = bool - })) - default = {} -} - -variable "cloud_storage_subscription_configs" { - description = "Configuration parameters for Cloud Storage subscriptions." - type = map(object({ - bucket = string - filename_prefix = string - filename_suffix = string - max_duration = string - max_bytes = number - avro_config = object({ - write_metadata = bool - }) - })) - default = {} -} - -variable "dead_letter_configs" { - description = "Per-subscription dead letter policy configuration." - type = map(object({ - topic = string - max_delivery_attempts = number - })) - default = {} -} - -variable "defaults" { - description = "Subscription defaults for options." - type = object({ - ack_deadline_seconds = number - message_retention_duration = string - retain_acked_messages = bool - expiration_policy_ttl = string - filter = string - }) - default = { - ack_deadline_seconds = null - message_retention_duration = null - retain_acked_messages = null - expiration_policy_ttl = null - filter = null - } -} - variable "iam" { description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} + nullable = false +} + +variable "iam_bindings" { + description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary." + type = map(object({ + members = list(string) + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })) + nullable = false + default = {} +} + +variable "iam_bindings_additive" { + description = "Keyring individual additive IAM bindings. Keys are arbitrary." + type = map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })) + nullable = false + default = {} } variable "kms_key" { @@ -83,6 +61,7 @@ variable "labels" { description = "Labels." type = map(string) default = {} + nullable = false } variable "message_retention_duration" { @@ -101,23 +80,11 @@ variable "project_id" { type = string } -variable "push_configs" { - description = "Push subscription configurations." - type = map(object({ - attributes = map(string) - endpoint = string - oidc_token = object({ - audience = string - service_account_email = string - }) - })) - default = {} -} - variable "regions" { description = "List of regions used to set persistence policy." type = list(string) default = [] + nullable = false } variable "schema" { @@ -130,23 +97,72 @@ variable "schema" { default = null } -variable "subscription_iam" { - description = "IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format." - type = map(map(list(string))) - default = {} -} - variable "subscriptions" { description = "Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null." type = map(object({ - labels = map(string) - options = object({ - ack_deadline_seconds = number - message_retention_duration = string - retain_acked_messages = bool - expiration_policy_ttl = string - filter = string - }) + labels = optional(map(string)) + ack_deadline_seconds = optional(number) + message_retention_duration = optional(string) + retain_acked_messages = optional(bool, false) + expiration_policy_ttl = optional(string) + filter = optional(string) + enable_message_ordering = optional(bool, false) + enable_exactly_once_delivery = optional(bool, false) + dead_letter_policy = optional(object({ + topic = string + max_delivery_attempts = optional(number) + })) + retry_policy = optional(object({ + minimum_backoff = optional(number) + maximum_backoff = optional(number) + })) + + bigquery = optional(object({ + table = string + use_topic_schema = optional(bool, false) + write_metadata = optional(bool, false) + drop_unknown_fields = optional(bool, false) + })) + cloud_storage = optional(object({ + bucket = string + filename_prefix = optional(string) + filename_suffix = optional(string) + max_duration = optional(string) + max_bytes = optional(number) + avro_config = optional(object({ + write_metadata = optional(bool, false) + })) + })) + push = optional(object({ + endpoint = string + attributes = optional(map(string)) + no_wrapper = optional(bool, false) + oidc_token = optional(object({ + audience = optional(string) + service_account_email = string + })) + })) + + iam = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + members = list(string) + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) + iam_bindings_additive = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) })) - default = {} + default = {} + nullable = false } diff --git a/tests/modules/pubsub/examples/simple.yaml b/tests/modules/pubsub/examples/simple.yaml index 51094a51..6fe54ec6 100644 --- a/tests/modules/pubsub/examples/simple.yaml +++ b/tests/modules/pubsub/examples/simple.yaml @@ -16,14 +16,14 @@ values: module.pubsub.google_pubsub_topic.default: name: my-topic project: my-project - module.pubsub.google_pubsub_topic_iam_binding.default["roles/pubsub.subscriber"]: + module.pubsub.google_pubsub_topic_iam_binding.authoritative["roles/pubsub.subscriber"]: condition: [] members: - user:user1@example.com project: my-project role: roles/pubsub.subscriber topic: my-topic - module.pubsub.google_pubsub_topic_iam_binding.default["roles/pubsub.viewer"]: + module.pubsub.google_pubsub_topic_iam_binding.authoritative["roles/pubsub.viewer"]: condition: [] members: - group:foo@example.com diff --git a/tests/modules/pubsub/examples/subscription-iam.yaml b/tests/modules/pubsub/examples/subscription-iam.yaml index d0fa9fb6..42ed2565 100644 --- a/tests/modules/pubsub/examples/subscription-iam.yaml +++ b/tests/modules/pubsub/examples/subscription-iam.yaml @@ -13,10 +13,10 @@ # limitations under the License. values: - module.pubsub.google_pubsub_subscription_iam_binding.default["test-1.roles/pubsub.subscriber"]: + module.pubsub.google_pubsub_subscription_iam_binding.authoritative["test-1.roles/pubsub.subscriber"]: condition: [] members: - - user:user1@ludomagno.net + - user:user1@example.com project: my-project role: roles/pubsub.subscriber subscription: test-1 diff --git a/tests/modules/pubsub/examples/subscriptions.yaml b/tests/modules/pubsub/examples/subscriptions.yaml index a87a6d47..b1a94212 100644 --- a/tests/modules/pubsub/examples/subscriptions.yaml +++ b/tests/modules/pubsub/examples/subscriptions.yaml @@ -16,22 +16,22 @@ values: module.pubsub.google_pubsub_subscription.default["test-pull"]: bigquery_config: [] dead_letter_policy: [] - enable_exactly_once_delivery: null - enable_message_ordering: null + enable_exactly_once_delivery: False + enable_message_ordering: False filter: null labels: null message_retention_duration: 604800s name: test-pull project: my-project push_config: [] - retain_acked_messages: null + retain_acked_messages: False retry_policy: [] topic: my-topic module.pubsub.google_pubsub_subscription.default["test-pull-override"]: bigquery_config: [] dead_letter_policy: [] - enable_exactly_once_delivery: null - enable_message_ordering: null + enable_exactly_once_delivery: False + enable_message_ordering: False filter: null labels: test: override @@ -39,7 +39,7 @@ values: name: test-pull-override project: my-project push_config: [] - retain_acked_messages: true + retain_acked_messages: True retry_policy: [] topic: my-topic module.pubsub.google_pubsub_topic.default: From 415bc14d7b2d135ada3841257122086a8480717d Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 17 Sep 2023 00:01:50 +0200 Subject: [PATCH 8/9] Update Pub/Sub readme --- modules/pubsub/README.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md index 6e706d9e..69a18dbe 100644 --- a/modules/pubsub/README.md +++ b/modules/pubsub/README.md @@ -162,30 +162,26 @@ module "pubsub" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L94) | PubSub topic name. | string | ✓ | | -| [project_id](variables.tf#L99) | Project used for resources. | string | ✓ | | -| [bigquery_subscription_configs](variables.tf#L17) | Configuration parameters for BigQuery subscriptions. | map(object({…})) | | {} | -| [cloud_storage_subscription_configs](variables.tf#L28) | Configuration parameters for Cloud Storage subscriptions. | map(object({…})) | | {} | -| [dead_letter_configs](variables.tf#L43) | Per-subscription dead letter policy configuration. | map(object({…})) | | {} | -| [defaults](variables.tf#L52) | Subscription defaults for options. | object({…}) | | {…} | -| [iam](variables.tf#L70) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [kms_key](variables.tf#L76) | KMS customer managed encryption key. | string | | null | -| [labels](variables.tf#L82) | Labels. | map(string) | | {} | -| [message_retention_duration](variables.tf#L88) | Minimum duration to retain a message after it is published to the topic. | string | | null | -| [push_configs](variables.tf#L104) | Push subscription configurations. | map(object({…})) | | {} | -| [regions](variables.tf#L117) | List of regions used to set persistence policy. | list(string) | | [] | -| [schema](variables.tf#L123) | Topic schema. If set, all messages in this topic should follow this schema. | object({…}) | | null | -| [subscription_iam](variables.tf#L133) | IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [subscriptions](variables.tf#L139) | Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null. | map(object({…})) | | {} | +| [name](variables.tf#L73) | PubSub topic name. | string | ✓ | | +| [project_id](variables.tf#L78) | Project used for resources. | string | ✓ | | +| [iam](variables.tf#L17) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_bindings](variables.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables.tf#L39) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | +| [kms_key](variables.tf#L54) | KMS customer managed encryption key. | string | | null | +| [labels](variables.tf#L60) | Labels. | map(string) | | {} | +| [message_retention_duration](variables.tf#L67) | Minimum duration to retain a message after it is published to the topic. | string | | null | +| [regions](variables.tf#L83) | List of regions used to set persistence policy. | list(string) | | [] | +| [schema](variables.tf#L90) | Topic schema. If set, all messages in this topic should follow this schema. | object({…}) | | null | +| [subscriptions](variables.tf#L100) | Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null. | map(object({…})) | | {} | ## Outputs | name | description | sensitive | |---|---|:---:| | [id](outputs.tf#L17) | Fully qualified topic id. | | -| [schema](outputs.tf#L26) | Schema resource. | | -| [schema_id](outputs.tf#L31) | Schema resource id. | | -| [subscription_id](outputs.tf#L36) | Subscription ids. | | -| [subscriptions](outputs.tf#L46) | Subscription resources. | | -| [topic](outputs.tf#L54) | Topic resource. | | +| [schema](outputs.tf#L27) | Schema resource. | | +| [schema_id](outputs.tf#L32) | Schema resource id. | | +| [subscription_id](outputs.tf#L37) | Subscription ids. | | +| [subscriptions](outputs.tf#L48) | Subscription resources. | | +| [topic](outputs.tf#L57) | Topic resource. | | From 3618c9ebdd1f4903a6f0c478cdce4bc95c6fe761 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 17 Sep 2023 00:21:57 +0200 Subject: [PATCH 9/9] Fix blueprints using pubsub --- .../asset-inventory-feed-remediation/main.tf | 10 ++++++---- blueprints/cloud-operations/quota-monitoring/main.tf | 2 +- .../scheduled-asset-inventory-export-bq/main.tf | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf b/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf index e4082f69..e396364e 100644 --- a/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf +++ b/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf @@ -55,10 +55,12 @@ module "vpc" { } module "pubsub" { - source = "../../../modules/pubsub" - project_id = module.project.project_id - name = var.name - subscriptions = { "${var.name}-default" = null } + source = "../../../modules/pubsub" + project_id = module.project.project_id + name = var.name + subscriptions = { + "${var.name}-default" = {} + } iam = { "roles/pubsub.publisher" = [ "serviceAccount:${module.project.service_accounts.robots.cloudasset}" diff --git a/blueprints/cloud-operations/quota-monitoring/main.tf b/blueprints/cloud-operations/quota-monitoring/main.tf index f644c8fb..a49891c0 100644 --- a/blueprints/cloud-operations/quota-monitoring/main.tf +++ b/blueprints/cloud-operations/quota-monitoring/main.tf @@ -39,7 +39,7 @@ module "pubsub" { project_id = module.project.project_id name = var.name subscriptions = { - "${var.name}-default" = null + "${var.name}-default" = {} } # the Cloud Scheduler robot service account already has pubsub.topics.publish # at the project level via roles/cloudscheduler.serviceAgent diff --git a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf index c10c0b6b..6460384e 100644 --- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf +++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf @@ -63,7 +63,7 @@ module "pubsub" { project_id = module.project.project_id name = var.name subscriptions = { - "${var.name}-default" = null + "${var.name}-default" = {} } # the Cloud Scheduler robot service account already has pubsub.topics.publish # at the project level via roles/cloudscheduler.serviceAgent @@ -74,7 +74,7 @@ module "pubsub_file" { project_id = module.project.project_id name = var.name_cffile subscriptions = { - "${var.name_cffile}-default" = null + "${var.name_cffile}-default" = {} } # the Cloud Scheduler robot service account already has pubsub.topics.publish # at the project level via roles/cloudscheduler.serviceAgent