Skip to content

Commit 8fda35e

Browse files
authored
feat(kms-key): support grants (#48)
1 parent bfea549 commit 8fda35e

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

modules/kms-key/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This module creates following resources.
3232
| Name | Type |
3333
|------|------|
3434
| [aws_kms_alias.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
35+
| [aws_kms_grant.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_grant) | resource |
3536
| [aws_kms_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
3637
| [aws_kms_key_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key_policy) | resource |
3738
| [aws_iam_policy_document.predefined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
@@ -48,6 +49,7 @@ This module creates following resources.
4849
| <a name="input_deletion_window_in_days"></a> [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | (Optional) Duration in days after which the key is deleted after destruction of the resource. Valid value is between `7` and `30` days. Defaults to `30`. | `number` | `30` | no |
4950
| <a name="input_description"></a> [description](#input\_description) | (Optional) The description of the KMS key. | `string` | `"Managed by Terraform."` | no |
5051
| <a name="input_enabled"></a> [enabled](#input\_enabled) | (Optional) Indicates whether the key is enabled. Defaults to `true`. | `bool` | `true` | no |
52+
| <a name="input_grants"></a> [grants](#input\_grants) | (Optional) A list of grants configuration for granting access to the KMS key. Each item of `grants` as defined below.<br/> (Required) `name` - A friendly name for the grant.<br/> (Required) `grantee_principal` - The principal that is given permission to perform the operations that the grant permits in ARN format.<br/> (Required) `operations` - A set of operations that the grant permits. Valid values are `Encrypt`, `Decrypt`, `GenerateDataKey`, `GenerateDataKeyWithoutPlaintext`, `ReEncryptFrom`, `ReEncryptTo`, `CreateGrant`, `RetireGrant`, `DescribeKey`, `GenerateDataKeyPair`, `GenerateDataKeyPairWithoutPlaintext`, `GetPublicKey`, `Sign`, `Verify`, `GenerateMac`, `VerifyMac`, or `DeriveSharedSecret`.<br/> (Optional) `retiring_principal` - The principal that is given permission to retire the grant by using RetireGrant operation in ARN format.<br/> (Optional) `retire_on_delete` - Whether to retire the grant upon deletion. Defaults to `false`.<br/> Retire: Grantee returns permissions voluntarily (normal termination)<br/> Revoke: Admin forcefully cancels permissions (emergency termination)<br/> (Optional) `grant_creation_tokens` - A list of grant tokens to be used when creating the grant. Use grant token for immediate access without waiting for grant propagation (up to 5 min). Required for time-sensitive operations.<br/> (Optional) `constraints` - A configuration for grant constraints. `constraints` block as defined below.<br/> (Optional) `type` - The type of constraints. Valid values are `ENCRYPTION_CONTEXT_EQUALS` or `ENCRYPTION_CONTEXT_SUBSET`. Defaults to `ENCRYPTION_CONTEXT_SUBSET`.<br/> (Optional) `value` - A map of key-value pair to be validated against the encryption context during cryptographic operations. | <pre>list(object({<br/> name = string<br/> grantee_principal = string<br/> operations = set(string)<br/><br/> retiring_principal = optional(string)<br/> retire_on_delete = optional(bool, false)<br/> grant_creation_tokens = optional(list(string))<br/><br/> constraints = optional(object({<br/> type = optional(string, "ENCRYPTION_CONTEXT_SUBSET")<br/> value = map(string)<br/> }))<br/> }))</pre> | `[]` | no |
5153
| <a name="input_key_rotation"></a> [key\_rotation](#input\_key\_rotation) | (Optional) A configuration for key rotation of the KMS key. This configuration is only applicable for symmetric encryption KMS keys. `key_rotation` block as defined below.<br/> (Optional) `enabled` - Whether key rotation is enabled. Defaults to `false`.<br/> (Optional) `period_in_days` - The custom period of t ime between each key rotation. Valid value is between `90` and `2560` days (inclusive). Defaults to `365`. | <pre>object({<br/> enabled = optional(bool, false)<br/> period_in_days = optional(number, 365)<br/> })</pre> | `{}` | no |
5254
| <a name="input_module_tags_enabled"></a> [module\_tags\_enabled](#input\_module\_tags\_enabled) | (Optional) Whether to create AWS Resource Tags for the module informations. | `bool` | `true` | no |
5355
| <a name="input_multi_region_enabled"></a> [multi\_region\_enabled](#input\_multi\_region\_enabled) | (Optional) Indicates whether the key is a multi-Region (true) or regional (false) key. Defaults to `false`. | `bool` | `false` | no |
@@ -71,6 +73,7 @@ This module creates following resources.
7173
| <a name="output_deletion_window_in_days"></a> [deletion\_window\_in\_days](#output\_deletion\_window\_in\_days) | Duration in days after which the key is deleted after destruction of the resource. |
7274
| <a name="output_description"></a> [description](#output\_description) | The description of the KMS key. |
7375
| <a name="output_enabled"></a> [enabled](#output\_enabled) | Whether the key is enabled. |
76+
| <a name="output_grants"></a> [grants](#output\_grants) | A collection of grants for the key. |
7477
| <a name="output_id"></a> [id](#output\_id) | The ID of the KMS key. |
7578
| <a name="output_key_rotation"></a> [key\_rotation](#output\_key\_rotation) | The key rotation configuration of the KMS key. |
7679
| <a name="output_multi_region_enabled"></a> [multi\_region\_enabled](#output\_multi\_region\_enabled) | Whether the key is a multi-region key. |

modules/kms-key/access-control.tf

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,40 @@
1+
###################################################
2+
# Grants for Customer Managed Key
3+
###################################################
4+
5+
resource "aws_kms_grant" "this" {
6+
for_each = {
7+
for grant in var.grants :
8+
grant.name => grant
9+
}
10+
11+
key_id = aws_kms_key.this.key_id
12+
13+
name = each.key
14+
grantee_principal = each.value.grantee_principal
15+
operations = each.value.operations
16+
17+
retiring_principal = each.value.retiring_principal
18+
retire_on_delete = each.value.retire_on_delete
19+
grant_creation_tokens = each.value.grant_creation_tokens
20+
21+
dynamic "constraints" {
22+
for_each = each.value.constraints != null ? [each.value.constraints] : []
23+
24+
content {
25+
encryption_context_equals = (constraints.value.type == "ENCRYPTION_CONTEXT_EQUALS"
26+
? constraints.value.value
27+
: null
28+
)
29+
encryption_context_subset = (constraints.value.type == "ENCRYPTION_CONTEXT_SUBSET"
30+
? constraints.value.value
31+
: null
32+
)
33+
}
34+
}
35+
}
36+
37+
138
###################################################
239
# Key Policy for Customer Managed Key
340
###################################################

modules/kms-key/outputs.tf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
locals {
2+
grants = {
3+
for grant in var.grants :
4+
grant.name => {
5+
id = aws_kms_grant.this[grant.name].grant_id
6+
name = aws_kms_grant.this[grant.name].name
7+
grantee_principal = aws_kms_grant.this[grant.name].grantee_principal
8+
operations = aws_kms_grant.this[grant.name].operations
9+
retiring_principal = aws_kms_grant.this[grant.name].retiring_principal
10+
11+
constraints = grant.constraints
12+
}
13+
}
14+
}
15+
116
output "arn" {
217
description = "The ARN of the KMS key."
318
value = aws_kms_key.this.arn
@@ -46,6 +61,15 @@ output "key_rotation" {
4661
}
4762
}
4863

64+
# NOTE: `grant_token` is intentionally not included in outputs because :
65+
# - Grant tokens are only returned during grant creation and cannot be retrieved afterwards
66+
# - Since Terraform-managed grants are created during infrastructure provisioning, they are already propagated by the time applications run, making tokens unnecessary
67+
# - For immediate access needs, applications should create grants at runtime and use the returned tokens directly rather than relying on pre-provisioned grants
68+
output "grants" {
69+
description = "A collection of grants for the key."
70+
value = local.grants
71+
}
72+
4973
output "predefined_roles" {
5074
description = "The predefined roles that have access to the KMS key."
5175
value = var.predefined_roles
@@ -89,5 +113,13 @@ output "aliases" {
89113
# k => v
90114
# if !contains(["key_id", "arn", "description", "is_enabled", "deletion_window_in_days", "key_usage", "customer_master_key_spec", "enable_key_rotation", "rotation_period_in_days", "custom_key_store_id", "xks_key_id", "multi_region", "tags", "tags_all", "id", "timeouts", "policy", "bypass_policy_lockout_safety_check"], k)
91115
# }
116+
# grants = {
117+
# for name, grant in aws_kms_grant.this :
118+
# name => {
119+
# for k, v in grant :
120+
# k => v
121+
# if !contains(["grant_id", "name", "id", "key_id", "grantee_principal", "operations", "retiring_principal", "retire_on_delete", "constraints", "grant_creation_tokens", "grant_token"], k)
122+
# }
123+
# }
92124
# }
93125
# }

modules/kms-key/variables.tf

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,90 @@ variable "key_rotation" {
103103
}
104104
}
105105

106+
variable "grants" {
107+
description = <<EOF
108+
(Optional) A list of grants configuration for granting access to the KMS key. Each item of `grants` as defined below.
109+
(Required) `name` - A friendly name for the grant.
110+
(Required) `grantee_principal` - The principal that is given permission to perform the operations that the grant permits in ARN format.
111+
(Required) `operations` - A set of operations that the grant permits. Valid values are `Encrypt`, `Decrypt`, `GenerateDataKey`, `GenerateDataKeyWithoutPlaintext`, `ReEncryptFrom`, `ReEncryptTo`, `CreateGrant`, `RetireGrant`, `DescribeKey`, `GenerateDataKeyPair`, `GenerateDataKeyPairWithoutPlaintext`, `GetPublicKey`, `Sign`, `Verify`, `GenerateMac`, `VerifyMac`, or `DeriveSharedSecret`.
112+
(Optional) `retiring_principal` - The principal that is given permission to retire the grant by using RetireGrant operation in ARN format.
113+
(Optional) `retire_on_delete` - Whether to retire the grant upon deletion. Defaults to `false`.
114+
Retire: Grantee returns permissions voluntarily (normal termination)
115+
Revoke: Admin forcefully cancels permissions (emergency termination)
116+
(Optional) `grant_creation_tokens` - A list of grant tokens to be used when creating the grant. Use grant token for immediate access without waiting for grant propagation (up to 5 min). Required for time-sensitive operations.
117+
(Optional) `constraints` - A configuration for grant constraints. `constraints` block as defined below.
118+
(Optional) `type` - The type of constraints. Valid values are `ENCRYPTION_CONTEXT_EQUALS` or `ENCRYPTION_CONTEXT_SUBSET`. Defaults to `ENCRYPTION_CONTEXT_SUBSET`.
119+
(Optional) `value` - A map of key-value pair to be validated against the encryption context during cryptographic operations.
120+
EOF
121+
type = list(object({
122+
name = string
123+
grantee_principal = string
124+
operations = set(string)
125+
126+
retiring_principal = optional(string)
127+
retire_on_delete = optional(bool, false)
128+
grant_creation_tokens = optional(list(string))
129+
130+
constraints = optional(object({
131+
type = optional(string, "ENCRYPTION_CONTEXT_SUBSET")
132+
value = map(string)
133+
}))
134+
}))
135+
default = []
136+
nullable = false
137+
138+
validation {
139+
condition = alltrue([
140+
for grant in var.grants :
141+
alltrue([
142+
for op in grant.operations :
143+
contains([
144+
"Encrypt",
145+
"Decrypt",
146+
"GenerateDataKey",
147+
"GenerateDataKeyWithoutPlaintext",
148+
"ReEncryptFrom",
149+
"ReEncryptTo",
150+
"CreateGrant",
151+
"RetireGrant",
152+
"DescribeKey",
153+
"GenerateDataKeyPair",
154+
"GenerateDataKeyPairWithoutPlaintext",
155+
"GetPublicKey",
156+
"Sign",
157+
"Verify",
158+
"GenerateMac",
159+
"VerifyMac",
160+
"DeriveSharedSecret",
161+
], op)
162+
])
163+
])
164+
error_message = "Valid values for grant operations are `Encrypt`, `Decrypt`, `GenerateDataKey`, `GenerateDataKeyWithoutPlaintext`, `ReEncryptFrom`, `ReEncryptTo`, `CreateGrant`, `RetireGrant`, `DescribeKey`, `GenerateDataKeyPair`, `GenerateDataKeyPairWithoutPlaintext`, `GetPublicKey`, `Sign`, `Verify`, `GenerateMac`, `VerifyMac`, or `DeriveSharedSecret`."
165+
}
166+
167+
validation {
168+
condition = alltrue([
169+
for grant in var.grants :
170+
contains([
171+
"ENCRYPTION_CONTEXT_EQUALS",
172+
"ENCRYPTION_CONTEXT_SUBSET"
173+
], grant.constraints.type)
174+
if grant.constraints != null
175+
])
176+
error_message = "Valid values for `constraints.type` are `ENCRYPTION_CONTEXT_EQUALS` or `ENCRYPTION_CONTEXT_SUBSET`."
177+
}
178+
validation {
179+
condition = alltrue([
180+
for grant in var.grants :
181+
anytrue([
182+
grant.constraints == null,
183+
grant.constraints != null && length(keys(grant.constraints.value)) > 0,
184+
])
185+
])
186+
error_message = "If `constraints` is defined, it must contain at least one key-value pair in `value`."
187+
}
188+
}
189+
106190
variable "predefined_roles" {
107191
description = <<EOF
108192
(Optional) A configuration for predefined roles of the KMS key. This configuration will be merged with given `policy` if it is defined. `predefined_roles` block as defined below.

0 commit comments

Comments
 (0)