Add support for KMS key creation to project factory (#3518)

* initial implementation

* context

* tfdoc

* add support for autokey to projects

* fix typo
This commit is contained in:
Ludovico Magnocavallo
2025-11-11 07:23:50 +01:00
committed by GitHub
parent 15a5486a1e
commit fc7aa71ada
20 changed files with 905 additions and 49 deletions

View File

@@ -83,3 +83,4 @@ resource "terraform_data" "precondition" {
}
}
}

View File

@@ -213,6 +213,118 @@
"iam_by_principals_additive": {
"$ref": "#/$defs/iam_by_principals"
},
"kms": {
"type": "object",
"additionalProperties": false,
"properties": {
"autokeys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location",
"resource_type_selector"
],
"properties": {
"location": {
"type": "string"
},
"resource_type_selector": {
"type": "string"
}
}
}
}
},
"keyrings": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location"
],
"properties": {
"location": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"keys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"destroy_scheduled_duration": {
"type": "string"
},
"rotation_period": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"purpose": {
"type": "string",
"default": "ENCRYPT_DECRYPT",
"enum": [
"CRYPTO_KEY_PURPOSE_UNSPECIFIED",
"ENCRYPT_DECRYPT",
"ASYMMETRIC_SIGN",
"ASYMMETRIC_DECRYPT",
"RAW_ENCRYPT_DECRYPT",
"MAC"
]
},
"version_template": {
"type": "object",
"additionalProperties": false,
"required": [
"algorithm"
],
"properties": {
"algorithm": {
"type": "string"
},
"protection_level": {
"type": "string",
"default": "SOFTWARE",
"enum": [
"SOFTWARE",
"HSM",
"EXTERNAL",
"EXTERNAL_VPC"
]
}
}
}
}
}
}
}
}
}
}
}
},
"labels": {
"type": "object"
},
@@ -651,6 +763,9 @@
"description": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},

View File

@@ -213,6 +213,118 @@
"iam_by_principals_additive": {
"$ref": "#/$defs/iam_by_principals"
},
"kms": {
"type": "object",
"additionalProperties": false,
"properties": {
"autokeys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location",
"resource_type_selector"
],
"properties": {
"location": {
"type": "string"
},
"resource_type_selector": {
"type": "string"
}
}
}
}
},
"keyrings": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location"
],
"properties": {
"location": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"keys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"destroy_scheduled_duration": {
"type": "string"
},
"rotation_period": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"purpose": {
"type": "string",
"default": "ENCRYPT_DECRYPT",
"enum": [
"CRYPTO_KEY_PURPOSE_UNSPECIFIED",
"ENCRYPT_DECRYPT",
"ASYMMETRIC_SIGN",
"ASYMMETRIC_DECRYPT",
"RAW_ENCRYPT_DECRYPT",
"MAC"
]
},
"version_template": {
"type": "object",
"additionalProperties": false,
"required": [
"algorithm"
],
"properties": {
"algorithm": {
"type": "string"
},
"protection_level": {
"type": "string",
"default": "SOFTWARE",
"enum": [
"SOFTWARE",
"HSM",
"EXTERNAL",
"EXTERNAL_VPC"
]
}
}
}
}
}
}
}
}
}
}
}
},
"labels": {
"type": "object"
},
@@ -651,6 +763,9 @@
"description": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},

View File

@@ -213,6 +213,118 @@
"iam_by_principals_additive": {
"$ref": "#/$defs/iam_by_principals"
},
"kms": {
"type": "object",
"additionalProperties": false,
"properties": {
"autokeys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location",
"resource_type_selector"
],
"properties": {
"location": {
"type": "string"
},
"resource_type_selector": {
"type": "string"
}
}
}
}
},
"keyrings": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location"
],
"properties": {
"location": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"keys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"destroy_scheduled_duration": {
"type": "string"
},
"rotation_period": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"purpose": {
"type": "string",
"default": "ENCRYPT_DECRYPT",
"enum": [
"CRYPTO_KEY_PURPOSE_UNSPECIFIED",
"ENCRYPT_DECRYPT",
"ASYMMETRIC_SIGN",
"ASYMMETRIC_DECRYPT",
"RAW_ENCRYPT_DECRYPT",
"MAC"
]
},
"version_template": {
"type": "object",
"additionalProperties": false,
"required": [
"algorithm"
],
"properties": {
"algorithm": {
"type": "string"
},
"protection_level": {
"type": "string",
"default": "SOFTWARE",
"enum": [
"SOFTWARE",
"HSM",
"EXTERNAL",
"EXTERNAL_VPC"
]
}
}
}
}
}
}
}
}
}
}
}
},
"labels": {
"type": "object"
},
@@ -651,6 +763,9 @@
"description": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},

