diff --git a/README.md b/README.md index df0cd28c..593c91b5 100644 --- a/README.md +++ b/README.md @@ -92,17 +92,21 @@ To attach access management tags to resources in this module, you need the follo | 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) | terraform-ibm-modules/cbr/ibm//modules/cbr-rule-module | 1.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.mysql_db](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.mysql_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 @@ -113,12 +117,9 @@ To attach access management tags to resources in this module, you need the follo | [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://ibm.biz/autoscaling-considerations 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 | -| [configuration](#input\_configuration) | Database configuration |
object({
max_connections = optional(number)
max_prepared_transactions = optional(number)
deadlock_timeout = optional(number)
effective_io_concurrency = optional(number)
max_replication_slots = optional(number)
max_wal_senders = optional(number)
shared_buffers = optional(number)
synchronous_commit = optional(string)
wal_level = optional(string)
archive_timeout = optional(number)
log_min_duration_statement = optional(number)
})
| `null` | no | -| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect Crypto Services or Key Protect instance in which the key specified in var.kms\_key\_crn and var.backup\_encryption\_key\_crn is coming from. Required only if var.kms\_encryption\_enabled is set to true, var.skip\_iam\_authorization\_policy is set to false, and you pass a value for var.kms\_key\_crn, var.backup\_encryption\_key\_crn, or both. | `string` | `null` | no | -| [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\_cpu\_count](#input\_member\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `0` | no | | [member\_disk\_mb](#input\_member\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `10240` | 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 | @@ -135,7 +136,9 @@ To attach access management tags to resources in this module, you need the follo | [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 | | [service\_endpoints](#input\_service\_endpoints) | Specify whether you want to enable the public, private, or both service endpoints. Supported values are 'public', 'private', or 'public-and-private'. | `string` | `"private"` | 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 MySQL database instances in the resource group to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing\_kms\_instance\_guid variable. In addition, no policy is created if var.kms\_encryption\_enabled is set to false. | `bool` | `false` | 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 MySQL instance. These blocks creates native MySQL database users, more info on that can be found here https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-user-management |
list(object({
name = string
password = string # pragma: allowlist secret
type = optional(string)
role = optional(string)
}))
| `[]` | no | ### Outputs diff --git a/cra-config.yaml b/cra-config.yaml index 6aeb714e..d86b3245 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -1,10 +1,11 @@ # 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_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_at_instance_crn: "crn:v1:bluemix:public:logdnaat:eu-de:a/abac0df06b644a9cabc6e44f55b3880e:b1ef3365-dfbf-4d8f-8ac8-75f4f84d6f4a::" - 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" + - CRA_TARGET: "solutions/standard" # Target directory for CRA scan. If not provided, the CRA Scan will not be run. + CRA_IGNORE_RULES_FILE: "cra-tf-validate-ignore-rules.json" # CRA Ignore file to use. If not provided, it checks the repo root directory for `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: # An optional map of environment variables for CRA, where the key is the variable name and value is the value. Useful for providing TF_VARs. + TF_VAR_existing_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_use_existing_resource_group: true + TF_VAR_resource_group_name: "geretain-test-mysql" + TF_VAR_provider_visibility: "public" diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 7f3192f2..d2db871f 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}-mysql" + backups_key_name = "${var.prefix}-mysql-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-mysql" keys = [ { - key_name = "${var.prefix}-my" + key_name = local.data_key_name + force_delete = true + }, + { + key_name = local.backups_key_name force_delete = true } ] @@ -93,23 +102,27 @@ module "cbr_zone" { ############################################################################## module "mysql_db" { - source = "../../" - resource_group_id = module.resource_group.resource_group_id - name = "${var.prefix}-mysql" - region = var.region - mysql_version = var.mysql_version - admin_pass = var.admin_pass - users = var.users - kms_encryption_enabled = true - kms_key_crn = module.key_protect_all_inclusive.keys["icd-mysql.${var.prefix}-my"].crn - existing_kms_instance_guid = module.key_protect_all_inclusive.kms_guid - resource_tags = var.resource_tags - service_credential_names = var.service_credential_names - access_tags = var.access_tags - member_host_flavor = "multitenant" - configuration = { - max_connections = 250 + source = "../../" + resource_group_id = module.resource_group.resource_group_id + name = "${var.prefix}-mysql" + region = var.region + mysql_version = var.mysql_version + admin_pass = var.admin_pass + users = var.users + # 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.${var.prefix}-mysql"].crn + backup_encryption_key_crn = module.key_protect_all_inclusive.keys["icd.${local.data_key_name}"].crn + service_credential_names = { + "mysql_admin" : "Administrator", + "mysql_operator" : "Operator", + "mysql_viewer" : "Viewer", + "mysql_editor" : "Editor", } + resource_tags = var.resource_tags + access_tags = var.access_tags + member_host_flavor = "multitenant" cbr_rules = [ { description = "${var.prefix}-mysql access only from vpc" diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index d5bdacda..5f2f2559 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -58,14 +58,3 @@ variable "users" { sensitive = true 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 = { - "mysql_admin" : "Administrator", - "mysql_operator" : "Operator", - "mysql_viewer" : "Viewer", - "mysql_editor" : "Editor", - } -} diff --git a/examples/fscloud/main.tf b/examples/fscloud/main.tf index 4ebdaacb..1b6720dc 100644 --- a/examples/fscloud/main.tf +++ b/examples/fscloud/main.tf @@ -54,20 +54,32 @@ module "cbr_zone" { ############################################################################## module "mysql_db" { - source = "../../modules/fscloud" - resource_group_id = module.resource_group.resource_group_id - name = "${var.prefix}-mysql" - region = var.region - mysql_version = var.mysql_version - kms_key_crn = var.kms_key_crn - existing_kms_instance_guid = var.existing_kms_instance_guid - resource_tags = var.resource_tags - service_credential_names = var.service_credential_names - 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}-mysql" + region = var.region + mysql_version = var.mysql_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 = { + "mysql_admin" : "Administrator", + "mysql_operator" : "Operator", + "mysql_viewer" : "Viewer", + "mysql_editor" : "Editor", + } + auto_scaling = { + disk = { + capacity_enabled : true, + io_enabled : true + } + memory = { + io_enabled : true, + } + } + member_host_flavor = "b3c.4x16.encrypted" + resource_tags = var.resource_tags + access_tags = var.access_tags cbr_rules = [ { description = "${var.prefix}-mysql access only from vpc" diff --git a/examples/fscloud/variables.tf b/examples/fscloud/variables.tf index 82b12cae..8b448cf1 100644 --- a/examples/fscloud/variables.tf +++ b/examples/fscloud/variables.tf @@ -40,27 +40,11 @@ variable "mysql_version" { 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 MySQL database." } -variable "service_credential_names" { - description = "Map of name, role for service credentials that you want to create for the database" - type = map(string) - default = { - "mysql_admin" : "Administrator", - "mysql_operator" : "Operator", - "mysql_viewer" : "Viewer", - "mysql_editor" : "Editor", - } -} - 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." @@ -73,38 +57,3 @@ variable "backup_encryption_key_crn" { default = null # Validation happens in the root module } - -variable "auto_scaling" { - type = object({ - disk = object({ - capacity_enabled = optional(bool) - free_space_less_than_percent = optional(number) - io_above_percent = optional(number) - io_enabled = optional(bool) - io_over_period = optional(string) - rate_increase_percent = optional(number) - rate_limit_mb_per_member = optional(number) - rate_period_seconds = optional(number) - rate_units = optional(string) - }) - memory = object({ - io_above_percent = optional(number) - io_enabled = optional(bool) - io_over_period = optional(string) - rate_increase_percent = optional(number) - rate_limit_mb_per_member = optional(number) - rate_period_seconds = optional(number) - rate_units = optional(string) - }) - }) - 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://ibm.biz/autoscaling-considerations in the IBM Cloud Docs." - default = { - disk = { - capacity_enabled : true, - io_enabled : true - } - memory = { - io_enabled : true, - } - } -} diff --git a/main.tf b/main.tf index d1d2be7b..5aab0810 100644 --- a/main.tf +++ b/main.tf @@ -5,19 +5,19 @@ 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_pitr_vars = (var.pitr_id != null && var.pitr_time == null) || (var.pitr_time != null && var.pitr_id == null) ? tobool("To use Point-In-Time Recovery (PITR), values for both var.pitr_id and var.pitr_time need to be set. Otherwise, unset both of these.") : 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_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_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_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 - # 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. - # If no value passed for 'backup_encryption_key_crn' use the value of 'kms_key_crn'. If this is a HPCS key (which is not currently supported for backup encryption), default to 'null' meaning encryption is done using randomly generated keys - # 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] @@ -27,49 +27,159 @@ locals { # Determine if restore, from backup or point in time recovery recovery_mode = var.backup_crn != null || var.pitr_id != null +} - # 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" : null - ) - ) : null +######################################################################################################################## +# 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 +} + +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 Authorization Policies to allow MySQL 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 = "databases-for-mysql" - 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 ICD MySQL 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-mysql" + source_resource_group_id = var.resource_group_id + roles = ["Reader"] + description = "Allow all MySQL 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-mysql" + source_resource_group_id = var.resource_group_id + roles = ["Reader"] + description = "Allow all MySQL 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" +} + +######################################################################################################################## +# MySQL instance +######################################################################################################################## + # Create MySQL database resource "ibm_database" "mysql_db" { - depends_on = [time_sleep.wait_for_authorization_policy] - resource_group_id = var.resource_group_id - name = var.name - service = "databases-for-mysql" - location = var.region - plan = "standard" # Only standard plan is available for mysql - backup_id = var.backup_crn - remote_leader_id = var.remote_leader_crn - version = var.mysql_version - tags = var.resource_tags - adminpassword = var.admin_pass - service_endpoints = var.service_endpoints - # remove elements with null values: see https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/issues/273 - configuration = var.configuration != null ? jsonencode({ for k, v in var.configuration : k => v if v != null }) : null + depends_on = [time_sleep.wait_for_authorization_policy] + resource_group_id = var.resource_group_id + name = var.name + service = "databases-for-mysql" + location = var.region + plan = "standard" # Only standard plan is available for mysql + backup_id = var.backup_crn + remote_leader_id = var.remote_leader_crn + version = var.mysql_version + tags = var.resource_tags + adminpassword = var.admin_pass + service_endpoints = var.service_endpoints key_protect_key = var.kms_key_crn backup_encryption_key_crn = local.backup_encryption_key_crn point_in_time_recovery_deployment_id = var.pitr_id diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index edb10590..a43ffbe3 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -34,23 +34,24 @@ 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://ibm.biz/autoscaling-considerations 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 | -| [configuration](#input\_configuration) | Database configuration |
object({
max_connections = optional(number)
max_prepared_transactions = optional(number)
deadlock_timeout = optional(number)
effective_io_concurrency = optional(number)
max_replication_slots = optional(number)
max_wal_senders = optional(number)
shared_buffers = optional(number)
synchronous_commit = optional(string)
wal_level = optional(string)
archive_timeout = optional(number)
log_min_duration_statement = optional(number)
})
| `null` | no | -| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect Crypto Services instance. | `string` | n/a | yes | -| [kms\_key\_crn](#input\_kms\_key\_crn) | The root key CRN of the Hyper Protect Crypto Services (HPCS) to use for disk encryption. | `string` | n/a | yes | +| [instance\_name](#input\_instance\_name) | The name to give the MySQL instance. | `string` | n/a | yes | +| [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\_cpu\_count](#input\_member\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `3` | no | | [member\_disk\_mb](#input\_member\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `10240` | 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 | | [member\_memory\_mb](#input\_member\_memory\_mb) | Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `4096` | no | | [members](#input\_members) | Allocated number of members. Members can be scaled up but not down. | `number` | `3` | no | | [mysql\_version](#input\_mysql\_version) | Version of the MySQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | -| [name](#input\_name) | The name to give the MySQL instance. | `string` | n/a | yes | | [region](#input\_region) | The region where you want to deploy your instance. Must be the same region as the Hyper Protect Crypto Services instance. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the MySQL instance will be created. | `string` | n/a | yes | | [resource\_tags](#input\_resource\_tags) | Optional list of tags to be added to the MySQL instance. | `list(string)` | `[]` | no | | [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 MySQL database 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 MySQL 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 | +| [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 MySQL instance. This blocks creates native MySQL database users, more info on that can be found here https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-user-management |
list(object({
name = string
password = string # pragma: allowlist secret
type = optional(string)
role = optional(string)
}))
| `[]` | no | ### Outputs @@ -58,11 +59,14 @@ No resources. | Name | Description | |------|-------------| | [adminuser](#output\_adminuser) | Database admin user name | +| [cbr\_rule\_ids](#output\_cbr\_rule\_ids) | CBR rule ids created to restrict MySQL | | [certificate\_base64](#output\_certificate\_base64) | Database connection certificate | | [crn](#output\_crn) | MySQL instance crn | | [guid](#output\_guid) | MySQL instance guid | | [hostname](#output\_hostname) | Database connection hostname | | [id](#output\_id) | MySQL instance id | | [port](#output\_port) | Database connection port | +| [service\_credentials\_json](#output\_service\_credentials\_json) | Service credentials json map | +| [service\_credentials\_object](#output\_service\_credentials\_object) | Service credentials object | | [version](#output\_version) | MySQL instance version | diff --git a/modules/fscloud/main.tf b/modules/fscloud/main.tf index 02a2fcc8..ae672472 100644 --- a/modules/fscloud/main.tf +++ b/modules/fscloud/main.tf @@ -1,27 +1,27 @@ module "mysql_db" { - source = "../../" - resource_group_id = var.resource_group_id - name = var.name - region = var.region - skip_iam_authorization_policy = var.skip_iam_authorization_policy - service_endpoints = "private" - mysql_version = var.mysql_version - kms_encryption_enabled = true - existing_kms_instance_guid = var.existing_kms_instance_guid - kms_key_crn = var.kms_key_crn - backup_encryption_key_crn = var.backup_encryption_key_crn - resource_tags = var.resource_tags - access_tags = var.access_tags - cbr_rules = var.cbr_rules - configuration = var.configuration - member_memory_mb = var.member_memory_mb - member_disk_mb = var.member_disk_mb - member_cpu_count = var.member_cpu_count - member_host_flavor = var.member_host_flavor - members = var.members - admin_pass = var.admin_pass - users = var.users - service_credential_names = var.service_credential_names - auto_scaling = var.auto_scaling - backup_crn = var.backup_crn + source = "../../" + resource_group_id = var.resource_group_id + name = var.instance_name + region = var.region + skip_iam_authorization_policy = var.skip_iam_authorization_policy + service_endpoints = "private" + mysql_version = var.mysql_version + 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 + resource_tags = var.resource_tags + access_tags = var.access_tags + cbr_rules = var.cbr_rules + member_memory_mb = var.member_memory_mb + member_disk_mb = var.member_disk_mb + member_cpu_count = var.member_cpu_count + member_host_flavor = var.member_host_flavor + members = var.members + admin_pass = var.admin_pass + users = var.users + service_credential_names = var.service_credential_names + auto_scaling = var.auto_scaling } diff --git a/modules/fscloud/outputs.tf b/modules/fscloud/outputs.tf index a7040837..af5fb9be 100644 --- a/modules/fscloud/outputs.tf +++ b/modules/fscloud/outputs.tf @@ -42,3 +42,20 @@ output "certificate_base64" { value = module.mysql_db.certificate_base64 sensitive = true } + +output "cbr_rule_ids" { + description = "CBR rule ids created to restrict MySQL" + value = module.mysql_db.cbr_rule_ids +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.mysql_db.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.mysql_db.service_credentials_object + sensitive = true +} diff --git a/modules/fscloud/variables.tf b/modules/fscloud/variables.tf index 6cce2152..4af600de 100644 --- a/modules/fscloud/variables.tf +++ b/modules/fscloud/variables.tf @@ -7,7 +7,7 @@ variable "resource_group_id" { description = "The resource group ID where the MySQL instance will be created." } -variable "name" { +variable "instance_name" { type = string description = "The name to give the MySQL instance." } @@ -95,24 +95,6 @@ variable "access_tags" { default = [] } -variable "configuration" { - type = object({ - max_connections = optional(number) - max_prepared_transactions = optional(number) - deadlock_timeout = optional(number) - effective_io_concurrency = optional(number) - max_replication_slots = optional(number) - max_wal_senders = optional(number) - shared_buffers = optional(number) - synchronous_commit = optional(string) - wal_level = optional(string) - archive_timeout = optional(number) - log_min_duration_statement = optional(number) - }) - description = "Database configuration" - default = null -} - ############################################################## # Auto Scaling ############################################################## @@ -148,26 +130,40 @@ 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 the Hyper Protect Crypto Services (HPCS) to use for disk encryption." + 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 +} + +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 } -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 MySQL database 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 MySQL 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-mysql.svg b/reference-architecture/deployable-architecture-mysql.svg new file mode 100644 index 00000000..3c9b426c --- /dev/null +++ b/reference-architecture/deployable-architecture-mysql.svg @@ -0,0 +1,4 @@ + + + +
IBM Cloud
IBM Cloud
KMS Encryption
KMS Encryption
Region
Region
Resource Group
Resource Group
IBM Cloud MySQL Instance
IBM Cloud MySQL Instance
MSQL
MSQL
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..c29d65c5 --- /dev/null +++ b/solutions/standard/DA-types.md @@ -0,0 +1,204 @@ +# Configuring complex inputs in Databases for MySQL + +Several optional input variables in the IBM Cloud [Databases for MySQL 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`) +- [Configuration](#configuaration) (`configuration`) + +## 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 + { + "mysql_admin" : "Administrator", + "mysql_reader" : "Operator", + "mysql_viewer" : "Viewer", + "mysql_editor" : "Editor" + } +``` + +## Service credential secrets + +When you add an IBM Database for MySQL 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 MySQL 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/databases-for-mysql?topic=databases-for-mysql-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/databases-for-mysql?topic=databases-for-mysql-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" + } +} +``` diff --git a/solutions/standard/README.md b/solutions/standard/README.md new file mode 100644 index 00000000..99cec06f --- /dev/null +++ b/solutions/standard/README.md @@ -0,0 +1,13 @@ + # IBM Cloud Databases for MySQL + +This architecture creates an instance of IBM Cloud Databases for MySQL 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 MySQL instance with KMS encryption. +- Autoscaling rules for the database instance, if provided. +- Service credential secrets and store them in secret manager. + +![fscloud-mysql](../../reference-architecture/deployable-architecture-mysql.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..d884b11c --- /dev/null +++ b/solutions/standard/catalogValidationValues.json.template @@ -0,0 +1,8 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY, + "region": "us-south", + "tags": $TAGS, + "instance_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..dd9934ea --- /dev/null +++ b/solutions/standard/main.tf @@ -0,0 +1,333 @@ +####################################################################################################################### +# 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 + mysql_key_name = var.prefix != null ? "${var.prefix}-${var.key_name}" : var.key_name + mysql_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.mysql_key_ring_name + existing_key_ring = false + keys = [ + { + key_name = local.mysql_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 MySQL) +# - if no value passed for 'ibmcloud_kms_api_key', the auth policy is created by the MySQL 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_mysql_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key + create_cross_account_backup_kms_auth_policy = !var.skip_mysql_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.mysql_key_ring_name, local.mysql_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.mysql_key_ring_name, local.mysql_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 = "databases-for-mysql" + source_resource_group_id = module.resource_group.resource_group_id + roles = ["Reader"] + description = "Allow all MySQL 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 = "databases-for-mysql" + source_resource_group_id = module.resource_group.resource_group_id + roles = ["Reader"] + description = "Allow all MySQL 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" +} + +####################################################################################################################### +# MySQL 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 + admin_pass = var.admin_pass == null ? (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) : var.admin_pass +} + +####################################################################################################################### +# MySQL +####################################################################################################################### + +# Create new instance +module "mysql" { + 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.instance_name}" : var.instance_name + region = var.region + mysql_version = var.mysql_version + skip_iam_authorization_policy = var.skip_mysql_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 + resource_tags = var.tags + admin_pass = local.admin_pass + users = var.users + members = var.members + member_host_flavor = var.member_host_flavor + member_memory_mb = var.member_memory_mb + member_disk_mb = var.member_disk_mb + member_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_mysql_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 MySQL) +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 = "databases-for-mysql" + target_resource_instance_id = module.mysql.guid + roles = ["Key Manager"] + description = "Allow Secrets Manager with instance id ${local.existing_secrets_manager_instance_guid} to manage key for the databases-for-mysql instance" +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_mysql_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.mysql.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_mysql_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..abc65031 --- /dev/null +++ b/solutions/standard/outputs.tf @@ -0,0 +1,66 @@ +############################################################################## +# Outputs +############################################################################## + +output "id" { + description = "MySQL instance id" + value = module.mysql.id +} + +output "version" { + description = "MySQL instance version" + value = module.mysql.version +} + +output "guid" { + description = "MySQL instance guid" + value = module.mysql.guid +} + +output "crn" { + description = "MySQL instance crn" + value = module.mysql.crn +} + +output "cbr_rule_ids" { + description = "CBR rule ids created to restrict MySQL" + value = module.mysql.cbr_rule_ids +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.mysql.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.mysql.service_credentials_object + sensitive = true +} + +output "adminuser" { + description = "Database admin user name" + value = module.mysql.adminuser +} + +output "hostname" { + description = "Database connection hostname" + value = module.mysql.hostname +} + +output "port" { + description = "Database connection port" + value = module.mysql.port +} + +output "certificate_base64" { + description = "Database connection certificate" + value = module.mysql.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..fd98d3e3 --- /dev/null +++ b/solutions/standard/provider.tf @@ -0,0 +1,6 @@ +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..8c42c7e3 --- /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 MySQL 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 "instance_name" { + type = string + description = "The name of the Databases for MySQL instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "mysql" +} + +variable "region" { + description = "The region where you want to deploy your instance." + type = string + default = "us-south" +} + +variable "mysql_version" { + description = "The version of the Databases for MySQL instance. If no value is specified, the current preferred version of Databases for MySQL 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/databases-for-mysql?topic=databases-for-mysql-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/databases-for-mysql?topic=databases-for-mysql-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/databases-for-mysql?topic=databases-for-mysql-resources-scaling)." + default = 3 +} + +variable "member_disk_mb" { + type = number + description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-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-mysql/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 MySQL 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 MySQL instance. This blocks creates native MySQL database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/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 MySQL instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the Databases for MySQL 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_mysql_kms_auth_policy" { + type = bool + description = "Whether to create an IAM authorization policy that permits all Databases for MySQL 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 MySQL instance. Leave this input empty if the same account owns both instances." + sensitive = true + default = null +} + +variable "key_ring_name" { + type = string + default = "mysql-key-ring" + description = "The name for the key ring created for the Databases for MySQL 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 = "mysql-key" + description = "The name for the key created for the Databases for MySQL 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-mysql/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 MySQL 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 MySQL. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/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_mysql_sm_auth_policy" { + type = bool + default = false + description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Databases for MySQL. If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the MySQL 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." +} 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 4a4945cd..fca6197d 100644 --- a/tests/other_test.go +++ b/tests/other_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" ) @@ -98,6 +99,45 @@ func testPlanICDVersions(t *testing.T, version string) { 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: "mysql-fscloud", + 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 tests 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"], + "mysql_version": "8.0", // Always lock this test into the latest supported mysql version + }, + ImplicitDestroy: []string{ + "module.mysql_db.module.mysql_db.time_sleep.wait_for_authorization_policy", + }, + CloudInfoService: sharedInfoSvc, + }) + options.SkipTestTearDown = true + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") + + // check if outputs exist + outputs := terraform.OutputAll(options.Testing, options.TerraformOptions) + expectedOutputs := []string{"port", "hostname"} + _, outputErr := testhelper.ValidateTerraformOutputs(outputs, expectedOutputs...) + assert.NoErrorf(t, outputErr, "Some outputs not found or nil") + options.TestTearDown() +} + func TestPlanICDVersions(t *testing.T) { t.Parallel() diff --git a/tests/pr_test.go b/tests/pr_test.go index 28dfdbc6..a2989119 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -4,18 +4,25 @@ package test import ( "crypto/rand" "encoding/base64" + "encoding/json" + "fmt" + "io/fs" "log" "os" + "path/filepath" + "strings" "testing" - "github.com/gruntwork-io/terratest/modules/terraform" "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-mysql" @@ -42,43 +49,159 @@ 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: "mysql-fscloud", - 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 tests 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"], - "mysql_version": "8.0", // Always lock this test into the latest supported mysql version - }, - ImplicitDestroy: []string{ - "module.mysql_db.module.mysql_db.time_sleep.wait_for_authorization_policy", - }, - 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 := "mysql-st-da" + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: tarIncludePatterns, + TemplateFolder: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: prefix, + ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, }) - options.SkipTestTearDown = true - 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: "mysql_version", Value: "8.0", DataType: "string"}, // Always lock this test into the latest supported MySQL 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") - - // check if outputs exist - outputs := terraform.OutputAll(options.Testing, options.TerraformOptions) - expectedOutputs := []string{"port", "hostname"} - _, outputErr := testhelper.ValidateTerraformOutputs(outputs, expectedOutputs...) - assert.NoErrorf(t, outputErr, "Some outputs not found or nil") - options.TestTearDown() +} + +func TestRunStandardUpgradeSolution(t *testing.T) { + t.Parallel() + + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: "mysql-st-da-upg", + ResourceGroup: resourceGroup, + }) + + // Generate a 15 char long random string for the admin_pass + randomBytes := make([]byte, 13) + _, randErr := rand.Read(randomBytes) + require.Nil(t, randErr) // do not proceed if we can't gen a random password + + randomPass := "A1" + base64.URLEncoding.EncodeToString(randomBytes)[:13] + + 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, + "admin_pass": randomPass, + } + + output, err := options.RunTestUpgrade() + if !options.UpgradeTestSkipped { + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") + } } func TestRunUpgradeCompleteExample(t *testing.T) { diff --git a/variables.tf b/variables.tf index 4616e1a1..47664934 100644 --- a/variables.tf +++ b/variables.tf @@ -137,24 +137,6 @@ variable "access_tags" { } } -variable "configuration" { - type = object({ - max_connections = optional(number) - max_prepared_transactions = optional(number) - deadlock_timeout = optional(number) - effective_io_concurrency = optional(number) - max_replication_slots = optional(number) - max_wal_senders = optional(number) - shared_buffers = optional(number) - synchronous_commit = optional(string) - wal_level = optional(string) - archive_timeout = optional(number) - log_min_duration_statement = optional(number) - }) - description = "Database configuration" - default = null -} - ############################################################## # Auto Scaling ############################################################## @@ -190,21 +172,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 { @@ -213,18 +195,28 @@ 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." } } @@ -234,12 +226,6 @@ variable "skip_iam_authorization_policy" { default = false } -variable "existing_kms_instance_guid" { - type = string - description = "The GUID of the Hyper Protect Crypto Services or Key Protect instance in which the key specified in var.kms_key_crn and var.backup_encryption_key_crn is coming from. Required only if var.kms_encryption_enabled is set to true, var.skip_iam_authorization_policy is set to false, and you pass a value for var.kms_key_crn, var.backup_encryption_key_crn, or both." - default = null -} - ############################################################## # Context-based restriction (CBR) ##############################################################