From bb5f43d9a13fc34d194fa7213192bb7efe303af2 Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Wed, 15 Jan 2025 22:42:42 +0000 Subject: [PATCH 1/7] refactor: kms logic + add da --- README.md | 16 +- examples/complete/main.tf | 48 ++- examples/complete/variables.tf | 11 - examples/fscloud/main.tf | 40 ++- examples/fscloud/variables.tf | 38 -- main.tf | 152 +++++++- modules/fscloud/README.md | 10 +- modules/fscloud/main.tf | 49 +-- modules/fscloud/variables.tf | 43 ++- .../deployable-architecture-rabbitmq.svg | 4 + solutions/standard/DA-types.md | 203 +++++++++++ solutions/standard/README.md | 13 + .../catalogValidationValues.json.template | 8 + solutions/standard/main.tf | 336 ++++++++++++++++++ solutions/standard/outputs.tf | 66 ++++ solutions/standard/provider.tf | 11 + solutions/standard/variables.tf | 297 ++++++++++++++++ solutions/standard/version.tf | 18 + tests/other_test.go | 28 ++ tests/pr_test.go | 172 +++++++-- variables.tf | 36 +- 21 files changed, 1422 insertions(+), 177 deletions(-) create mode 100644 reference-architecture/deployable-architecture-rabbitmq.svg create mode 100644 solutions/standard/DA-types.md create mode 100644 solutions/standard/README.md create mode 100644 solutions/standard/catalogValidationValues.json.template create mode 100644 solutions/standard/main.tf create mode 100644 solutions/standard/outputs.tf create mode 100644 solutions/standard/provider.tf create mode 100644 solutions/standard/variables.tf create mode 100644 solutions/standard/version.tf diff --git a/README.md b/README.md index d2085faf..8aed7643 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,21 @@ You need the following permissions to run this module. | Name | Source | Version | |------|--------|---------| +| [backup\_key\_crn\_parser](#module\_backup\_key\_crn\_parser) | terraform-ibm-modules/common-utilities/ibm//modules/crn-parser | 1.1.0 | | [cbr\_rule](#module\_cbr\_rule) | git::https://github.com/terraform-ibm-modules/terraform-ibm-cbr//modules/cbr-rule-module | v1.29.0 | +| [kms\_key\_crn\_parser](#module\_kms\_key\_crn\_parser) | terraform-ibm-modules/common-utilities/ibm//modules/crn-parser | 1.1.0 | ### Resources | Name | Type | |------|------| | [ibm_database.rabbitmq_database](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database) | resource | +| [ibm_iam_authorization_policy.backup_kms_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_iam_authorization_policy.kms_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_resource_key.service_credentials](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_key) | resource | | [ibm_resource_tag.rabbitmq_tag](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_tag) | resource | | [time_sleep.wait_for_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [time_sleep.wait_for_backup_kms_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | | [ibm_database_connection.database_connection](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/database_connection) | data source | ### Inputs @@ -85,15 +89,13 @@ You need the following permissions to run this module. | [admin\_pass](#input\_admin\_pass) | The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block. | `string` | `null` | no | | [auto\_scaling](#input\_auto\_scaling) | Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-autoscaling in the IBM Cloud Docs. |
object({
disk = object({
capacity_enabled = optional(bool, false)
free_space_less_than_percent = optional(number, 10)
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 3670016)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
memory = object({
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 114688)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
})
| `null` | no | | [backup\_crn](#input\_backup\_crn) | The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty. | `string` | `null` | no | -| [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a KMS (Key Protect or Hyper Protect Crypto Services) key to use for encrypting the disk that holds deployment backups. Only used if var.kms\_encryption\_enabled is set to true. There are limitation per region on the type of KMS service (Key Protect or Hyper Protect Crypto Services) and region for those services. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok and https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups | `string` | `null` | no | +| [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of CBR rules to create |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
}))
| `[]` | no | | [cpu\_count](#input\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `0` | no | | [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `1024` | no | | [endpoints](#input\_endpoints) | Endpoints available to the database instance (public, private, public-and-private) | `string` | `"private"` | no | -| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect or Key Protect instance in which the key specified in var.kms\_key\_crn and var.backup\_encryption\_key\_crn is coming from. Only required if var.kms\_encryption\_enabled is 'true', var.skip\_iam\_authorization\_policy is 'false', and passing a value for var.kms\_key\_crn and/or var.backup\_encryption\_key\_crn. | `string` | `null` | no | | [instance\_name](#input\_instance\_name) | The name to give the RabbitMQ instance | `string` | n/a | yes | -| [kms\_encryption\_enabled](#input\_kms\_encryption\_enabled) | Set this to true to control the encryption keys used to encrypt the data that you store in IBM Cloud® Databases. If set to false, the data is encrypted by using randomly generated keys. For more info on Key Protect integration, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect. For more info on HPCS integration, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs | `bool` | `false` | no | -| [kms\_key\_crn](#input\_kms\_key\_crn) | The root key CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Services (HPCS) that you want to use for disk encryption. Only used if var.kms\_encryption\_enabled is set to true. | `string` | `null` | no | +| [kms\_key\_crn](#input\_kms\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [member\_host\_flavor](#input\_member\_host\_flavor) | Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor). | `string` | `null` | no | | [members](#input\_members) | Allocated number of members. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `3` | no | | [memory\_mb](#input\_memory\_mb) | Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `8192` | no | @@ -102,9 +104,11 @@ You need the following permissions to run this module. | [region](#input\_region) | The region where you want to deploy your instance. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the RabbitMQ instance will be created. | `string` | n/a | yes | | [service\_credential\_names](#input\_service\_credential\_names) | Map of name, role for service credentials that you want to create for the database | `map(string)` | `{}` | no | -| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of an IAM authorization policy that permits all RabbitMQ instances in the given resource group to read the encryption key from the Hyper Protect or Key Protect instance passed in var.existing\_kms\_instance\_guid. If set to 'false', a value must be passed for var.existing\_kms\_instance\_guid. No policy is created if var.kms\_encryption\_enabled is set to 'false'. | `bool` | `false` | no | +| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of IAM authorization policies that permits all Databases for RabbitMQ instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key that was provided in the `kms_key_crn` and `backup_encryption_key_crn` inputs. This policy is required in order to enable KMS encryption, so only skip creation if there is one already present in your account. No policy is created if `use_ibm_owned_encryption_key` is true. | `bool` | `false` | no | | [tags](#input\_tags) | Optional list of tags to be added to the RabbitMQ instance. | `list(any)` | `[]` | no | -| [use\_default\_backup\_encryption\_key](#input\_use\_default\_backup\_encryption\_key) | Set to true to use default ICD randomly generated keys. | `bool` | `false` | no | +| [use\_default\_backup\_encryption\_key](#input\_use\_default\_backup\_encryption\_key) | When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `kms_key_crn`, or in `backup_encryption_key_crn` if a value is passed. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data. | `bool` | `false` | no | +| [use\_ibm\_owned\_encryption\_key](#input\_use\_ibm\_owned\_encryption\_key) | IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input. | `bool` | `true` | no | +| [use\_same\_kms\_key\_for\_backups](#input\_use\_same\_kms\_key\_for\_backups) | Set this to false if you wan't to use a different key that you own to encrypt backups. When set to false, a value is required for the `backup_encryption_key_crn` input. Alternatiely set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Applies only if `use_ibm_owned_encryption_key` is false. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `bool` | `true` | no | | [users](#input\_users) | A list of users that you want to create on the database. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service\_credential\_names) is sufficient to control access to the RabbitMQ instance. This blocks creates native RabbitMQ database users, more info on that can be found here https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-user-management |
list(object({
name = string
password = string # pragma: allowlist secret
type = optional(string)
role = optional(string)
}))
| `[]` | no | ### Outputs diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 57e6272a..0a68fefb 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -14,6 +14,11 @@ module "resource_group" { # Key Protect All Inclusive ############################################################################## +locals { + data_key_name = "${var.prefix}-rabbitmq" + backups_key_name = "${var.prefix}-rabbitmq-backups" +} + module "key_protect_all_inclusive" { source = "terraform-ibm-modules/kms-all-inclusive/ibm" version = "4.19.2" @@ -28,7 +33,11 @@ module "key_protect_all_inclusive" { key_ring_name = "icd" keys = [ { - key_name = "${var.prefix}-rabbitmq" + key_name = local.data_key_name + force_delete = true + }, + { + key_name = local.backups_key_name force_delete = true } ] @@ -81,21 +90,28 @@ module "cbr_zone" { ############################################################################## module "icd_rabbitmq" { - source = "../../" - resource_group_id = module.resource_group.resource_group_id - instance_name = "${var.prefix}-rabbitmq" - region = var.region - kms_encryption_enabled = true - existing_kms_instance_guid = module.key_protect_all_inclusive.kms_guid - service_credential_names = var.service_credential_names - admin_pass = var.admin_pass - users = var.users - rabbitmq_version = var.rabbitmq_version - kms_key_crn = module.key_protect_all_inclusive.keys["icd.${var.prefix}-rabbitmq"].crn - tags = var.resource_tags - access_tags = var.access_tags - auto_scaling = var.auto_scaling - member_host_flavor = "multitenant" + source = "../../" + resource_group_id = module.resource_group.resource_group_id + instance_name = "${var.prefix}-rabbitmq" + region = var.region + admin_pass = var.admin_pass + users = var.users + rabbitmq_version = var.rabbitmq_version + tags = var.resource_tags + access_tags = var.access_tags + auto_scaling = var.auto_scaling + # Example of how to use different KMS keys for data and backups + use_ibm_owned_encryption_key = false + use_same_kms_key_for_backups = false + kms_key_crn = module.key_protect_all_inclusive.keys["icd.${local.data_key_name}"].crn + backup_encryption_key_crn = module.key_protect_all_inclusive.keys["icd.${local.data_key_name}"].crn + service_credential_names = { + "rabbitmq_admin" : "Administrator", + "rabbitmq_operator" : "Operator", + "rabbitmq_viewer" : "Viewer", + "rabbitmq_editor" : "Editor", + } + member_host_flavor = "multitenant" cbr_rules = [ { description = "${var.prefix}-rabbitmq access only from vpc" diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index c4fc1e1e..2ec2b078 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -29,17 +29,6 @@ variable "users" { description = "A list of users that you want to create on the database. Multiple blocks are allowed. The user password must be in the range of 10-32 characters." } -variable "service_credential_names" { - description = "Map of name, role for service credentials that you want to create for the database" - type = map(string) - default = { - "rabbit_admin" : "Administrator", - "rabbit_operator" : "Operator", - "rabbit_viewer" : "Viewer", - "rabbit_editor" : "Editor", - } -} - variable "prefix" { type = string description = "Prefix to append to all resources created by this example" diff --git a/examples/fscloud/main.tf b/examples/fscloud/main.tf index ef21f49d..d55a0e31 100644 --- a/examples/fscloud/main.tf +++ b/examples/fscloud/main.tf @@ -54,20 +54,32 @@ module "cbr_zone" { ############################################################################## module "rabbitmq_database" { - source = "../../modules/fscloud" - resource_group_id = module.resource_group.resource_group_id - instance_name = "${var.prefix}-rabbitmq" - region = var.region - rabbitmq_version = var.rabbitmq_version - kms_key_crn = var.kms_key_crn - existing_kms_instance_guid = var.existing_kms_instance_guid - service_credential_names = var.service_credential_names - tags = var.tags - access_tags = var.access_tags - auto_scaling = var.auto_scaling - member_host_flavor = "b3c.4x16.encrypted" - backup_encryption_key_crn = var.backup_encryption_key_crn - backup_crn = var.backup_crn + source = "../../modules/fscloud" + resource_group_id = module.resource_group.resource_group_id + instance_name = "${var.prefix}-rabbitmq" + region = var.region + rabbitmq_version = var.rabbitmq_version + kms_key_crn = var.kms_key_crn + backup_encryption_key_crn = var.backup_encryption_key_crn + backup_crn = var.backup_crn + service_credential_names = { + "rabbitmq_admin" : "Administrator", + "rabbitmq_operator" : "Operator", + "rabbitmq_viewer" : "Viewer", + "rabbitmq_editor" : "Editor", + } + auto_scaling = { + disk = { + capacity_enabled : true, + io_enabled : true + } + memory = { + io_enabled : true, + } + } + member_host_flavor = "b3c.4x16.encrypted" + tags = var.tags + access_tags = var.access_tags cbr_rules = [ { description = "${var.prefix}-rabbitmq access only from vpc" diff --git a/examples/fscloud/variables.tf b/examples/fscloud/variables.tf index 94c0c540..0e8a753a 100644 --- a/examples/fscloud/variables.tf +++ b/examples/fscloud/variables.tf @@ -28,11 +28,6 @@ variable "resource_group" { default = null } -variable "existing_kms_instance_guid" { - description = "The GUID of the Hyper Protect Crypto services in which the key specified in var.kms_key_crn is coming from" - type = string -} - variable "kms_key_crn" { type = string description = "The root key CRN of a Hyper Protect Crypto Services (HPCS) that you want to use for disk encryption. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs&interface=ui for more information on integrating HPCS with RabbitMQ instance." @@ -44,33 +39,6 @@ variable "rabbitmq_version" { default = null } -variable "auto_scaling" { - type = object({ - disk = object({ - capacity_enabled = optional(bool, false) - free_space_less_than_percent = optional(number, 10) - io_above_percent = optional(number, 90) - io_enabled = optional(bool, false) - io_over_period = optional(string, "15m") - rate_increase_percent = optional(number, 10) - rate_limit_mb_per_member = optional(number, 3670016) - rate_period_seconds = optional(number, 900) - rate_units = optional(string, "mb") - }) - memory = object({ - io_above_percent = optional(number, 90) - io_enabled = optional(bool, false) - io_over_period = optional(string, "15m") - rate_increase_percent = optional(number, 10) - rate_limit_mb_per_member = optional(number, 114688) - rate_period_seconds = optional(number, 900) - rate_units = optional(string, "mb") - }) - }) - description = "Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-autoscaling in the IBM Cloud Docs." - default = null -} - variable "backup_crn" { type = string description = "The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty." @@ -84,12 +52,6 @@ variable "backup_encryption_key_crn" { # Validation happens in the root module } -variable "service_credential_names" { - description = "Map of name, role for service credentials that you want to create for the database" - type = map(string) - default = {} -} - variable "tags" { type = list(any) description = "Optional list of tags to be added to the RabbitMQ instance." diff --git a/main.tf b/main.tf index 0da2eced..7866dcf2 100644 --- a/main.tf +++ b/main.tf @@ -1,50 +1,164 @@ locals { # Validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) # tflint-ignore: terraform_unused_declarations - validate_kms_values = !var.kms_encryption_enabled && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? tobool("When passing values for var.backup_encryption_key_crn or var.kms_key_crn, you must set var.kms_encryption_enabled to true. Otherwise unset them to use default encryption") : true + validate_kms_values = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? tobool("When passing values for 'kms_key_crn' or 'backup_encryption_key_crn', you must set 'use_ibm_owned_encryption_key' to false. Otherwise unset them to use default encryption.") : true # tflint-ignore: terraform_unused_declarations - validate_kms_vars = var.kms_encryption_enabled && var.kms_key_crn == null && var.backup_encryption_key_crn == null ? tobool("When setting var.kms_encryption_enabled to true, a value must be passed for var.kms_key_crn and/or var.backup_encryption_key_crn") : true + validate_kms_vars = !var.use_ibm_owned_encryption_key && var.kms_key_crn == null ? tobool("When setting 'use_ibm_owned_encryption_key' to false, a value must be passed for 'kms_key_crn'.") : true # tflint-ignore: terraform_unused_declarations - validate_auth_policy = var.kms_encryption_enabled && var.skip_iam_authorization_policy == false && var.existing_kms_instance_guid == null ? tobool("When var.skip_iam_authorization_policy is set to false, and var.kms_encryption_enabled to true, a value must be passed for var.existing_kms_instance_guid in order to create the auth policy.") : true + validate_backup_key = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn != null && (var.use_default_backup_encryption_key || var.use_same_kms_key_for_backups) ? tobool("When passing a value for 'backup_encryption_key_crn' you cannot set 'use_default_backup_encryption_key' to true or 'use_ibm_owned_encryption_key' to false.") : true # tflint-ignore: terraform_unused_declarations - validate_backup_key = var.backup_encryption_key_crn != null && var.use_default_backup_encryption_key == true ? tobool("When passing a value for 'backup_encryption_key_crn' you cannot set 'use_default_backup_encryption_key' to 'true'") : true + validate_backup_key_2 = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn == null && !var.use_same_kms_key_for_backups ? tobool("When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'.") : true # If no value passed for 'backup_encryption_key_crn' use the value of 'kms_key_crn' and perform validation of 'kms_key_crn' to check if region is supported by backup encryption key. - # For more info, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok and https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups" - backup_encryption_key_crn = var.use_default_backup_encryption_key == true ? null : (var.backup_encryption_key_crn != null ? var.backup_encryption_key_crn : var.kms_key_crn) + + # If 'use_ibm_owned_encryption_key' is true or 'use_default_backup_encryption_key' is true, default to null. + # If no value is passed for 'backup_encryption_key_crn', then default to use 'kms_key_crn'. + backup_encryption_key_crn = var.use_ibm_owned_encryption_key || var.use_default_backup_encryption_key ? null : (var.backup_encryption_key_crn != null ? var.backup_encryption_key_crn : var.kms_key_crn) # Determine if auto scaling is enabled auto_scaling_enabled = var.auto_scaling == null ? [] : [1] # Determine if host_flavor is used host_flavor_set = var.member_host_flavor != null ? true : false +} + +######################################################################################################################## +# Parse info from KMS key CRNs +######################################################################################################################## + +module "kms_key_crn_parser" { + count = var.use_ibm_owned_encryption_key ? 0 : 1 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.kms_key_crn +} - # Determine what KMS service is being used for database encryption - kms_service = var.kms_key_crn != null ? ( - can(regex(".*kms.*", var.kms_key_crn)) ? "kms" : ( - can(regex(".*hs-crypto.*", var.kms_key_crn)) ? "hs-crypto" : "unrecognized key type" - ) - ) : "no key crn" +module "backup_key_crn_parser" { + count = var.use_ibm_owned_encryption_key ? 0 : 1 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = local.backup_encryption_key_crn +} + +# Put parsed values into locals +locals { + kms_service = !var.use_ibm_owned_encryption_key ? module.kms_key_crn_parser[0].service_name : null + kms_account_id = !var.use_ibm_owned_encryption_key ? module.kms_key_crn_parser[0].account_id : null + kms_key_id = !var.use_ibm_owned_encryption_key ? module.kms_key_crn_parser[0].resource : null + kms_key_instance_guid = !var.use_ibm_owned_encryption_key ? module.kms_key_crn_parser[0].service_instance : null + backup_kms_service = !var.use_ibm_owned_encryption_key ? module.backup_key_crn_parser[0].service_name : null + backup_kms_account_id = !var.use_ibm_owned_encryption_key ? module.backup_key_crn_parser[0].account_id : null + backup_kms_key_id = !var.use_ibm_owned_encryption_key ? module.backup_key_crn_parser[0].resource : null + backup_kms_key_instance_guid = !var.use_ibm_owned_encryption_key ? module.backup_key_crn_parser[0].service_instance : null +} + +######################################################################################################################## +# KMS IAM Authorization Policies +######################################################################################################################## + +locals { + # only create auth policy if 'use_ibm_owned_encryption_key' is false, and 'skip_iam_authorization_policy' is false + create_kms_auth_policy = !var.use_ibm_owned_encryption_key && !var.skip_iam_authorization_policy ? 1 : 0 + # only create backup auth policy if 'use_ibm_owned_encryption_key' is false, 'skip_iam_authorization_policy' is false and 'use_same_kms_key_for_backups' is false + create_backup_kms_auth_policy = !var.use_ibm_owned_encryption_key && !var.skip_iam_authorization_policy && !var.use_same_kms_key_for_backups ? 1 : 0 } # Create IAM Access policy to allow RabbitMQ to access KMS for the encryption key resource "ibm_iam_authorization_policy" "kms_policy" { - count = var.kms_encryption_enabled == false || var.skip_iam_authorization_policy ? 0 : 1 - source_service_name = "messages-for-rabbitmq" - source_resource_group_id = var.resource_group_id - target_service_name = local.kms_service - target_resource_instance_id = var.existing_kms_instance_guid - roles = ["Reader"] - description = "Allow all RabbitMQ instances in the resource group ${var.resource_group_id} to read from the ${local.kms_service} instance GUID ${var.existing_kms_instance_guid}" + count = local.create_kms_auth_policy + source_service_name = "databases-for-rabbitmq" + source_resource_group_id = var.resource_group_id + roles = ["Reader"] + description = "Allow all RabbitMQ instances in the resource group ${var.resource_group_id} to read the ${local.kms_service} key ${local.kms_key_id} from the instance GUID ${local.kms_key_instance_guid}" + resource_attributes { + name = "serviceName" + operator = "stringEquals" + value = local.kms_service + } + resource_attributes { + name = "accountId" + operator = "stringEquals" + value = local.kms_account_id + } + resource_attributes { + name = "serviceInstance" + operator = "stringEquals" + value = local.kms_key_instance_guid + } + resource_attributes { + name = "resourceType" + operator = "stringEquals" + value = "key" + } + resource_attributes { + name = "resource" + operator = "stringEquals" + value = local.kms_key_id + } + # Scope of policy now includes the key, so ensure to create new policy before + # destroying old one to prevent any disruption to every day services. + lifecycle { + create_before_destroy = true + } } # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 resource "time_sleep" "wait_for_authorization_policy" { + count = local.create_kms_auth_policy depends_on = [ibm_iam_authorization_policy.kms_policy] create_duration = "30s" } +resource "ibm_iam_authorization_policy" "backup_kms_policy" { + count = local.create_backup_kms_auth_policy + source_service_name = "databases-for-rabbitmq" + source_resource_group_id = var.resource_group_id + roles = ["Reader"] + description = "Allow all RabbitMQ instances in the Resource Group ${var.resource_group_id} to read the ${local.backup_kms_service} key ${local.backup_kms_key_id} from the instance GUID ${local.backup_kms_key_instance_guid}" + resource_attributes { + name = "serviceName" + operator = "stringEquals" + value = local.backup_kms_service + } + resource_attributes { + name = "accountId" + operator = "stringEquals" + value = local.backup_kms_account_id + } + resource_attributes { + name = "serviceInstance" + operator = "stringEquals" + value = local.backup_kms_key_instance_guid + } + resource_attributes { + name = "resourceType" + operator = "stringEquals" + value = "key" + } + resource_attributes { + name = "resource" + operator = "stringEquals" + value = local.backup_kms_key_id + } + # Scope of policy now includes the key, so ensure to create new policy before + # destroying old one to prevent any disruption to every day services. + lifecycle { + create_before_destroy = true + } +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_backup_kms_authorization_policy" { + count = local.create_backup_kms_auth_policy + depends_on = [ibm_iam_authorization_policy.backup_kms_policy] + create_duration = "30s" +} + +######################################################################################################################## +# RabbitMQ instance +######################################################################################################################## + resource "ibm_database" "rabbitmq_database" { depends_on = [time_sleep.wait_for_authorization_policy] name = var.instance_name diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index 51a28d31..e3111dc2 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -34,13 +34,12 @@ No resources. | [admin\_pass](#input\_admin\_pass) | The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block. | `string` | `null` | no | | [auto\_scaling](#input\_auto\_scaling) | Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-autoscaling in the IBM Cloud Docs. |
object({
disk = object({
capacity_enabled = optional(bool, false)
free_space_less_than_percent = optional(number, 10)
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 3670016)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
memory = object({
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 114688)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
})
| `null` | no | | [backup\_crn](#input\_backup\_crn) | The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty. | `string` | `null` | no | -| [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a Hyper Protect Crypto Services use for encrypting the disk that holds deployment backups. Only used if var.kms\_encryption\_enabled is set to true. There are limitation per region on the Hyper Protect Crypto Services and region for those services. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups | `string` | `null` | no | +| [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of CBR rules to create |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
}))
| `[]` | no | | [cpu\_count](#input\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `0` | no | | [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `1024` | no | -| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect Crypto Services instance. | `string` | n/a | yes | | [instance\_name](#input\_instance\_name) | The name of the RabbitMQ instance | `string` | n/a | yes | -| [kms\_key\_crn](#input\_kms\_key\_crn) | The root key CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Services (HPCS) that you want to use for disk encryption. Only used if var.kms\_encryption\_enabled is set to true. | `string` | `null` | no | +| [kms\_key\_crn](#input\_kms\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [member\_host\_flavor](#input\_member\_host\_flavor) | Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor). | `string` | `null` | no | | [members](#input\_members) | Allocated number of members. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling) | `number` | `3` | no | | [memory\_mb](#input\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling). | `number` | `8192` | no | @@ -48,8 +47,11 @@ No resources. | [region](#input\_region) | The region where you want to deploy your instance. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the RabbitMQ instance will be created. | `string` | n/a | yes | | [service\_credential\_names](#input\_service\_credential\_names) | Map of name, role for service credentials that you want to create for the database | `map(string)` | `{}` | no | -| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of an IAM authorization policy that permits all RabbitMQ instances in the resource group to read the encryption key from the Hyper Protect Crypto Services instance. The HPCS instance is passed in through the var.existing\_kms\_instance\_guid variable. | `bool` | `false` | no | +| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of IAM authorization policies that permits all Databases for RabbitMQ instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key that was provided in the `kms_key_crn` and `backup_encryption_key_crn` inputs. This policy is required in order to enable KMS encryption, so only skip creation if there is one already present in your account. No policy is created if `use_ibm_owned_encryption_key` is true. | `bool` | `false` | no | | [tags](#input\_tags) | Optional list of tags to be added to the RabbitMQ instance. | `list(any)` | `[]` | no | +| [use\_default\_backup\_encryption\_key](#input\_use\_default\_backup\_encryption\_key) | When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `kms_key_crn`, or in `backup_encryption_key_crn` if a value is passed. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data. | `bool` | `false` | no | +| [use\_ibm\_owned\_encryption\_key](#input\_use\_ibm\_owned\_encryption\_key) | Set to true to use the default IBM Cloud® Databases randomly generated keys for disk and backups encryption. To control the encryption keys, use the `kms_key_crn` and `backup_encryption_key_crn` inputs. | `string` | `false` | no | +| [use\_same\_kms\_key\_for\_backups](#input\_use\_same\_kms\_key\_for\_backups) | Set this to false if you wan't to use a different key that you own to encrypt backups. When set to false, a value is required for the `backup_encryption_key_crn` input. Alternatiely set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Applies only if `use_ibm_owned_encryption_key` is false. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `bool` | `true` | no | | [users](#input\_users) | A list of users that you want to create on the database. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service\_credential\_names) is sufficient to control access to the RabbitMQ instance. This blocks creates native RabbitMQ database users, more info on that can be found here https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-user-management |
list(object({
name = string
password = string # pragma: allowlist secret
type = optional(string)
role = optional(string)
}))
| `[]` | no | ### Outputs diff --git a/modules/fscloud/main.tf b/modules/fscloud/main.tf index c5c05536..921cebd1 100644 --- a/modules/fscloud/main.tf +++ b/modules/fscloud/main.tf @@ -1,26 +1,27 @@ module "rabbitmq_database" { - source = "../../" - resource_group_id = var.resource_group_id - instance_name = var.instance_name - region = var.region - skip_iam_authorization_policy = var.skip_iam_authorization_policy - endpoints = "private" - rabbitmq_version = var.rabbitmq_version - tags = var.tags - access_tags = var.access_tags - kms_encryption_enabled = true - existing_kms_instance_guid = var.existing_kms_instance_guid - service_credential_names = var.service_credential_names - backup_encryption_key_crn = var.backup_encryption_key_crn - kms_key_crn = var.kms_key_crn - admin_pass = var.admin_pass - members = var.members - users = var.users - memory_mb = var.memory_mb - disk_mb = var.disk_mb - cpu_count = var.cpu_count - member_host_flavor = var.member_host_flavor - auto_scaling = var.auto_scaling - cbr_rules = var.cbr_rules - backup_crn = var.backup_crn + source = "../../" + resource_group_id = var.resource_group_id + instance_name = var.instance_name + region = var.region + skip_iam_authorization_policy = var.skip_iam_authorization_policy + endpoints = "private" + rabbitmq_version = var.rabbitmq_version + tags = var.tags + access_tags = var.access_tags + use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key + use_same_kms_key_for_backups = var.use_same_kms_key_for_backups + use_default_backup_encryption_key = var.use_default_backup_encryption_key + kms_key_crn = var.kms_key_crn + backup_crn = var.backup_crn + backup_encryption_key_crn = var.backup_encryption_key_crn + service_credential_names = var.service_credential_names + admin_pass = var.admin_pass + members = var.members + users = var.users + memory_mb = var.memory_mb + disk_mb = var.disk_mb + cpu_count = var.cpu_count + member_host_flavor = var.member_host_flavor + auto_scaling = var.auto_scaling + cbr_rules = var.cbr_rules } diff --git a/modules/fscloud/variables.tf b/modules/fscloud/variables.tf index 10408e18..67e54b32 100644 --- a/modules/fscloud/variables.tf +++ b/modules/fscloud/variables.tf @@ -130,27 +130,56 @@ variable "auto_scaling" { # Encryption ############################################################## +variable "use_ibm_owned_encryption_key" { + type = string + description = "Set to true to use the default IBM Cloud® Databases randomly generated keys for disk and backups encryption. To control the encryption keys, use the `kms_key_crn` and `backup_encryption_key_crn` inputs." + default = false +} + variable "kms_key_crn" { type = string - description = "The root key CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Services (HPCS) that you want to use for disk encryption. Only used if var.kms_encryption_enabled is set to true." + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." default = null + validation { + condition = anytrue([ + var.kms_key_crn == null, + can(regex(".*kms.*", var.kms_key_crn)), + can(regex(".*hs-crypto.*", var.kms_key_crn)), + ]) + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance." + } +} + +variable "use_same_kms_key_for_backups" { + type = bool + description = "Set this to false if you wan't to use a different key that you own to encrypt backups. When set to false, a value is required for the `backup_encryption_key_crn` input. Alternatiely set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Applies only if `use_ibm_owned_encryption_key` is false. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." + default = true } variable "backup_encryption_key_crn" { type = string - description = "The CRN of a Hyper Protect Crypto Services use for encrypting the disk that holds deployment backups. Only used if var.kms_encryption_enabled is set to true. There are limitation per region on the Hyper Protect Crypto Services and region for those services. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups" + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." default = null + validation { + condition = anytrue([ + var.backup_encryption_key_crn == null, + can(regex(".*kms.*", var.backup_encryption_key_crn)), + can(regex(".*hs-crypto.*", var.backup_encryption_key_crn)), + ]) + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance in one of the supported backup regions." + } } -variable "skip_iam_authorization_policy" { +variable "use_default_backup_encryption_key" { type = bool - description = "Set to true to skip the creation of an IAM authorization policy that permits all RabbitMQ instances in the resource group to read the encryption key from the Hyper Protect Crypto Services instance. The HPCS instance is passed in through the var.existing_kms_instance_guid variable." + description = "When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `kms_key_crn`, or in `backup_encryption_key_crn` if a value is passed. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data." default = false } -variable "existing_kms_instance_guid" { - type = string - description = "The GUID of the Hyper Protect Crypto Services instance." +variable "skip_iam_authorization_policy" { + type = bool + description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for RabbitMQ instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key that was provided in the `kms_key_crn` and `backup_encryption_key_crn` inputs. This policy is required in order to enable KMS encryption, so only skip creation if there is one already present in your account. No policy is created if `use_ibm_owned_encryption_key` is true." + default = false } ############################################################## diff --git a/reference-architecture/deployable-architecture-rabbitmq.svg b/reference-architecture/deployable-architecture-rabbitmq.svg new file mode 100644 index 00000000..1071c607 --- /dev/null +++ b/reference-architecture/deployable-architecture-rabbitmq.svg @@ -0,0 +1,4 @@ + + + +
IBM Cloud
IBM Cloud
KMS Encryption
KMS Encryption
Region
Region
Resource Group
Resource Group
IBM Cloud RabbitMQ Instance
IBM Cloud RabbitMQ Instance
RMQ
RMQ
Text is not SVG - cannot display
\ No newline at end of file diff --git a/solutions/standard/DA-types.md b/solutions/standard/DA-types.md new file mode 100644 index 00000000..6536d19f --- /dev/null +++ b/solutions/standard/DA-types.md @@ -0,0 +1,203 @@ +# Configuring complex inputs in Databases for RabbitMQ + +Several optional input variables in the IBM Cloud [Databases for RabbitMQ deployable architecture](https://cloud.ibm.com/catalog#deployable_architecture) use complex object types. You specify these inputs when you configure deployable architecture. + +- [Service credentials](#svc-credential-name) (`service_credential_names`) +- [Service credential secrets](#service-credential-secrets) (`service_credential_secrets`) +- [Users](#users) (`users`) +- [Autoscaling](#autoscaling) (`auto_scaling`) + +## Service credentials + +You can specify a set of IAM credentials to connect to the database with the `service_credential_names` input variable. Include a credential name and IAM service role for each key-value pair. Each role provides a specific level of access to the database. For more information, see [Adding and viewing credentials](https://cloud.ibm.com/docs/account?topic=account-service_credentials&interface=ui). If you want to add service credentials to secret manager and to allow secret manager to manage it, you should use `service_credential_secrets` , see [Service credential secrets](#service-credential-secrets) + +- Variable name: `service_credential_names`. +- Type: A map. The key is the name of the service credential. The value is the role that is assigned to that credential. +- Default value: An empty map (`{}`). + +### Options for service_credential_names + +- Key (required): The name of the service credential. +- Value (required): The IAM service role that is assigned to the credential. The following values are valid for service credential roles: "Administrator", "Operator", "Viewer" and "Editor". For more information, see [IBM Cloud IAM roles](https://cloud.ibm.com/docs/account?topic=account-userroles). + +### Example service credential + +```hcl + { + "rabbitmq_admin" : "Administrator", + "rabbitmq_reader" : "Operator", + "rabbitmq_viewer" : "Viewer", + "rabbitmq_editor" : "Editor" + } +``` + +## Service credential secrets + +When you add an IBM Database for RabbitMQ deployable architecture from the IBM Cloud catalog to IBM Cloud Project , you can configure service credentials. In edit mode for the projects configuration, from the configure panel click the optional tab. + +To enter a custom value, use the edit action to open the "Edit Array" panel. Add the service credential secrets configurations to the array here. + +In the configuration, specify the secret group name, whether it already exists or will be created and include all the necessary service credential secrets that need to be created within that secret group. + + [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/sm_service_credentials_secret) about service credential secrets. + +- Variable name: `service_credential_secrets`. +- Type: A list of objects that represent a service credential secret groups and secrets +- Default value: An empty list (`[]`) + +### Options for service_credential_secrets + +- `secret_group_name` (required): A unique human-readable name that identifies this service credential secret group. +- `secret_group_description` (optional, default = `null`): A human-readable description for this secret group. +- `existing_secret_group`: (optional, default = `false`): Set to true, if secret group name provided in the variable `secret_group_name` already exists. +- `service_credentials`: (optional, default = `[]`): A list of object that represents a service credential secret. + +#### Options for service_credentials + +- `secret_name`: (required): A unique human-readable name of the secret to create. +- `service_credentials_source_service_role`: (required): The role to give the service credential in the Databases for RabbitMQ service. Acceptable values are `Writer`, `Reader`, `Manager`, and `None` +- `secret_labels`: (optional, default = `[]`): Labels of the secret to create. Up to 30 labels can be created. Labels can be 2 - 30 characters, including spaces. Special characters that are not permitted include the angled brackets (<>), comma (,), colon (:), ampersand (&), and vertical pipe character (|). +- `secret_auto_rotation`: (optional, default = `true`): Whether to configure automatic rotation of service credential. +- `secret_auto_rotation_unit`: (optional, default = `day`): Specifies the unit of time for rotation of a secret. Acceptable values are `day` or `month`. +- `secret_auto_rotation_interval`: (optional, default = `89`): Specifies the rotation interval for the rotation unit. +- `service_credentials_ttl`: (optional, default = `7776000`): The time-to-live (TTL) to assign to generated service credentials (in seconds). +- `service_credential_secret_description`: (optional, default = `null`): Description of the secret to create. + +The following example includes all the configuration options for four service credentials and two secret groups. +```hcl +[ + { + "secret_group_name": "sg-1" + "existing_secret_group": true + "service_credentials": [ # pragma: allowlist secret + { + "secret_name": "cred-1" + "service_credentials_source_service_role": "Writer" + "secret_labels": ["test-writer-1", "test-writer-2"] + "secret_auto_rotation": true + "secret_auto_rotation_unit": "day" + "secret_auto_rotation_interval": 89 + "service_credentials_ttl": 7776000 + "service_credential_secret_description": "sample description" + }, + { + "secret_name": "cred-2" + "service_credentials_source_service_role": "Reader" + } + ] + }, + { + "secret_group_name": "sg-2" + "service_credentials": [ # pragma: allowlist secret + { + "secret_name": "cred-3" + "service_credentials_source_service_role": "Editor" + }, + { + "secret_name": "cred-4" + "service_credentials_source_service_role": "None" + } + ] + } +] +``` + +## Users + +If you can't use the IAM-enabled `service_credential_names` input variable for access, you can create users and roles directly in the database. For more information, see [Managing users and roles](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-user-management&interface=ui). + +:exclamation: **Important:** The `users` input contains sensitive information (the user's password). + +- Variable name: `users`. +- Type: A list of objects that represent a user +- Default value: An empty list (`[]`) + +### Options for users + + - `name` (required): The username for the user account. + - `password` (required): The password for the user account in the range of 10-32 characters. + - `type` (required): The user type. The "type" field is required to generate the connection string for the outputs. + - `role`: The user role. The role determines the user's access level and permissions. + +### Example users + + +```hcl +[ + { + "name": "es_admin", + "password": "securepassword123", # pragma: allowlist secret + "type": "database", + }, + { + "name": "es_reader", + "password": "readpassword123", # pragma: allowlist secret + "type": "ops_manager" + } +] +``` + +## Autoscaling + +The Autoscaling variable sets the rules for how database increase resources in response to usage. Make sure you understand the effects of autoscaling, especially for production environments. For more information, see [Autoscaling](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-autoscaling&interface=ui#autoscaling-consider). + +- Variable name: `auto_scaling` +- Type: An object with `disk` and `memory` configurations. + +### Disk options for auto_scaling + +Disk autoscaling specifies thresholds when scaling can occur based on disk usage, disk I/O utilization, or both. + +The disk object in the `auto_scaling` input contains the following options. All options are optional. + +- `capacity_enabled`: Whether disk capacity autoscaling is enabled (default: `false`). +- `free_space_less_than_percent`: The percentage of free disk space that triggers autoscaling (default: `10`). +- `io_above_percent`: The percentage of I/O (input/output) disk usage that triggers autoscaling (default: `90`). +- `io_enabled`: Indicates whether IO-based autoscaling is enabled (default: `false`). +- `io_over_period`: How long I/O usage is evaluated for autoscaling (default: `"15m"` (15 minutes)). +- `rate_increase_percent`: The percentage increase in disk capacity when autoscaling is triggered (default: `10`). +- `rate_limit_mb_per_member`: The limit in megabytes for the rate of disk increase per member (default: `3670016`). +- `rate_period_seconds`: How long (in seconds) the rate limit is applied for disk (default: `900` (15 minutes)). +- `rate_units`: The units to use for the rate increase (default: `"mb"` (megabytes)). + + +### Memory options for auto_scaling + +The memory object within auto_scaling contains the following options. All options are optional. + +- `io_above_percent`: The percentage of I/O memory usage that triggers autoscaling (default: `90`). +- `io_enabled`: Whether IO-based autoscaling for memory is enabled (default: `false`). +- `io_over_period`: How long I/O usage is evaluated for memory autoscaling (default: `"15m"` (15 minutes)). +- `rate_increase_percent`: The percentage increase in memory capacity that triggers autoscaling (default: `10`). +- `rate_limit_mb_per_member`: The limit in megabytes for the rate of memory increase per member (default: `114688`). +- `rate_period_seconds`: How long (in seconds) the rate limit is applied for memory (default: `900` (15 minutes)). +- `rate_units`: The memory size units to use for the rate increase (default: `"mb"` (megabytes)). + +### Example autoscaling + +The following example shows values for both disk and memory for the `auto_scaling` input. + +```hcl +{ + "disk": { + "capacity_enabled": true, + "free_space_less_than_percent": 15, + "io_above_percent": 85, + "io_enabled": true, + "io_over_period": "15m", + "rate_increase_percent": 15, + "rate_limit_mb_per_member": 3670016, + "rate_period_seconds": 900, + "rate_units": "mb" + }, + "memory": { + "io_above_percent": 90, + "io_enabled": true, + "io_over_period": "15m", + "rate_increase_percent": 10, + "rate_limit_mb_per_member": 114688, + "rate_period_seconds": 900, + "rate_units": "mb" + } +} +``` \ No newline at end of file diff --git a/solutions/standard/README.md b/solutions/standard/README.md new file mode 100644 index 00000000..04b4a86a --- /dev/null +++ b/solutions/standard/README.md @@ -0,0 +1,13 @@ + # IBM Cloud Databases for RabbitMQ + +This architecture creates an instance of IBM Cloud Databases for RabbitMQ and supports provisioning of the following resources: + +- A resource group, if one is not passed in. +- A KMS root key, if one is not passed in. +- An IBM Cloud Databases for RabbitMQ instance with KMS encryption. +- Autoscaling rules for the database instance, if provided. +- Service credential secrets and store them in secret manager. + +![fscloud-rabbitmq](../../reference-architecture/deployable-architecture-rabbitmq.svg) + +:exclamation: **Important:** This solution is not intended to be called by other modules because it contains a provider configuration and is not compatible with the `for_each`, `count`, and `depends_on` arguments. For more information, see [Providers Within Modules](https://developer.hashicorp.com/terraform/language/modules/develop/providers). diff --git a/solutions/standard/catalogValidationValues.json.template b/solutions/standard/catalogValidationValues.json.template new file mode 100644 index 00000000..e69e502f --- /dev/null +++ b/solutions/standard/catalogValidationValues.json.template @@ -0,0 +1,8 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY, + "region": "us-south", + "tags": $TAGS, + "name": $PREFIX, + "resource_group_name": $PREFIX, + "existing_kms_instance_crn": $HPCS_US_SOUTH_CRN +} diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf new file mode 100644 index 00000000..9817ed4e --- /dev/null +++ b/solutions/standard/main.tf @@ -0,0 +1,336 @@ +####################################################################################################################### +# Resource Group +####################################################################################################################### + +module "resource_group" { + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.6" + resource_group_name = var.use_existing_resource_group == false ? (var.prefix != null ? "${var.prefix}-${var.resource_group_name}" : var.resource_group_name) : null + existing_resource_group_name = var.use_existing_resource_group == true ? var.resource_group_name : null +} + +####################################################################################################################### +# KMS related variable validation +# (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) +# +# TODO: Replace with terraform cross variable validation: https://github.ibm.com/GoldenEye/issues/issues/10836 +####################################################################################################################### + +locals { + # tflint-ignore: terraform_unused_declarations + validate_kms_1 = var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn != null || var.existing_kms_key_crn != null || var.existing_backup_kms_key_crn != null) ? tobool("When setting values for 'existing_kms_instance_crn', 'existing_kms_key_crn' or 'existing_backup_kms_key_crn', the 'use_ibm_owned_encryption_key' input must be set to false.") : true + # tflint-ignore: terraform_unused_declarations + validate_kms_2 = !var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null) ? tobool("When 'use_ibm_owned_encryption_key' is false, a value is required for either 'existing_kms_instance_crn' (to create a new key), or 'existing_kms_key_crn' to use an existing key.") : true +} + +####################################################################################################################### +# KMS encryption key +####################################################################################################################### + +locals { + create_new_kms_key = !var.use_ibm_owned_encryption_key && var.existing_kms_key_crn == null ? true : false # no need to create any KMS resources if passing an existing key, or using IBM owned keys + rabbitmq_key_name = var.prefix != null ? "${var.prefix}-${var.key_name}" : var.key_name + rabbitmq_key_ring_name = var.prefix != null ? "${var.prefix}-${var.key_ring_name}" : var.key_ring_name +} + +module "kms" { + providers = { + ibm = ibm.kms + } + count = local.create_new_kms_key ? 1 : 0 + source = "terraform-ibm-modules/kms-all-inclusive/ibm" + version = "4.19.2" + create_key_protect_instance = false + region = local.kms_region + existing_kms_instance_crn = var.existing_kms_instance_crn + key_ring_endpoint_type = var.kms_endpoint_type + key_endpoint_type = var.kms_endpoint_type + keys = [ + { + key_ring_name = local.rabbitmq_key_ring_name + existing_key_ring = false + keys = [ + { + key_name = local.rabbitmq_key_name + standard_key = false + rotation_interval_month = 3 + dual_auth_delete_enabled = false + force_delete = true + } + ] + } + ] +} + +######################################################################################################################## +# Parse KMS info from given CRNs +######################################################################################################################## + +module "kms_instance_crn_parser" { + count = var.existing_kms_instance_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_kms_instance_crn +} + +module "kms_key_crn_parser" { + count = var.existing_kms_key_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_kms_key_crn +} + +module "kms_backup_key_crn_parser" { + count = var.existing_backup_kms_key_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_backup_kms_key_crn +} + +####################################################################################################################### +# KMS IAM Authorization Policies +# - only created if user passes a value for 'ibmcloud_kms_api_key' (used when KMS is in different account to RabbitMQ) +# - if no value passed for 'ibmcloud_kms_api_key', the auth policy is created by the RabbitMQ module +####################################################################################################################### + +# Lookup account ID +data "ibm_iam_account_settings" "iam_account_settings" { +} + +locals { + account_id = data.ibm_iam_account_settings.iam_account_settings.account_id + create_cross_account_kms_auth_policy = !var.skip_rabbitmq_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key + create_cross_account_backup_kms_auth_policy = !var.skip_rabbitmq_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key && var.existing_backup_kms_key_crn != null + + # If KMS encryption enabled (and existing ES instance is not being passed), parse details from the existing key if being passed, otherwise get it from the key that the DA creates + kms_account_id = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].account_id : module.kms_instance_crn_parser[0].account_id + kms_service = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_name : module.kms_instance_crn_parser[0].service_name + kms_instance_guid = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_instance : module.kms_instance_crn_parser[0].service_instance + kms_key_crn = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.rabbitmq_key_ring_name, local.rabbitmq_key_name)].crn + kms_key_id = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].resource : module.kms[0].keys[format("%s.%s", local.rabbitmq_key_ring_name, local.rabbitmq_key_name)].key_id + kms_region = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].region : module.kms_instance_crn_parser[0].region + + # If creating KMS cross account policy for backups, parse backup key details from passed in key CRN + backup_kms_account_id = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].account_id : local.kms_account_id + backup_kms_service = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].service_name : local.kms_service + backup_kms_instance_guid = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].service_instance : local.kms_instance_guid + backup_kms_key_id = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].resource : local.kms_key_id + backup_kms_key_crn = var.use_ibm_owned_encryption_key ? null : var.existing_backup_kms_key_crn + # Always use same key for backups unless user explicially passed a value for 'existing_backup_kms_key_crn' + use_same_kms_key_for_backups = var.existing_backup_kms_key_crn == null ? true : false +} + +# Create auth policy (scoped to exact KMS key) +resource "ibm_iam_authorization_policy" "kms_policy" { + count = local.create_cross_account_kms_auth_policy ? 1 : 0 + provider = ibm.kms + source_service_account = local.account_id + source_service_name = "messages-for-rabbitmq" + source_resource_group_id = module.resource_group.resource_group_id + roles = ["Reader"] + description = "Allow all RabbitMQ instances in the resource group ${module.resource_group.resource_group_id} in the account ${local.account_id} to read the ${local.kms_service} key ${local.kms_key_id} from the instance GUID ${local.kms_instance_guid}" + resource_attributes { + name = "serviceName" + operator = "stringEquals" + value = local.kms_service + } + resource_attributes { + name = "accountId" + operator = "stringEquals" + value = local.kms_account_id + } + resource_attributes { + name = "serviceInstance" + operator = "stringEquals" + value = local.kms_instance_guid + } + resource_attributes { + name = "resourceType" + operator = "stringEquals" + value = "key" + } + resource_attributes { + name = "resource" + operator = "stringEquals" + value = local.kms_key_id + } + # Scope of policy now includes the key, so ensure to create new policy before + # destroying old one to prevent any disruption to every day services. + lifecycle { + create_before_destroy = true + } +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_authorization_policy" { + count = local.create_cross_account_kms_auth_policy ? 1 : 0 + depends_on = [ibm_iam_authorization_policy.kms_policy] + create_duration = "30s" +} + +# Create auth policy (scoped to exact KMS key for backups) +resource "ibm_iam_authorization_policy" "backup_kms_policy" { + count = local.create_cross_account_backup_kms_auth_policy ? 1 : 0 + provider = ibm.kms + source_service_account = local.account_id + source_service_name = "messages-for-rabbitmq" + source_resource_group_id = module.resource_group.resource_group_id + roles = ["Reader"] + description = "Allow all RabbitMQ instances in the resource group ${module.resource_group.resource_group_id} in the account ${local.account_id} to read the ${local.backup_kms_service} key ${local.backup_kms_key_id} from the instance GUID ${local.backup_kms_instance_guid}" + resource_attributes { + name = "serviceName" + operator = "stringEquals" + value = local.backup_kms_service + } + resource_attributes { + name = "accountId" + operator = "stringEquals" + value = local.backup_kms_account_id + } + resource_attributes { + name = "serviceInstance" + operator = "stringEquals" + value = local.backup_kms_instance_guid + } + resource_attributes { + name = "resourceType" + operator = "stringEquals" + value = "key" + } + resource_attributes { + name = "resource" + operator = "stringEquals" + value = local.backup_kms_key_id + } + # Scope of policy now includes the key, so ensure to create new policy before + # destroying old one to prevent any disruption to every day services. + lifecycle { + create_before_destroy = true + } +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_backup_kms_authorization_policy" { + count = local.create_cross_account_backup_kms_auth_policy ? 1 : 0 + depends_on = [ibm_iam_authorization_policy.backup_kms_policy] + create_duration = "30s" +} + +####################################################################################################################### +# RabbitMQ admin password +####################################################################################################################### + +resource "random_password" "admin_password" { + count = var.admin_pass == null ? 1 : 0 + length = 32 + special = true + override_special = "-_" + min_numeric = 1 +} + +locals { + # _- are invalid first characters + # if - replace first char with J + # elseif _ replace first char with K + # else use asis + generated_admin_password = startswith(random_password.admin_password[0].result, "-") ? "J${substr(random_password.admin_password[0].result, 1, -1)}" : startswith(random_password.admin_password[0].result, "_") ? "K${substr(random_password.admin_password[0].result, 1, -1)}" : random_password.admin_password[0].result + + # admin password to use + admin_pass = var.admin_pass == null ? local.generated_admin_password : var.admin_pass +} + +####################################################################################################################### +# RabbitMQ +####################################################################################################################### + +# Create new instance +module "rabbitmq" { + source = "../../modules/fscloud" + depends_on = [time_sleep.wait_for_authorization_policy, time_sleep.wait_for_backup_kms_authorization_policy] + resource_group_id = module.resource_group.resource_group_id + instance_name = var.prefix != null ? "${var.prefix}-${var.name}" : var.name + region = var.region + rabbitmq_version = var.rabbitmq_version + skip_iam_authorization_policy = var.skip_rabbitmq_kms_auth_policy + use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key + kms_key_crn = local.kms_key_crn + backup_encryption_key_crn = local.backup_kms_key_crn + use_same_kms_key_for_backups = local.use_same_kms_key_for_backups + use_default_backup_encryption_key = var.use_default_backup_encryption_key + access_tags = var.access_tags + tags = var.tags + admin_pass = local.admin_pass + users = var.users + members = var.members + member_host_flavor = var.member_host_flavor + memory_mb = var.member_memory_mb + disk_mb = var.member_disk_mb + cpu_count = var.member_cpu_count + auto_scaling = var.auto_scaling + service_credential_names = var.service_credential_names + backup_crn = var.backup_crn +} + +locals { + create_sm_auth_policy = var.skip_rabbitmq_sm_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 +} + +# create a service authorization between Secrets Manager and the target service (Databases for RabbitMQ) +resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { + count = local.create_sm_auth_policy + source_service_name = "secrets-manager" + source_resource_instance_id = local.existing_secrets_manager_instance_guid + target_service_name = "messages-for-rabbitmq" + target_resource_instance_id = module.rabbitmq.guid + roles = ["Key Manager"] + description = "Allow Secrets Manager with instance id ${local.existing_secrets_manager_instance_guid} to manage key for the messages-for-rabbitmq instance" +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_rabbitmq_authorization_policy" { + count = local.create_sm_auth_policy + depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager] + create_duration = "30s" +} + +locals { + service_credential_secrets = [ + for service_credentials in var.service_credential_secrets : { + secret_group_name = service_credentials.secret_group_name + secret_group_description = service_credentials.secret_group_description + existing_secret_group = service_credentials.existing_secret_group + secrets = [ + for secret in service_credentials.service_credentials : { + secret_name = secret.secret_name + secret_labels = secret.secret_labels + secret_auto_rotation = secret.secret_auto_rotation + secret_auto_rotation_unit = secret.secret_auto_rotation_unit + secret_auto_rotation_interval = secret.secret_auto_rotation_interval + service_credentials_ttl = secret.service_credentials_ttl + service_credential_secret_description = secret.service_credential_secret_description + service_credentials_source_service_role = secret.service_credentials_source_service_role + service_credentials_source_service_crn = module.rabbitmq.crn + secret_type = "service_credentials" #checkov:skip=CKV_SECRET_6 + } + ] + } + ] + + existing_secrets_manager_instance_crn_split = var.existing_secrets_manager_instance_crn != null ? split(":", var.existing_secrets_manager_instance_crn) : null + existing_secrets_manager_instance_guid = var.existing_secrets_manager_instance_crn != null ? element(local.existing_secrets_manager_instance_crn_split, length(local.existing_secrets_manager_instance_crn_split) - 3) : null + existing_secrets_manager_instance_region = var.existing_secrets_manager_instance_crn != null ? element(local.existing_secrets_manager_instance_crn_split, length(local.existing_secrets_manager_instance_crn_split) - 5) : null + + # tflint-ignore: terraform_unused_declarations + validate_sm_crn = length(local.service_credential_secrets) > 0 && var.existing_secrets_manager_instance_crn == null ? tobool("`existing_secrets_manager_instance_crn` is required when adding service credentials to a secrets manager secret.") : false +} + +module "secrets_manager_service_credentials" { + count = length(local.service_credential_secrets) > 0 ? 1 : 0 + depends_on = [time_sleep.wait_for_rabbitmq_authorization_policy] + source = "terraform-ibm-modules/secrets-manager/ibm//modules/secrets" + version = "1.19.10" + existing_sm_instance_guid = local.existing_secrets_manager_instance_guid + existing_sm_instance_region = local.existing_secrets_manager_instance_region + endpoint_type = var.existing_secrets_manager_endpoint_type + secrets = local.service_credential_secrets +} diff --git a/solutions/standard/outputs.tf b/solutions/standard/outputs.tf new file mode 100644 index 00000000..8c8f2ca3 --- /dev/null +++ b/solutions/standard/outputs.tf @@ -0,0 +1,66 @@ +############################################################################## +# Outputs +############################################################################## + +output "id" { + description = "RabbitMQ instance id" + value = module.rabbitmq.id +} + +output "version" { + description = "RabbitMQ instance version" + value = module.rabbitmq.version +} + +output "guid" { + description = "RabbitMQ instance guid" + value = module.rabbitmq.guid +} + +output "crn" { + description = "RabbitMQ instance crn" + value = module.rabbitmq.crn +} + +output "cbr_rule_ids" { + description = "CBR rule ids created to restrict RabbitMQ" + value = module.rabbitmq.cbr_rule_ids +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.rabbitmq.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.rabbitmq.service_credentials_object + sensitive = true +} + +output "adminuser" { + description = "Database admin user name" + value = module.rabbitmq.adminuser +} + +output "hostname" { + description = "Database connection hostname" + value = module.rabbitmq.hostname +} + +output "port" { + description = "Database connection port" + value = module.rabbitmq.port +} + +output "certificate_base64" { + description = "Database connection certificate" + value = module.rabbitmq.certificate_base64 + sensitive = true +} + +output "secrets_manager_secrets" { + description = "Service credential secrets" + value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null +} diff --git a/solutions/standard/provider.tf b/solutions/standard/provider.tf new file mode 100644 index 00000000..65c38f7d --- /dev/null +++ b/solutions/standard/provider.tf @@ -0,0 +1,11 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region + visibility = var.provider_visibility +} +provider "ibm" { + alias = "kms" + ibmcloud_api_key = var.ibmcloud_kms_api_key != null ? var.ibmcloud_kms_api_key : var.ibmcloud_api_key + region = local.kms_region + visibility = var.provider_visibility +} diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf new file mode 100644 index 00000000..f9e15379 --- /dev/null +++ b/solutions/standard/variables.tf @@ -0,0 +1,297 @@ +############################################################################## +# Input Variables +############################################################################## + +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API key to deploy resources." + sensitive = true +} +variable "use_existing_resource_group" { + type = bool + description = "Whether to use an existing resource group." + default = false +} + +variable "resource_group_name" { + type = string + description = "The name of a new or an existing resource group to provision the Databases for RabbitMQ in. If a prefix input variable is specified, the prefix is added to the name in the `-` format." +} + +variable "prefix" { + type = string + description = "Prefix to add to all resources created by this solution." + default = null +} + +variable "name" { + type = string + description = "The name of the Databases for RabbitMQ instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "rabbitmq" +} + +variable "region" { + description = "The region where you want to deploy your instance." + type = string + default = "us-south" +} + +variable "rabbitmq_version" { + description = "The version of the Databases for RabbitMQ instance. If no value is specified, the current preferred version of Databases for RabbitMQ is used." + type = string + default = null +} + +############################################################################## +# ICD hosting model properties +############################################################################## + +variable "members" { + type = number + description = "The number of members that are allocated. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)." + default = 3 +} + +variable "member_memory_mb" { + type = number + description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)" + default = 4096 +} + +variable "member_cpu_count" { + type = number + description = "The dedicated CPU per member that is allocated. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)." + default = 0 +} + +variable "member_disk_mb" { + type = number + description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)." + default = 10240 +} + +variable "member_host_flavor" { + type = string + description = "The host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." + default = "multitenant" +} + +variable "service_credential_names" { + description = "Map of name, role for service credentials that you want to create for the database. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/blob/main/solutions/standard/DA-types.md#svc-credential-name)" + type = map(string) + default = {} +} + +variable "admin_pass" { + type = string + description = "The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block." + default = null + sensitive = true +} + +variable "users" { + type = list(object({ + name = string + password = string # pragma: allowlist secret + type = string # "type" is required to generate the connection string for the outputs. + role = optional(string) + })) + default = [] + sensitive = true +description = "A list of users that you want to create on the database. Users block is supported by RabbitMQ version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the RabbitMQ instance. This blocks creates native RabbitMQ database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/blob/main/solutions/standard/DA-types.md#users)" +} + +variable "tags" { + type = list(any) + description = "The list of tags to be added to the Databases for RabbitMQ instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the Databases for RabbitMQ instance created by the solution. [Learn more](https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial)." + default = [] +} + +############################################################## +# Encryption +############################################################## + +variable "use_ibm_owned_encryption_key" { + type = bool + description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for `existing_kms_instance_crn` to create a new key, or `existing_kms_key_crn` and/or `existing_backup_kms_key_crn` to use an existing key." + default = false +} + +variable "existing_kms_instance_crn" { + type = string + description = "The CRN of a Key Protect or Hyper Protect Crypto Services instance. Required to create a new encryption key and key ring which will be used to encrypt both deployment data and backups. Applies only if `use_ibm_owned_encryption_key` is false. To use an existing key, pass values for `existing_kms_key_crn` and/or `existing_backup_kms_key_crn`. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." + default = null +} + +variable "existing_kms_key_crn" { + type = string + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the optional `existing_backup_kms_key_crn` input. If no value is passed a new key will be created in the instance specified in the `existing_kms_instance_crn` input. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." + default = null +} + +variable "kms_endpoint_type" { + type = string + description = "The type of endpoint to use for communicating with the Key Protect or Hyper Protect Crypto Services instance. Possible values: `public`, `private`. Applies only if `existing_kms_key_crn` is not specified." + default = "private" + validation { + condition = can(regex("public|private", var.kms_endpoint_type)) + error_message = "The kms_endpoint_type value must be 'public' or 'private'." + } +} + +variable "skip_rabbitmq_kms_auth_policy" { + type = bool + description = "Whether to create an IAM authorization policy that permits all Databases for RabbitMQ instances in the resource group to read the encryption key from the Hyper Protect Crypto Services instance specified in the `existing_kms_instance_crn` variable." + default = false +} + +variable "ibmcloud_kms_api_key" { + type = string + description = "The IBM Cloud API key that can create a root key and key ring in the key management service (KMS) instance. If not specified, the 'ibmcloud_api_key' variable is used. Specify this key if the instance in `existing_kms_instance_crn` is in an account that's different from the RabbitMQ instance. Leave this input empty if the same account owns both instances." + sensitive = true + default = null +} + +variable "key_ring_name" { + type = string + default = "rabbitmq-key-ring" + description = "The name for the key ring created for the Databases for RabbitMQ key. Applies only if not specifying an existing key. If a prefix input variable is specified, the prefix is added to the name in the `-` format." +} + +variable "key_name" { + type = string + default = "rabbitmq-key" + description = "The name for the key created for the Databases for RabbitMQ key. Applies only if not specifying an existing key. If a prefix input variable is specified, the prefix is added to the name in the `-` format." +} + +variable "existing_backup_kms_key_crn" { + type = string + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false. If no value is passed, the value of `existing_kms_key_crn` is used. If no value is passed for `existing_kms_key_crn`, a new key will be created in the instance specified in the `existing_kms_instance_crn` input. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." + default = null +} + +variable "use_default_backup_encryption_key" { + type = bool + description = "When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `existing_kms_key_crn`, in `existing_backup_kms_key_crn`, or with a new key that will be created in the instance specified in the `existing_kms_instance_crn` input. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data." + default = false +} + +variable "backup_crn" { + type = string + description = "The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty." + default = null + + validation { + condition = anytrue([ + var.backup_crn == null, + can(regex("^crn:.*:backup:", var.backup_crn)) + ]) + error_message = "backup_crn must be null OR starts with 'crn:' and contains ':backup:'" + } +} +variable "provider_visibility" { + description = "Set the visibility value for the IBM terraform provider. Supported values are `public`, `private`, `public-and-private`. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/guides/custom-service-endpoints)." + type = string + default = "private" + + validation { + condition = contains(["public", "private", "public-and-private"], var.provider_visibility) + error_message = "Invalid visibility option. Allowed values are 'public', 'private', or 'public-and-private'." + } +} + +############################################################## +# Auto Scaling +############################################################## + +variable "auto_scaling" { + type = object({ + disk = object({ + capacity_enabled = optional(bool, false) + free_space_less_than_percent = optional(number, 10) + io_above_percent = optional(number, 90) + io_enabled = optional(bool, false) + io_over_period = optional(string, "15m") + rate_increase_percent = optional(number, 10) + rate_limit_mb_per_member = optional(number, 3670016) + rate_period_seconds = optional(number, 900) + rate_units = optional(string, "mb") + }) + memory = object({ + io_above_percent = optional(number, 90) + io_enabled = optional(bool, false) + io_over_period = optional(string, "15m") + rate_increase_percent = optional(number, 10) + rate_limit_mb_per_member = optional(number, 114688) + rate_period_seconds = optional(number, 900) + rate_units = optional(string, "mb") + }) + }) + description = "Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/blob/main/solutions/standard/DA-types.md#autoscaling)" + default = null +} + +############################################################################## +## Secrets Manager Service Credentials +############################################################################## + +variable "existing_secrets_manager_instance_crn" { + type = string + default = null + description = "The CRN of existing secrets manager to use to create service credential secrets for Databases for RabbitMQ instance." +} + +variable "existing_secrets_manager_endpoint_type" { + type = string + description = "The endpoint type to use if `existing_secrets_manager_instance_crn` is specified. Possible values: public, private." + default = "private" + validation { + condition = contains(["public", "private"], var.existing_secrets_manager_endpoint_type) + error_message = "Only \"public\" and \"private\" are allowed values for 'existing_secrets_endpoint_type'." + } +} + +variable "service_credential_secrets" { + type = list(object({ + secret_group_name = string + secret_group_description = optional(string) + existing_secret_group = optional(bool) + service_credentials = list(object({ + secret_name = string + service_credentials_source_service_role = string + secret_labels = optional(list(string)) + secret_auto_rotation = optional(bool) + secret_auto_rotation_unit = optional(string) + secret_auto_rotation_interval = optional(number) + service_credentials_ttl = optional(string) + service_credential_secret_description = optional(string) + + })) + })) + default = [] + description = "Service credential secrets configuration for Databases for RabbitMQ. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/tree/main/solutions/standard/DA-types.md#service-credential-secrets)." + + validation { + condition = alltrue([ + for group in var.service_credential_secrets : alltrue([ + for credential in group.service_credentials : contains( + ["Writer", "Reader", "Manager", "None"], credential.service_credentials_source_service_role + ) + ]) + ]) + error_message = "service_credentials_source_service_role role must be one of 'Writer', 'Reader', 'Manager', and 'None'." + + } +} + +variable "skip_rabbitmq_sm_auth_policy" { + type = bool + description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Databases for RabbitMQ. If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the RabbitMQ instance created by the Deployable Architecture. Set to `true` to use an existing policy. The value of this is ignored if any value for 'existing_secrets_manager_instance_crn' is not passed." + default = false +} diff --git a/solutions/standard/version.tf b/solutions/standard/version.tf new file mode 100644 index 00000000..672c93d1 --- /dev/null +++ b/solutions/standard/version.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.3.0" + # Lock DA into an exact provider version - renovate automation will keep it updated + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "1.74.0" + } + time = { + source = "hashicorp/time" + version = "0.12.1" + } + random = { + source = "hashicorp/random" + version = "3.6.3" + } + } +} diff --git a/tests/other_test.go b/tests/other_test.go index 70094c39..66cf027b 100644 --- a/tests/other_test.go +++ b/tests/other_test.go @@ -71,3 +71,31 @@ func TestRunRestoredDBExample(t *testing.T) { assert.Nil(t, err, "This should not have errored") assert.NotNil(t, output, "Expected some output") } + +func TestRunFSCloudExample(t *testing.T) { + t.Parallel() + options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ + Testing: t, + TerraformDir: "examples/fscloud", + Prefix: "rabbitmq-cmp", + Region: "us-south", // For FSCloud locking into us-south since that is where the HPCS permanent instance is + /* + Comment out the 'ResourceGroup' input to force this test to create a unique resource group to ensure tests do + not clash. This is due to the fact that an auth policy may already exist in this resource group since we are + re-using a permanent HPCS instance. By using a new resource group, the auth policy will not already exist + since this module scopes auth policies by resource group. + */ + //ResourceGroup: resourceGroup, + TerraformVars: map[string]interface{}{ + "access_tags": permanentResources["accessTags"], + "existing_kms_instance_guid": permanentResources["hpcs_south"], + "kms_key_crn": permanentResources["hpcs_south_root_key_crn"], + "rabbitmq_version": "3.13", // Always lock this test into the latest supported RabbitMQ version + }, + CloudInfoService: sharedInfoSvc, + }) + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") +} diff --git a/tests/pr_test.go b/tests/pr_test.go index 4ba648d2..889d7b54 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -4,16 +4,25 @@ package test import ( "crypto/rand" "encoding/base64" + "encoding/json" + "fmt" + "io/fs" "log" "os" + "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/cloudinfo" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/common" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" + "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) +const standardSolutionTerraformDir = "solutions/standard" + // Use existing resource group const resourceGroup = "geretain-test-rabbitmq" @@ -40,32 +49,151 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestRunFSCloudExample(t *testing.T) { +type tarIncludePatterns struct { + excludeDirs []string + + includeFiletypes []string + + includeDirs []string +} + +func getTarIncludePatternsRecursively(dir string, dirsToExclude []string, fileTypesToInclude []string) ([]string, error) { + r := tarIncludePatterns{dirsToExclude, fileTypesToInclude, nil} + err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { + return walk(&r, path, entry, err) + }) + if err != nil { + fmt.Println("error") + return r.includeDirs, err + } + return r.includeDirs, nil +} + +func walk(r *tarIncludePatterns, s string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + for _, excludeDir := range r.excludeDirs { + if strings.Contains(s, excludeDir) { + return nil + } + } + if s == ".." { + r.includeDirs = append(r.includeDirs, "*.tf") + return nil + } + for _, includeFiletype := range r.includeFiletypes { + r.includeDirs = append(r.includeDirs, strings.ReplaceAll(s+"/*"+includeFiletype, "../", "")) + } + } + return nil +} + +func TestRunStandardSolutionSchematics(t *testing.T) { t.Parallel() - options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ - Testing: t, - TerraformDir: "examples/fscloud", - Prefix: "rabbitmq-cmp", - Region: "us-south", // For FSCloud locking into us-south since that is where the HPCS permanent instance is - /* - Comment out the 'ResourceGroup' input to force this test to create a unique resource group to ensure tests do - not clash. This is due to the fact that an auth policy may already exist in this resource group since we are - re-using a permanent HPCS instance. By using a new resource group, the auth policy will not already exist - since this module scopes auth policies by resource group. - */ - //ResourceGroup: resourceGroup, - TerraformVars: map[string]interface{}{ - "access_tags": permanentResources["accessTags"], - "existing_kms_instance_guid": permanentResources["hpcs_south"], - "kms_key_crn": permanentResources["hpcs_south_root_key_crn"], - "rabbitmq_version": "3.13", // Always lock this test into the latest supported RabbitMQ version - }, - CloudInfoService: sharedInfoSvc, + + excludeDirs := []string{ + ".terraform", + ".docs", + ".github", + ".git", + ".idea", + "common-dev-assets", + "examples", + "tests", + "reference-architectures", + } + includeFiletypes := []string{ + ".tf", + ".yaml", + ".py", + ".tpl", + ".sh", + } + + tarIncludePatterns, recurseErr := getTarIncludePatternsRecursively("..", excludeDirs, includeFiletypes) + + // if error producing tar patterns (very unexpected) fail test immediately + require.NoError(t, recurseErr, "Schematic Test had unexpected error traversing directory tree") + prefix := "mongodb-st-da" + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: tarIncludePatterns, + TemplateFolder: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: prefix, + ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, }) - output, err := options.RunTestConsistency() + serviceCredentialSecrets := []map[string]interface{}{ + { + "secret_group_name": fmt.Sprintf("%s-secret-group", options.Prefix), + "service_credentials": []map[string]string{ + { + "secret_name": fmt.Sprintf("%s-cred-reader", options.Prefix), + "service_credentials_source_service_role": "Reader", + }, + { + "secret_name": fmt.Sprintf("%s-cred-writer", options.Prefix), + "service_credentials_source_service_role": "Writer", + }, + }, + }, + } + + serviceCredentialNames := map[string]string{ + "admin": "Administrator", + "user1": "Viewer", + "user2": "Editor", + } + + serviceCredentialNamesJSON, err := json.Marshal(serviceCredentialNames) + if err != nil { + log.Fatalf("Error converting to JSON: %s", err) + } + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, + {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, + {Name: "kms_endpoint_type", Value: "private", DataType: "string"}, + {Name: "mongodb_version", Value: "6.0", DataType: "string"}, // Always lock this test into the latest supported MongoDB version + {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, + {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, + {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, + {Name: "service_credential_names", Value: string(serviceCredentialNamesJSON), DataType: "map(string)"}, + } + err = options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") +} + +func TestRunStandardUpgradeSolution(t *testing.T) { + t.Parallel() + + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: "mongodb-st-da-upg", + ResourceGroup: resourceGroup, + }) + + options.TerraformVars = map[string]interface{}{ + "access_tags": permanentResources["accessTags"], + "existing_kms_instance_crn": permanentResources["hpcs_south_crn"], + "kms_endpoint_type": "public", + "provider_visibility": "public", + "resource_group_name": options.Prefix, + } + + output, err := options.RunTestUpgrade() + if !options.UpgradeTestSkipped { + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") + } } func TestRunCompleteUpgradeExample(t *testing.T) { diff --git a/variables.tf b/variables.tf index ff10d6ae..9d607e48 100644 --- a/variables.tf +++ b/variables.tf @@ -180,21 +180,21 @@ variable "auto_scaling" { # Encryption ############################################################## -variable "kms_encryption_enabled" { +variable "use_ibm_owned_encryption_key" { type = bool - description = "Set this to true to control the encryption keys used to encrypt the data that you store in IBM Cloud® Databases. If set to false, the data is encrypted by using randomly generated keys. For more info on Key Protect integration, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect. For more info on HPCS integration, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs" - default = false + description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input." + default = true } variable "use_default_backup_encryption_key" { type = bool - description = "Set to true to use default ICD randomly generated keys." + description = "When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `kms_key_crn`, or in `backup_encryption_key_crn` if a value is passed. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data." default = false } variable "kms_key_crn" { type = string - description = "The root key CRN of a Key Management Services like Key Protect or Hyper Protect Crypto Services (HPCS) that you want to use for disk encryption. Only used if var.kms_encryption_enabled is set to true." + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." default = null validation { @@ -203,33 +203,37 @@ variable "kms_key_crn" { can(regex(".*kms.*", var.kms_key_crn)), can(regex(".*hs-crypto.*", var.kms_key_crn)), ]) - error_message = "Value must be the root key CRN from either the Key Protect or Hyper Protect Crypto Services (HPCS)" + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance." } } +variable "use_same_kms_key_for_backups" { + type = bool + description = "Set this to false if you wan't to use a different key that you own to encrypt backups. When set to false, a value is required for the `backup_encryption_key_crn` input. Alternatiely set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Applies only if `use_ibm_owned_encryption_key` is false. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." + default = true +} + variable "backup_encryption_key_crn" { type = string - description = "The CRN of a KMS (Key Protect or Hyper Protect Crypto Services) key to use for encrypting the disk that holds deployment backups. Only used if var.kms_encryption_enabled is set to true. There are limitation per region on the type of KMS service (Key Protect or Hyper Protect Crypto Services) and region for those services. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok and https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups" + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups)." default = null validation { - condition = var.backup_encryption_key_crn == null ? true : length(regexall("^crn:v1:bluemix:public:kms:(us-south|us-east|eu-de):a/[[:xdigit:]]{32}:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}:key:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$|^crn:v1:bluemix:public:hs-crypto:[a-z-]+:a/[[:xdigit:]]{32}:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}:key:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$", var.backup_encryption_key_crn)) > 0 - error_message = "Valid values for backup_encryption_key_crn is null, a Hyper Protect Crypto Services key CRN or a Key Protect key CRN from us-south, us-east or eu-de" + condition = anytrue([ + var.backup_encryption_key_crn == null, + can(regex(".*kms.*", var.backup_encryption_key_crn)), + can(regex(".*hs-crypto.*", var.backup_encryption_key_crn)), + ]) + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance in one of the supported backup regions." } } variable "skip_iam_authorization_policy" { type = bool - description = "Set to true to skip the creation of an IAM authorization policy that permits all RabbitMQ instances in the given resource group to read the encryption key from the Hyper Protect or Key Protect instance passed in var.existing_kms_instance_guid. If set to 'false', a value must be passed for var.existing_kms_instance_guid. No policy is created if var.kms_encryption_enabled is set to 'false'." + description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for RabbitMQ instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key that was provided in the `kms_key_crn` and `backup_encryption_key_crn` inputs. This policy is required in order to enable KMS encryption, so only skip creation if there is one already present in your account. No policy is created if `use_ibm_owned_encryption_key` is true." default = false } -variable "existing_kms_instance_guid" { - type = string - description = "The GUID of the Hyper Protect or Key Protect instance in which the key specified in var.kms_key_crn and var.backup_encryption_key_crn is coming from. Only required if var.kms_encryption_enabled is 'true', var.skip_iam_authorization_policy is 'false', and passing a value for var.kms_key_crn and/or var.backup_encryption_key_crn." - default = null -} - ############################################################## # Context-based restriction (CBR) ############################################################## From bbbc1e828be1d4de3deab779fa5d0b5efa076f8d Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Wed, 15 Jan 2025 22:45:47 +0000 Subject: [PATCH 2/7] refactor: kms logic + add da SKIP UPGRADE TESTS --- cra-config.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cra-config.yaml b/cra-config.yaml index cb738ed9..6e06880b 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -1,9 +1,12 @@ # More info about this file at https://github.com/terraform-ibm-modules/common-pipeline-assets/blob/main/.github/workflows/terraform-test-pipeline.md#cra-config-yaml version: "v1" CRA_TARGETS: - - CRA_TARGET: "examples/fscloud" + - CRA_TARGET: "solutions/standard" CRA_IGNORE_RULES_FILE: "cra-tf-validate-ignore-rules.json" PROFILE_ID: "fe96bd4d-9b37-40f2-b39f-a62760e326a3" # SCC profile ID (currently set to 'IBM Cloud Framework for Financial Services' '1.7.0' profile). CRA_ENVIRONMENT_VARIABLES: - TF_VAR_existing_kms_instance_guid: "e6dce284-e80f-46e1-a3c1-830f7adff7a9" - TF_VAR_kms_key_crn: "crn:v1:bluemix:public:hs-crypto:us-south:a/abac0df06b644a9cabc6e44f55b3880e:e6dce284-e80f-46e1-a3c1-830f7adff7a9:key:76170fae-4e0c-48c3-8ebe-326059ebb533" + TF_VAR_existing_kms_instance_crn: "crn:v1:bluemix:public:hs-crypto:us-south:a/abac0df06b644a9cabc6e44f55b3880e:e6dce284-e80f-46e1-a3c1-830f7adff7a9::" + TF_VAR_existing_kms_key_crn: "crn:v1:bluemix:public:hs-crypto:us-south:a/abac0df06b644a9cabc6e44f55b3880e:e6dce284-e80f-46e1-a3c1-830f7adff7a9:key:1368d2eb-3ed0-4a8b-b09c-2155895f01ea" + TF_VAR_use_existing_resource_group: true + TF_VAR_resource_group_name: "geretain-test-redis" + TF_VAR_provider_visibility: "public" \ No newline at end of file From 729eb9014c3eca663746f6a457e9887a05abd5b9 Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Wed, 15 Jan 2025 22:46:57 +0000 Subject: [PATCH 3/7] refactor: kms logic + add da SKIP UPGRADE TESTS --- tests/pr_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/pr_test.go b/tests/pr_test.go index 889d7b54..2f2655fe 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -116,7 +116,7 @@ func TestRunStandardSolutionSchematics(t *testing.T) { // if error producing tar patterns (very unexpected) fail test immediately require.NoError(t, recurseErr, "Schematic Test had unexpected error traversing directory tree") - prefix := "mongodb-st-da" + prefix := "rabbitmq-st-da" options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ Testing: t, TarIncludePatterns: tarIncludePatterns, @@ -160,7 +160,7 @@ func TestRunStandardSolutionSchematics(t *testing.T) { {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, {Name: "kms_endpoint_type", Value: "private", DataType: "string"}, - {Name: "mongodb_version", Value: "6.0", DataType: "string"}, // Always lock this test into the latest supported MongoDB version + {Name: "rabbitmq_version", Value: "6.0", DataType: "string"}, // Always lock this test into the latest supported RabbitMQ version {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, @@ -177,7 +177,7 @@ func TestRunStandardUpgradeSolution(t *testing.T) { Testing: t, TerraformDir: standardSolutionTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: "mongodb-st-da-upg", + Prefix: "rabbitmq-st-da-upg", ResourceGroup: resourceGroup, }) From fab75f796b5be6fe9cad674fbd3c5b6dd790df31 Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Wed, 15 Jan 2025 22:53:02 +0000 Subject: [PATCH 4/7] refactor: kms logic + add da --- cra-config.yaml | 2 +- solutions/standard/DA-types.md | 2 +- solutions/standard/main.tf | 4 ++-- solutions/standard/variables.tf | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cra-config.yaml b/cra-config.yaml index 6e06880b..fbae9039 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -9,4 +9,4 @@ CRA_TARGETS: TF_VAR_existing_kms_key_crn: "crn:v1:bluemix:public:hs-crypto:us-south:a/abac0df06b644a9cabc6e44f55b3880e:e6dce284-e80f-46e1-a3c1-830f7adff7a9:key:1368d2eb-3ed0-4a8b-b09c-2155895f01ea" TF_VAR_use_existing_resource_group: true TF_VAR_resource_group_name: "geretain-test-redis" - TF_VAR_provider_visibility: "public" \ No newline at end of file + TF_VAR_provider_visibility: "public" diff --git a/solutions/standard/DA-types.md b/solutions/standard/DA-types.md index 6536d19f..1b73b9e2 100644 --- a/solutions/standard/DA-types.md +++ b/solutions/standard/DA-types.md @@ -200,4 +200,4 @@ The following example shows values for both disk and memory for the `auto_scalin "rate_units": "mb" } } -``` \ No newline at end of file +``` diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf index 9817ed4e..614d7447 100644 --- a/solutions/standard/main.tf +++ b/solutions/standard/main.tf @@ -28,7 +28,7 @@ locals { ####################################################################################################################### locals { - create_new_kms_key = !var.use_ibm_owned_encryption_key && var.existing_kms_key_crn == null ? true : false # no need to create any KMS resources if passing an existing key, or using IBM owned keys + create_new_kms_key = !var.use_ibm_owned_encryption_key && var.existing_kms_key_crn == null ? true : false # no need to create any KMS resources if passing an existing key, or using IBM owned keys rabbitmq_key_name = var.prefix != null ? "${var.prefix}-${var.key_name}" : var.key_name rabbitmq_key_ring_name = var.prefix != null ? "${var.prefix}-${var.key_ring_name}" : var.key_ring_name } @@ -250,7 +250,7 @@ module "rabbitmq" { resource_group_id = module.resource_group.resource_group_id instance_name = var.prefix != null ? "${var.prefix}-${var.name}" : var.name region = var.region - rabbitmq_version = var.rabbitmq_version + rabbitmq_version = var.rabbitmq_version skip_iam_authorization_policy = var.skip_rabbitmq_kms_auth_policy use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key kms_key_crn = local.kms_key_crn diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf index f9e15379..25d964b1 100644 --- a/solutions/standard/variables.tf +++ b/solutions/standard/variables.tf @@ -98,7 +98,7 @@ variable "users" { })) default = [] sensitive = true -description = "A list of users that you want to create on the database. Users block is supported by RabbitMQ version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the RabbitMQ instance. This blocks creates native RabbitMQ database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/blob/main/solutions/standard/DA-types.md#users)" + description = "A list of users that you want to create on the database. Users block is supported by RabbitMQ version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the RabbitMQ instance. This blocks creates native RabbitMQ database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-rabbitmq/blob/main/solutions/standard/DA-types.md#users)" } variable "tags" { From 90b780bcf67bd55e65252dedd909f6034a98a31a Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Thu, 16 Jan 2025 00:31:45 +0000 Subject: [PATCH 5/7] refactor: kms logic + add da --- solutions/standard/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf index 25d964b1..37130c0c 100644 --- a/solutions/standard/variables.tf +++ b/solutions/standard/variables.tf @@ -55,7 +55,7 @@ variable "members" { variable "member_memory_mb" { type = number description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)" - default = 4096 + default = 8192 } variable "member_cpu_count" { @@ -67,7 +67,7 @@ variable "member_cpu_count" { variable "member_disk_mb" { type = number description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/messages-for-rabbitmq?topic=messages-for-rabbitmq-resources-scaling)." - default = 10240 + default = 1024 } variable "member_host_flavor" { From 565c8681a032332350dc9a925160e2218475b680 Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Thu, 16 Jan 2025 00:43:36 +0000 Subject: [PATCH 6/7] refactor: kms logic + add da --- tests/pr_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pr_test.go b/tests/pr_test.go index 2f2655fe..02a01d4e 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -160,7 +160,7 @@ func TestRunStandardSolutionSchematics(t *testing.T) { {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, {Name: "kms_endpoint_type", Value: "private", DataType: "string"}, - {Name: "rabbitmq_version", Value: "6.0", DataType: "string"}, // Always lock this test into the latest supported RabbitMQ version + {Name: "rabbitmq_version", Value: "3.13", DataType: "string"}, // Always lock this test into the latest supported RabbitMQ version {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, From fb2db8829d26da0e46694f6be75804d3e3b7d3cf Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Thu, 16 Jan 2025 01:00:25 +0000 Subject: [PATCH 7/7] refactor: kms logic + add da --- main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.tf b/main.tf index 7866dcf2..100f7149 100644 --- a/main.tf +++ b/main.tf @@ -66,7 +66,7 @@ locals { # Create IAM Access policy to allow RabbitMQ to access KMS for the encryption key resource "ibm_iam_authorization_policy" "kms_policy" { count = local.create_kms_auth_policy - source_service_name = "databases-for-rabbitmq" + source_service_name = "messages-for-rabbitmq" source_resource_group_id = var.resource_group_id roles = ["Reader"] description = "Allow all RabbitMQ instances in the resource group ${var.resource_group_id} to read the ${local.kms_service} key ${local.kms_key_id} from the instance GUID ${local.kms_key_instance_guid}" @@ -112,7 +112,7 @@ resource "time_sleep" "wait_for_authorization_policy" { resource "ibm_iam_authorization_policy" "backup_kms_policy" { count = local.create_backup_kms_auth_policy - source_service_name = "databases-for-rabbitmq" + source_service_name = "messages-for-rabbitmq" source_resource_group_id = var.resource_group_id roles = ["Reader"] description = "Allow all RabbitMQ instances in the Resource Group ${var.resource_group_id} to read the ${local.backup_kms_service} key ${local.backup_kms_key_id} from the instance GUID ${local.backup_kms_key_instance_guid}"