View File

@@ -213,6 +213,118 @@
"iam_by_principals_additive": {
"$ref": "#/$defs/iam_by_principals"
},
"kms": {
"type": "object",
"additionalProperties": false,
"properties": {
"autokeys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location",
"resource_type_selector"
],
"properties": {
"location": {
"type": "string"
},
"resource_type_selector": {
"type": "string"
}
}
}
}
},
"keyrings": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location"
],
"properties": {
"location": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"keys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"destroy_scheduled_duration": {
"type": "string"
},
"rotation_period": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"purpose": {
"type": "string",
"default": "ENCRYPT_DECRYPT",
"enum": [
"CRYPTO_KEY_PURPOSE_UNSPECIFIED",
"ENCRYPT_DECRYPT",
"ASYMMETRIC_SIGN",
"ASYMMETRIC_DECRYPT",
"RAW_ENCRYPT_DECRYPT",
"MAC"
]
},
"version_template": {
"type": "object",
"additionalProperties": false,
"required": [
"algorithm"
],
"properties": {
"algorithm": {
"type": "string"
},
"protection_level": {
"type": "string",
"default": "SOFTWARE",
"enum": [
"SOFTWARE",
"HSM",
"EXTERNAL",
"EXTERNAL_VPC"
]
}
}
}
}
}
}
}
}
}
}
}
},
"labels": {
"type": "object"
},
@@ -651,6 +763,9 @@
"description": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,7 @@ locals {
}
}
}
kms_keys = local.projects_kms_keys[k]
number = module.projects[k].number
project_id = module.projects[k].project_id
log_buckets = {
@@ -85,6 +86,11 @@ output "iam_principals" {
value = local.iam_principals
}
output "kms_keys" {
description = "KMS key ids."
value = local.kms_keys
}
output "log_buckets" {
description = "Log bucket ids."
value = merge([

View File

@@ -42,6 +42,7 @@ module "bigquery-datasets" {
local.automation_sas_iam_emails,
lookup(local.self_sas_iam_emails, each.value.project_key, {})
)
kms_keys = merge(local.ctx.kms_keys, local.kms_keys, local.kms_autokeys)
locations = local.ctx.locations
project_ids = local.ctx_project_ids
})

View File

@@ -75,6 +75,7 @@ module "buckets" {
local.automation_sas_iam_emails,
lookup(local.self_sas_iam_emails, each.value.project_key, {})
)
kms_keys = merge(local.ctx.kms_keys, local.kms_keys, local.kms_autokeys)
locations = local.ctx.locations
project_ids = local.ctx_project_ids
})

View File

