From f6b1da8f0d8630ff5f34b1986570166f28dc753e Mon Sep 17 00:00:00 2001 From: Matthew Lemmond Date: Tue, 17 Dec 2024 09:14:09 -0500 Subject: [PATCH] feat: Added KMIP (Key Management Interoperability Protocol support). [Learn more](https://cloud.ibm.com/docs/key-protect?topic=key-protect-kmip) (#646) --- README.md | 9 +++-- examples/basic/version.tf | 2 +- examples/complete/main.tf | 66 ++++++++++++++++++++++++++++++++++ examples/complete/outputs.tf | 10 ++++++ examples/complete/variables.tf | 12 +++++++ examples/complete/version.tf | 2 +- main.tf | 49 +++++++++++++++++++++++++ outputs.tf | 10 ++++++ tests/pr_test.go | 8 +++++ variables.tf | 37 +++++++++++++++++++ version.tf | 4 +-- 11 files changed, 203 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b43799f..5bfe9c1 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ You need the following permissions to run this module. | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.0 | -| [ibm](#requirement\_ibm) | >= 1.49.0, < 2.0.0 | +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [ibm](#requirement\_ibm) | >= 1.69.0, < 2.0.0 | ### Modules @@ -77,6 +77,8 @@ No modules. | [ibm_kms_key.key](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_key) | resource | | [ibm_kms_key_policies.root_key_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_key_policies) | resource | | [ibm_kms_key_policies.standard_key_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_key_policies) | resource | +| [ibm_kms_kmip_adapter.kmip_adapter](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_kmip_adapter) | resource | +| [ibm_kms_kmip_client_cert.kmip_cert](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/kms_kmip_client_cert) | resource | ### Inputs @@ -86,6 +88,7 @@ No modules. | [endpoint\_type](#input\_endpoint\_type) | Endpoint to use when creating the Key | `string` | `"public"` | no | | [force\_delete](#input\_force\_delete) | Set as true to enable forcing deletion even if key is in use | `bool` | `false` | no | | [key\_name](#input\_key\_name) | Name to give the key | `string` | n/a | yes | +| [kmip](#input\_kmip) | Allows a key to utilize the key management interoperability protocol (KMIP), for more information see https://cloud.ibm.com/docs/key-protect?topic=key-protect-kmip |
list(object({
name = string
description = optional(string)
certificates = optional(list(object({
name = optional(string)
certificate = string
})))
}))
| `[]` | no | | [kms\_instance\_id](#input\_kms\_instance\_id) | ID or GUID of KMS Instance | `string` | n/a | yes | | [kms\_key\_ring\_id](#input\_kms\_key\_ring\_id) | The ID of the key ring where you want to add your KMS key | `string` | `"default"` | no | | [rotation\_interval\_month](#input\_rotation\_interval\_month) | The key rotation time interval in months. Rotation policy cannot be set for standard key, so value is ignored if var.standard\_key is true | `number` | `1` | no | @@ -95,6 +98,8 @@ No modules. | Name | Description | |------|-------------| +| [adapter\_ids](#output\_adapter\_ids) | KMIP Adapter IDs of the associated root key | +| [cert\_ids](#output\_cert\_ids) | KMIP Cert IDs | | [crn](#output\_crn) | Key CRN | | [dual\_auth\_delete](#output\_dual\_auth\_delete) | Is Dual Auth Delete Enabled | | [key\_id](#output\_key\_id) | Key ID | diff --git a/examples/basic/version.tf b/examples/basic/version.tf index fceff70..edce2b3 100644 --- a/examples/basic/version.tf +++ b/examples/basic/version.tf @@ -5,7 +5,7 @@ terraform { # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example). ibm = { source = "IBM-Cloud/ibm" - version = "1.49.0" + version = "1.69.0" } } } diff --git a/examples/complete/main.tf b/examples/complete/main.tf index a18848a..8e71728 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -10,6 +10,62 @@ module "resource_group" { existing_resource_group_name = var.resource_group } +############################################################################## +# Secrets Manager Certificate Setup +############################################################################## + +module "secrets_manager" { + count = var.existing_secrets_manager_crn == null ? 1 : 0 + source = "terraform-ibm-modules/secrets-manager/ibm" + version = "1.18.14" + secrets_manager_name = "${var.prefix}-secrets-manager" + sm_service_plan = "trial" + resource_group_id = module.resource_group.resource_group_id + region = var.region +} + +module "sm_crn" { + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_secrets_manager_crn == null ? module.secrets_manager[0].secrets_manager_crn : var.existing_secrets_manager_crn +} + +locals { + certificate_template_name = var.existing_cert_template_name == null ? "${var.prefix}-template" : var.existing_cert_template_name +} + +module "secrets_manager_private_cert_engine" { + count = var.existing_secrets_manager_crn == null && var.existing_cert_template_name == null ? 1 : 0 + source = "terraform-ibm-modules/secrets-manager-private-cert-engine/ibm" + version = "1.3.4" + secrets_manager_guid = module.sm_crn.service_instance + region = var.region + root_ca_name = "${var.prefix}-ca" + root_ca_common_name = "*.cloud.ibm.com" + intermediate_ca_name = "${var.prefix}-int-ca" + certificate_template_name = local.certificate_template_name + root_ca_max_ttl = "8760h" +} + +module "secrets_manager_cert" { + # explicit depends on because the cert engine must complete creating the template before the cert is created + # no outputs from the private cert engine to reference in this module call + depends_on = [module.secrets_manager_private_cert_engine] + source = "terraform-ibm-modules/secrets-manager-private-cert/ibm" + version = "1.3.2" + secrets_manager_guid = module.sm_crn.service_instance + secrets_manager_region = module.sm_crn.region + cert_name = "${var.prefix}-kmip-cert" + cert_common_name = "*.cloud.ibm.com" + cert_template = local.certificate_template_name +} + +data "ibm_sm_private_certificate" "kmip_cert" { + instance_id = module.sm_crn.service_instance + region = module.sm_crn.region + secret_id = module.secrets_manager_cert.secret_id +} + ############################################################################## # KMS (Key Protect) instance ############################################################################## @@ -31,6 +87,16 @@ module "kms_root_key" { source = "../.." kms_instance_id = ibm_resource_instance.key_protect_instance.guid key_name = "${var.prefix}-root-key" + kmip = [ + { + name = "${var.prefix}-kmip-adapter" + certificates = [ + { + certificate = data.ibm_sm_private_certificate.kmip_cert.certificate + } + ] + } + ] } ############################################################################## diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 0fc0696..ffcd4bf 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -51,3 +51,13 @@ output "resource_group_id" { description = "Resource group ID" value = module.resource_group.resource_group_id } + +output "root_key_adapter_ids" { + description = "Root Key KMIP adapter IDs" + value = module.kms_root_key.adapter_ids +} + +output "root_key_cert_ids" { + description = "Root Key KMIP cert IDs" + value = module.kms_root_key.cert_ids +} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 4bcf0cc..89c0b61 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -25,3 +25,15 @@ variable "resource_tags" { description = "Optional list of tags to be added to created resources" default = [] } + +variable "existing_secrets_manager_crn" { + type = string + description = "CRN of an existing Secrets Manager instance" + default = null +} + +variable "existing_cert_template_name" { + type = string + description = "Name of an existing Private Certificate template to use, required if providing a value for `existing_secrets_manager_crn`" + default = null +} diff --git a/examples/complete/version.tf b/examples/complete/version.tf index a253b33..d3a9aec 100644 --- a/examples/complete/version.tf +++ b/examples/complete/version.tf @@ -5,7 +5,7 @@ terraform { # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example). ibm = { source = "IBM-Cloud/ibm" - version = ">= 1.49.0" + version = ">= 1.69.0" } } } diff --git a/main.tf b/main.tf index 0b97ed7..78fd69f 100644 --- a/main.tf +++ b/main.tf @@ -33,3 +33,52 @@ resource "ibm_kms_key_policies" "standard_key_policy" { enabled = var.dual_auth_delete_enabled } } + +locals { + # tflint-ignore: terraform_unused_declarations + kmip_root_key_validation = (length(var.kmip) > 0 && var.standard_key) ? tobool("When providing a value for `kmip`, the key being created must be a root key.") : true + + kmip_certs = flatten([ + [ + for adapter in var.kmip : [ + for certificate in adapter.certificates : { + adapter_name = adapter.name + certificate_name = try(certificate.name, null) + certificate = certificate.certificate + # Check if filepath string is given, used in ibm_kms_kmip_client_cert call + cert_is_file = length(regexall("^.+\\.pem$", certificate.certificate)) > 0 + } + ] + ] + ]) + + kmip_adapter_id_output = { + for idx, _ in ibm_kms_kmip_adapter.kmip_adapter : + idx => ibm_kms_kmip_adapter.kmip_adapter[idx].adapter_id + } + kmip_cert_id_output = { + for idx, _ in ibm_kms_kmip_client_cert.kmip_cert : + idx => ibm_kms_kmip_client_cert.kmip_cert[idx].cert_id + } +} + +resource "ibm_kms_kmip_adapter" "kmip_adapter" { + for_each = { for adapter in var.kmip : adapter.name => adapter } + instance_id = var.kms_instance_id + profile = "native_1.0" + profile_data = { + "crk_id" = ibm_kms_key.key.key_id + } + name = each.key + description = each.value.description + endpoint_type = var.endpoint_type +} + +resource "ibm_kms_kmip_client_cert" "kmip_cert" { + for_each = { for idx, obj in local.kmip_certs : "${obj.adapter_name}-${idx}" => obj } + endpoint_type = var.endpoint_type + instance_id = var.kms_instance_id + adapter_id = ibm_kms_kmip_adapter.kmip_adapter[each.value.adapter_name].adapter_id + certificate = each.value.cert_is_file ? file(each.value.certificate) : each.value.certificate + name = each.value.certificate_name +} diff --git a/outputs.tf b/outputs.tf index 32cf282..73f956f 100644 --- a/outputs.tf +++ b/outputs.tf @@ -21,3 +21,13 @@ output "dual_auth_delete" { description = "Is Dual Auth Delete Enabled" value = var.standard_key ? ibm_kms_key_policies.standard_key_policy[0].dual_auth_delete : ibm_kms_key_policies.root_key_policy[0].dual_auth_delete } + +output "adapter_ids" { + description = "KMIP Adapter IDs of the associated root key" + value = local.kmip_adapter_id_output +} + +output "cert_ids" { + description = "KMIP Cert IDs" + value = local.kmip_cert_id_output +} diff --git a/tests/pr_test.go b/tests/pr_test.go index 9a9cc0c..3c4c55c 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -48,6 +48,14 @@ func TestRunCompleteExample(t *testing.T) { t.Parallel() options := setupOptions(t, "kms-key") + + options.TerraformVars = map[string]interface{}{ + "existing_secrets_manager_crn": permanentResources["secretsManagerCRN"], + "existing_cert_template_name": permanentResources["privateCertTemplateName"], + "prefix": options.Prefix, + "region": permanentResources["secretsManagerRegion"], + } + output, err := options.RunTestConsistency() assert.Nil(t, err, "This should not have errored") assert.NotNil(t, output, "Expected some output") diff --git a/variables.tf b/variables.tf index 64d8f87..5c822ad 100644 --- a/variables.tf +++ b/variables.tf @@ -56,3 +56,40 @@ variable "force_delete" { description = "Set as true to enable forcing deletion even if key is in use" default = false } + +variable "kmip" { + type = list(object({ + name = string + description = optional(string) + certificates = optional(list(object({ + name = optional(string) + certificate = string + }))) + })) + description = "Allows a key to utilize the key management interoperability protocol (KMIP), for more information see https://cloud.ibm.com/docs/key-protect?topic=key-protect-kmip" + default = [] + + validation { + condition = alltrue([for adapter in var.kmip : + length(adapter.name) >= 2 && length(adapter.name) <= 40 + ]) + error_message = "`kmip[*].name` must be between 2 and 40 characters." + } + + validation { + condition = alltrue([for adapter in var.kmip : + adapter.description == null ? true : + length(adapter.description) >= 2 && length(adapter.description) <= 240 + ]) + error_message = "`kmip[*].description` must be between 2 and 240 characters." + } + + validation { + condition = alltrue([ + for adapter in var.kmip : + adapter.certificates == null ? true : + length(adapter.certificates) <= 200 + ]) + error_message = "Each adapter can contain up to 200 certificates, current length exceeds this limit." + } +} diff --git a/version.tf b/version.tf index 6c82ee0..d5264a9 100644 --- a/version.tf +++ b/version.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0.0" + required_version = ">= 1.3.0" required_providers { ibm = { source = "IBM-Cloud/ibm" # Use "greater than or equal to" range in modules - version = ">= 1.49.0, < 2.0.0" + version = ">= 1.69.0, < 2.0.0" } } }