Update kms module key-level IAM

This commit is contained in:
Julio Castillo 2023-09-15 16:05:36 +02:00
parent 1c5aabbd08
commit da883bab8c
8 changed files with 124 additions and 180 deletions

View File

@ -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 = {}
}
}
###############################################################################

View File

@ -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
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [keyring](variables.tf#L119) | Keyring attributes. | <code title="object&#40;&#123;&#10; location &#61; string&#10; name &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [project_id](variables.tf#L142) | Project id where the keyring will be created. | <code>string</code> | ✓ | |
| [keyring](variables.tf#L53) | Keyring attributes. | <code title="object&#40;&#123;&#10; location &#61; string&#10; name &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [project_id](variables.tf#L102) | Project id where the keyring will be created. | <code>string</code> | ✓ | |
| [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables.tf#L23) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables.tf#L38) | Keyring individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_iam](variables.tf#L53) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_iam_bindings](variables.tf#L59) | Key authoritative IAM bindings in {KEY => {BINDING_KEY => {role = ROLE, members = [], condition = {}}}}. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_iam_bindings_additive](variables.tf#L74) | Key individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; key &#61; string&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [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. | <code title="map&#40;object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [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. | <code title="object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; purpose &#61; null&#10; version_template &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [keyring_create](variables.tf#L127) | Set to false to manage keys and IAM bindings in an existing keyring. | <code>bool</code> | | <code>true</code> |
| [keys](variables.tf#L133) | Key names and base attributes. Set attributes to null if not needed. | <code title="map&#40;object&#40;&#123;&#10; rotation_period &#61; string&#10; labels &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables.tf#L147) | Tag bindings for this keyring, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [keyring_create](variables.tf#L61) | Set to false to manage keys and IAM bindings in an existing keyring. | <code>bool</code> | | <code>true</code> |
| [keys](variables.tf#L67) | Key names and base attributes. Set attributes to null if not needed. | <code title="map&#40;object&#40;&#123;&#10; rotation_period &#61; optional&#40;string&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;&#41;&#10; purpose &#61; optional&#40;string, &#34;ENCRYPT_DECRYPT&#34;&#41;&#10; skip_initial_version_creation &#61; optional&#40;bool, false&#41;&#10; version_template &#61; optional&#40;object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; optional&#40;string, &#34;SOFTWARE&#34;&#41;&#10; &#125;&#41;&#41;&#10;&#10;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables.tf#L107) | Tag bindings for this keyring, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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: {}

View File

@ -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: {}