@@ -55,7 +55,11 @@ locals {
iam_bindings_additive = try(v.iam_bindings_additive, {}) # type: map(object({...}))
iam_by_principals_additive = try(v.iam_by_principals_additive, {}) # type: map(list(string))
iam_by_principals = try(v.iam_by_principals, {}) # map(list(string))
labels = coalesce( # type: map(string)
kms = {
autokeys = try(v.kms.autokeys, {})
keyrings = try(v.kms.keyrings, {})
}
labels = coalesce( # type: map(string)
try(v.labels, null),
local.data_defaults.defaults.labels
)

View File

@@ -0,0 +1,79 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
projects_keyrings = flatten([
for k, v in local.projects_input : [
for name, opts in v.kms.keyrings : {
project_key = k
project_name = v.name
name = name
location = opts.location
iam = lookup(opts, "iam", {})
iam_bindings = lookup(opts, "iam_bindings", {})
iam_bindings_additive = lookup(opts, "iam_bindings_additive", {})
keys = lookup(opts, "keys", {})
} if try(opts.location, null) != null
]
])
projects_kms_keys = {
for k, v in local.projects_input : k => merge([
for kk, kv in v.kms.keyrings : {
for key_k, key_v in module.kms["${k}/${kk}"].key_ids :
"${k}/${kk}/${key_k}" => key_v if try(kv.location, null) != null
}
]...)
}
kms_autokeys = merge([
for k, v in module.projects : {
for kk, kv in v.kms_autokeys : "autokey/${k}/${kk}" => v
}
]...)
kms_keys = merge([
for k, v in local.projects_kms_keys : v
]...)
}
module "kms" {
source = "../kms"
for_each = {
for k in local.projects_keyrings : "${k.project_key}/${k.name}" => k
}
project_id = module.projects[each.value.project_key].project_id
keyring = {
location = coalesce(
local.data_defaults.overrides.locations.storage,
lookup(each.value, "location", null),
local.data_defaults.defaults.locations.storage
)
name = each.value.name
}
iam = each.value.iam
iam_bindings = each.value.iam_bindings
iam_bindings_additive = each.value.iam_bindings_additive
keys = each.value.keys
context = merge(local.ctx, {
iam_principals = merge(
local.ctx.iam_principals,
local.projects_sas_iam_emails,
local.automation_sas_iam_emails,
lookup(local.self_sas_iam_emails, each.value.project_key, {}),
local.projects_service_agents
)
locations = local.ctx.locations
project_ids = local.ctx_project_ids
})
}

View File

@@ -53,6 +53,7 @@ module "log-buckets" {
local.automation_sas_iam_emails,
lookup(local.self_sas_iam_emails, each.value.project_key, {})
)
kms_keys = merge(local.ctx.kms_keys, local.kms_keys, local.kms_autokeys)
locations = local.ctx.locations
project_ids = local.ctx_project_ids
})

View File

@@ -60,6 +60,11 @@ locals {
for key, log_bucket in module.log-buckets : key => log_bucket.id
}
projects_input = merge(var.projects, local._projects_output)
projects_service_agents = merge([
for k, v in module.projects : {
for kk, vv in v.service_agents : "service_agents/${k}/${kk}" => vv.iam_email
}
]...)
}
resource "terraform_data" "project-preconditions" {
@@ -103,6 +108,7 @@ module "projects" {
scc_sha_custom_modules = try(each.value.factories_config.scc_sha_custom_modules, null)
tags = try(each.value.factories_config.tags, null)
}
kms_autokeys = try(each.value.kms.autokeys, {})
labels = merge(
each.value.labels, var.data_merges.labels
)
@@ -146,7 +152,8 @@ module "projects-iam" {
kms_keys = local.ctx.kms_keys
iam_principals = merge(
local.ctx_iam_principals,
lookup(local.self_sas_iam_emails, each.key, {})
lookup(local.self_sas_iam_emails, each.key, {}),
local.projects_service_agents
)
log_buckets = local.ctx_log_buckets
})

View File

@@ -213,6 +213,118 @@
"iam_by_principals_additive": {
"$ref": "#/$defs/iam_by_principals"
},
"kms": {
"type": "object",
"additionalProperties": false,
"properties": {
"autokeys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location",
"resource_type_selector"
],
"properties": {
"location": {
"type": "string"
},
"resource_type_selector": {
"type": "string"
}
}
}
}
},
"keyrings": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"type": "object",
"additionalProperties": false,
"required": [
"location"
],
"properties": {
"location": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"keys": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z0-9-]+[a-z0-9]$": {
"destroy_scheduled_duration": {
"type": "string"
},
"rotation_period": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"purpose": {
"type": "string",
"default": "ENCRYPT_DECRYPT",
"enum": [
"CRYPTO_KEY_PURPOSE_UNSPECIFIED",
"ENCRYPT_DECRYPT",
"ASYMMETRIC_SIGN",
"ASYMMETRIC_DECRYPT",
"RAW_ENCRYPT_DECRYPT",
"MAC"
]
},
"version_template": {
"type": "object",
"additionalProperties": false,
"required": [
"algorithm"
],
"properties": {
"algorithm": {
"type": "string"
},
"protection_level": {
"type": "string",
"default": "SOFTWARE",
"enum": [
"SOFTWARE",
"HSM",
"EXTERNAL",
"EXTERNAL_VPC"
]
}
}
}
}
}
}
}
}
}
}
}
},
"labels": {
"type": "object"
},
@@ -651,6 +763,9 @@
"description": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},

View File

@@ -177,8 +177,64 @@ variable "projects" {
}))
})), {})
iam_by_principals = optional(map(list(string)), {})
labels = optional(map(string), {})
metric_scopes = optional(list(string), [])
kms = optional(object({
autokeys = optional(map(object({
location = string
resource_type_selector = string
})), {})
keyrings = optional(map(object({
location = 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)
}))
})), {})
keys = optional(map(object({
destroy_scheduled_duration = optional(string)
rotation_period = optional(string)
purpose = optional(string, "ENCRYPT_DECRYPT")
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)
}))
})), {})
version_template = optional(object({
algorithm = string
protection_level = optional(string, "SOFTWARE")
}))
})), {})
})), {})
}), {})
labels = optional(map(string), {})
metric_scopes = optional(list(string), [])
pam_entitlements = optional(map(object({
max_request_duration = string
eligible_users = list(string)

File diff suppressed because one or more lines are too long

View File

@@ -150,6 +150,16 @@ resource "google_resource_manager_lien" "lien" {
reason = var.lien_reason
}
resource "google_kms_key_handle" "default" {
for_each = var.kms_autokeys
project = local.project.project_id
name = each.key
location = try(
local.ctx.locations[each.value.location], each.value.location
)
resource_type_selector = each.value.resource_type_selector
}
resource "google_essential_contacts_contact" "contact" {
provider = google-beta
for_each = var.contacts

View File

@@ -66,6 +66,13 @@ output "id" {
]
}
output "kms_autokeys" {
description = "KMS Autokey key ids."
value = {
for k, v in google_kms_key_handle.default : k => v.kms_key
}
}
output "name" {
description = "Project name."
value = local.project.name

View File

@@ -166,6 +166,24 @@ variable "factories_config" {
default = {}
}
variable "kms_autokeys" {
description = "KMS Autokey key handles."
type = map(object({
location = string
resource_type_selector = optional(string, "compute.googleapis.com/Disk")
}))
nullable = false
default = {}
validation {
condition = alltrue([
for k, v in var.kms_autokeys : k == try(regex(
"^[a-z][a-z0-9-]+[a-z0-9]$", k
), null)
])
error_message = "Autokey keys need to be valid GCP resource names."
}
}
variable "labels" {
description = "Resource labels."
type = map(string)

View File

@@ -0,0 +1,59 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
module.project.google_kms_key_handle.default["compute-disk-ew8"]:
location: europe-west8
name: compute-disk-ew8
project: test-project
resource_type_selector: compute.googleapis.com/Disk
timeouts: null
module.project.google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456
deletion_policy: DELETE
effective_labels:
goog-terraform-provisioned: 'true'
folder_id: '1122334455'
labels: null
name: test-project
org_id: null
project_id: test-project
tags: null
terraform_labels:
goog-terraform-provisioned: 'true'
timeouts: null
module.project.google_project_iam_member.service_agents["cloudkms"]:
condition: []
project: test-project
role: roles/cloudkms.serviceAgent
module.project.google_project_service.project_services["cloudkms.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-project
service: cloudkms.googleapis.com
timeouts: null
module.project.google_project_service_identity.default["cloudkms.googleapis.com"]:
project: test-project
service: cloudkms.googleapis.com
timeouts: null
counts:
google_kms_key_handle: 1
google_project: 1
google_project_iam_member: 1
google_project_service: 1
google_project_service_identity: 1
modules: 1
resources: 5