diff --git a/README.md b/README.md index be729f5b..9aea03e1 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ You need the following permissions to run this module. | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [terraform](#requirement\_terraform) | >= 1.9.0 | | [ibm](#requirement\_ibm) | >= 1.71.0, <2.0.0 | | [time](#requirement\_time) | >= 0.9.1 | diff --git a/examples/basic/version.tf b/examples/basic/version.tf index 1a06bdf3..9dc10a91 100644 --- a/examples/basic/version.tf +++ b/examples/basic/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { ibm = { source = "IBM-Cloud/ibm" diff --git a/examples/complete/version.tf b/examples/complete/version.tf index 5e41cfef..e93e0af0 100644 --- a/examples/complete/version.tf +++ b/examples/complete/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { # Use latest version of provider in non-basic examples to verify latest version works with module ibm = { diff --git a/examples/fscloud/version.tf b/examples/fscloud/version.tf index e486069e..31b01308 100644 --- a/examples/fscloud/version.tf +++ b/examples/fscloud/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { # Use latest version of provider in non-basic examples to verify latest version works with module ibm = { diff --git a/ibm_catalog.json b/ibm_catalog.json index 6481306b..f09643e8 100644 --- a/ibm_catalog.json +++ b/ibm_catalog.json @@ -150,6 +150,18 @@ }, { "key": "service_credential_names" + }, + { + "key": "existing_secrets_manager_endpoint_type" + }, + { + "key": "existing_secrets_manager_instance_crn" + }, + { + "key": "service_credential_secrets" + }, + { + "key": "skip_event_streams_secrets_manager_auth_policy" } ], "iam_permissions": [ @@ -336,6 +348,18 @@ }, { "key": "existing_kms_key_crn" + }, + { + "key": "existing_secrets_manager_endpoint_type" + }, + { + "key": "existing_secrets_manager_instance_crn" + }, + { + "key": "service_credential_secrets" + }, + { + "key": "skip_event_streams_secrets_manager_auth_policy" } ], "iam_permissions": [ diff --git a/main.tf b/main.tf index 2313cfa5..6d67b67e 100644 --- a/main.tf +++ b/main.tf @@ -3,38 +3,11 @@ ####################################################################################### locals { - # Validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) - - # tflint-ignore: terraform_unused_declarations - validate_kms_plan = var.plan != "enterprise-3nodes-2tb" && var.kms_key_crn != null ? tobool("KMS encryption is only supported for enterprise plan.") : true - # tflint-ignore: terraform_unused_declarations - validate_metrics = var.plan != "enterprise-3nodes-2tb" && length(var.metrics) > 0 ? tobool("Metrics are only supported for enterprise plan.") : true - # tflint-ignore: terraform_unused_declarations - validate_quotas = var.plan != "enterprise-3nodes-2tb" && length(var.quotas) > 0 ? tobool("Quotas are only supported for enterprise plan.") : true - # tflint-ignore: terraform_unused_declarations - validate_schema_global_rule = var.plan != "enterprise-3nodes-2tb" && var.schema_global_rule != null ? tobool("Schema global rule is only supported for enterprise plan.") : true - - # tflint-ignore: terraform_unused_declarations - validate_kms_values = !var.kms_encryption_enabled && var.kms_key_crn != null ? tobool("When passing values for var.kms_key_crn, you must set var.kms_encryption_enabled to true. Otherwise unset them to use default encryption.") : true - # tflint-ignore: terraform_unused_declarations - validate_kms_vars = var.kms_encryption_enabled && var.kms_key_crn == null ? 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 - # tflint-ignore: terraform_unused_declarations - validate_auth_policy = var.kms_encryption_enabled && var.skip_kms_iam_authorization_policy == false && var.kms_key_crn == null ? tobool("When var.skip_kms_iam_authorization_policy is set to false, and var.kms_encryption_enabled to true, a value must be passed for var.kms_key_crn in order to create the auth policy.") : true - # tflint-ignore: terraform_unused_declarations - validate_throughput_lite_standard = ((var.plan == "lite" || var.plan == "standard") && var.throughput != 150) ? tobool("Throughput value cannot be changed in lite and standard plan. Default value is 150.") : true - # tflint-ignore: terraform_unused_declarations - validate_storage_size_lite_standard = ((var.plan == "lite" || var.plan == "standard") && var.storage_size != 2048) ? tobool("Storage size value cannot be changed in lite and standard plan. Default value is 2048.") : true - # tflint-ignore: terraform_unused_declarations - validate_service_end_points_lite_standard = ((var.plan == "lite" || var.plan == "standard") && var.service_endpoints != "public") ? tobool("Service endpoint cannot be changed in lite and standard plan. Default is public.") : true - # tflint-ignore: terraform_unused_declarations - validate_mirroring_topics = var.mirroring == null && var.mirroring_topic_patterns != null ? tobool("When passing values for var.mirroring_topic_patterns, values must also be passed for var.mirroring.") : true - # tflint-ignore: terraform_unused_declarations - validate_mirroring_config = var.mirroring != null && var.mirroring_topic_patterns == null ? tobool("When passing values for var.mirroring, values must also be passed for var.mirroring_topic_patterns.") : true - parsed_kms_key_crn = var.kms_key_crn != null ? split(":", var.kms_key_crn) : [] - kms_service = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[4] : null - kms_scope = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[6] : null - kms_account_id = length(local.parsed_kms_key_crn) > 0 ? split("/", local.kms_scope)[1] : null - kms_key_id = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[9] : null + parsed_kms_key_crn = var.kms_key_crn != null ? split(":", var.kms_key_crn) : [] + kms_service = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[4] : null + kms_scope = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[6] : null + kms_account_id = length(local.parsed_kms_key_crn) > 0 ? split("/", local.kms_scope)[1] : null + kms_key_id = length(local.parsed_kms_key_crn) > 0 ? local.parsed_kms_key_crn[9] : null } # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index a94f1887..f5f44ea6 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -9,7 +9,7 @@ The default values in this profile were scanned by [IBM Code Risk Analyzer (CRA) | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [terraform](#requirement\_terraform) | >= 1.9.0 | | [ibm](#requirement\_ibm) | >= 1.71.0, <2.0.0 | ### Modules diff --git a/modules/fscloud/version.tf b/modules/fscloud/version.tf index bc2af967..6f0dfd3b 100644 --- a/modules/fscloud/version.tf +++ b/modules/fscloud/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { # The below tflint-ignore is required because although the below provider is not directly required by this submodule, # it is required by consuming modules, and if not set here, the top level module calling this module will not be diff --git a/solutions/enterprise/DA-types.md b/solutions/enterprise/DA-types.md index 1168fb4a..74bff97c 100644 --- a/solutions/enterprise/DA-types.md +++ b/solutions/enterprise/DA-types.md @@ -3,6 +3,7 @@ Several optional input variables in the IBM Cloud Event Streams deployable architecture use complex object types. You specify these inputs when you configure you deployable architecture. - [Service credentials](#svc-credential-name) (`service_credential_names`) +- [Service credential secrets](#service-credential-secrets) (`service_credential_secrets`) - [Quotas](#quotas) (`quotas`) - [Mirroring](#mirroring) (`quotas`) @@ -10,6 +11,8 @@ Several optional input variables in the IBM Cloud Event Streams deployable archi You can specify a set of IAM credentials to connect to the instance 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 instance. 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 (`{}`). @@ -29,6 +32,73 @@ You can specify a set of IAM credentials to connect to the instance with the `se } ``` +## Service credential secrets + +When you add an IBM Event Streams 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://cloud.ibm.com/docs/secrets-manager?topic=secrets-manager-getting-started#getting-started) about service credential secrets. + +- Variable name: `service_credential_secrets`. +- Type: A list of objects that represent 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`: (required): 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_crn`: (required): The CRN of the role to give the service credential in the IBM Cloud Database service. Service credentials role CRNs can be found at https://cloud.ibm.com/iam/roles, select the IBM Cloud Database and select the role. +- `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_crn": "crn:v1:bluemix:public:iam::::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_crn": "crn:v1:bluemix:public:iam::::role:Reader" + } + ] +}, +{ + "secret_group_name": "sg-2" + "service_credentials": [ #pragma: allowlist secret + { + "secret_name": "cred-3" + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Editor" + } + ] +} +] +``` + ## Quotas You can set quotas of an Event Streams service instance. Both the default quota and user quotas may be managed. Quotas are only available on Event Streams Enterprise plan service instances. For more information, see [Event Streams Quotas](https://cloud.ibm.com/docs/EventStreams?topic=EventStreams-enabling_kafka_quotas). diff --git a/solutions/enterprise/main.tf b/solutions/enterprise/main.tf index 8612473e..be218d28 100644 --- a/solutions/enterprise/main.tf +++ b/solutions/enterprise/main.tf @@ -162,3 +162,72 @@ module "event_streams" { skip_kms_iam_authorization_policy = var.skip_event_streams_kms_auth_policy skip_es_s2s_iam_authorization_policy = var.skip_event_streams_s2s_iam_auth_policy } + +######################################################################################################################## +# Service Credentials +######################################################################################################################## + +# If existing EN intance CRN passed, parse details from it +module "existing_sm_crn_parser" { + count = var.existing_secrets_manager_instance_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_secrets_manager_instance_crn +} + +locals { + # parse SM GUID from CRN + existing_secrets_manager_instance_guid = var.existing_secrets_manager_instance_crn != null ? module.existing_sm_crn_parser[0].service_instance : null + # parse SM region from CRN + existing_secrets_manager_instance_region = var.existing_secrets_manager_instance_crn != null ? module.existing_sm_crn_parser[0].region : null + # generate list of service credential secrets to create + 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_crn = secret.service_credentials_source_service_role_crn + service_credentials_source_service_crn = module.event_streams.crn + secret_type = "service_credentials" #checkov:skip=CKV_SECRET_6 + } + ] + } + ] +} + +# create a service authorization between Secrets Manager and the target service (Event Streams) +resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { + count = var.skip_event_streams_secrets_manager_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 + source_service_name = "secrets-manager" + source_resource_instance_id = local.existing_secrets_manager_instance_guid + target_service_name = "messagehub" + target_resource_instance_id = module.event_streams.guid + roles = ["Key Manager"] + description = "Allow Secrets Manager instance to manage key for the event-streams instance" +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_en_authorization_policy" { + depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager] + create_duration = "30s" +} + +module "secrets_manager_service_credentials" { + count = length(local.service_credential_secrets) > 0 ? 1 : 0 + depends_on = [time_sleep.wait_for_en_authorization_policy] + source = "terraform-ibm-modules/secrets-manager/ibm//modules/secrets" + version = "1.22.0" + 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/enterprise/outputs.tf b/solutions/enterprise/outputs.tf index 6a54e241..809c849e 100644 --- a/solutions/enterprise/outputs.tf +++ b/solutions/enterprise/outputs.tf @@ -53,6 +53,16 @@ output "service_credentials_object" { sensitive = true } +output "service_credential_secrets" { + description = "Service credential secrets" + value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null +} + +output "service_credential_secret_groups" { + description = "Service credential secret groups" + value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secret_groups : null +} + output "mirroring_config_id" { description = "The ID of the mirroring config in CRN format" value = module.event_streams.mirroring_config_id diff --git a/solutions/enterprise/variables.tf b/solutions/enterprise/variables.tf index 110974f7..717812df 100644 --- a/solutions/enterprise/variables.tf +++ b/solutions/enterprise/variables.tf @@ -209,6 +209,10 @@ variable "existing_kms_instance_crn" { type = string description = "The CRN of a Key Protect or Hyper Protect Crypto Services instance. Required only when creating a new encryption key and key ring which will be used to encrypt event streams. To use an existing key, pass values for `existing_kms_key_crn`." default = null + validation { + condition = !(var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null) + error_message = "Both 'existing_kms_instance_crn' and 'existing_kms_key_crn' input variables can not be null. Set 'existing_kms_instance_crn' to create a new KMS key or 'existing_kms_key_crn' to use an existing KMS key." + } } variable "existing_kms_key_crn" { @@ -252,3 +256,63 @@ variable "ibmcloud_kms_api_key" { sensitive = true default = null } + +############################################################################# +# Secrets Manager Service Credentials +############################################################################# +variable "existing_secrets_manager_instance_crn" { + type = string + description = "The CRN of existing secrets manager to use to create service credential secrets for Event Streams instance." + default = null +} + +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_crn = 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 = [] + nullable = false + description = "Service credential secrets configuration for Event Streams. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-event-streams/tree/main/solutions/enterprise/DA-types.md#service-credential-secrets)." + validation { + # Service roles CRNs can be found at https://cloud.ibm.com/iam/roles, select Event Streams and select the role + condition = alltrue([ + for group in var.service_credential_secrets : alltrue([ + # crn:v?:bluemix; two non-empty segments; three possibly empty segments; :serviceRole or role: non-empty segment + for credential in group.service_credentials : can(regex("^crn:v[0-9]:bluemix(:..*){2}(:.*){3}:(serviceRole|role):..*$", credential.service_credentials_source_service_role_crn)) + ]) + ]) + error_message = "service_credentials_source_service_role_crn input variable must be a serviceRole CRN. See https://cloud.ibm.com/iam/roles" + } + validation { + condition = !(length(var.service_credential_secrets) > 0 && var.existing_secrets_manager_instance_crn == null) + error_message = "'existing_secrets_manager_instance_crn' is required when adding service credentials with the 'service_credential_secrets' input." + } +} + +variable "skip_event_streams_secrets_manager_auth_policy" { + type = bool + default = false + nullable = false + description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Event Streams.If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the Event Streams 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/enterprise/version.tf b/solutions/enterprise/version.tf index 8070399f..50c5bc98 100644 --- a/solutions/enterprise/version.tf +++ b/solutions/enterprise/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { ibm = { source = "IBM-Cloud/ibm" diff --git a/solutions/quickstart/DA-types.md b/solutions/quickstart/DA-types.md index 756f6376..0c306c4b 100644 --- a/solutions/quickstart/DA-types.md +++ b/solutions/quickstart/DA-types.md @@ -3,11 +3,14 @@ Several optional input variables in the IBM Cloud [Event Streams deployable architecture](https://cloud.ibm.com/catalog/7df1e4ca-d54c-4fd0-82ce-3d13247308cd/architecture/deploy-arch-ibm-event-streams-8272d54f-b54f-46a6-8dd6-772c6db82e87) use complex object types. You specify these inputs when you configure you deployable architecture. - [Service credentials](#svc-credential-name) (`service_credential_names`) +- [Service credential secrets](#service-credential-secrets) (`service_credential_secrets`) ## Service credentials You can specify a set of IAM credentials to connect to the instance 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 instance. 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 (`{}`). @@ -26,3 +29,70 @@ You can specify a set of IAM credentials to connect to the instance with the `se "es_manager" : "Manager" } ``` + +## Service credential secrets + +When you add an IBM Event Streams 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://cloud.ibm.com/docs/secrets-manager?topic=secrets-manager-getting-started#getting-started) about service credential secrets. + +- Variable name: `service_credential_secrets`. +- Type: A list of objects that represent 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`: (required): 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_crn`: (required): The CRN of the role to give the service credential in the IBM Cloud Database service. Service credentials role CRNs can be found at https://cloud.ibm.com/iam/roles, select the IBM Cloud Database and select the role. +- `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_crn": "crn:v1:bluemix:public:iam::::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_crn": "crn:v1:bluemix:public:iam::::role:Reader" + } + ] +}, +{ + "secret_group_name": "sg-2" + "service_credentials": [ #pragma: allowlist secret + { + "secret_name": "cred-3" + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Editor" + } + ] +} +] +``` diff --git a/solutions/quickstart/main.tf b/solutions/quickstart/main.tf index 35585000..7dd1ef42 100644 --- a/solutions/quickstart/main.tf +++ b/solutions/quickstart/main.tf @@ -26,3 +26,73 @@ module "event_streams" { access_tags = var.access_tags service_credential_names = var.service_credential_names } + + +######################################################################################################################## +# Service Credentials +######################################################################################################################## + +# If existing EN intance CRN passed, parse details from it +module "existing_sm_crn_parser" { + count = var.existing_secrets_manager_instance_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_secrets_manager_instance_crn +} + +locals { + # parse SM GUID from CRN + existing_secrets_manager_instance_guid = var.existing_secrets_manager_instance_crn != null ? module.existing_sm_crn_parser[0].service_instance : null + # parse SM region from CRN + existing_secrets_manager_instance_region = var.existing_secrets_manager_instance_crn != null ? module.existing_sm_crn_parser[0].region : null + # generate list of service credential secrets to create + 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_crn = secret.service_credentials_source_service_role_crn + service_credentials_source_service_crn = module.event_streams.crn + secret_type = "service_credentials" #checkov:skip=CKV_SECRET_6 + } + ] + } + ] +} + +# create a service authorization between Secrets Manager and the target service (Event Streams) +resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { + count = var.skip_event_streams_secrets_manager_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 + source_service_name = "secrets-manager" + source_resource_instance_id = local.existing_secrets_manager_instance_guid + target_service_name = "messagehub" + target_resource_instance_id = module.event_streams.guid + roles = ["Key Manager"] + description = "Allow Secrets Manager instance to manage key for the event-streams instance" +} + +# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 +resource "time_sleep" "wait_for_en_authorization_policy" { + depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager] + create_duration = "30s" +} + +module "secrets_manager_service_credentials" { + count = length(local.service_credential_secrets) > 0 ? 1 : 0 + depends_on = [time_sleep.wait_for_en_authorization_policy] + source = "terraform-ibm-modules/secrets-manager/ibm//modules/secrets" + version = "1.22.0" + 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/quickstart/outputs.tf b/solutions/quickstart/outputs.tf index 6656c847..24678eb9 100644 --- a/solutions/quickstart/outputs.tf +++ b/solutions/quickstart/outputs.tf @@ -47,3 +47,13 @@ output "service_credentials_object" { value = module.event_streams.service_credentials_object sensitive = true } + +output "service_credential_secrets" { + description = "Service credential secrets" + value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null +} + +output "service_credential_secret_groups" { + description = "Service credential secret groups" + value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secret_groups : null +} diff --git a/solutions/quickstart/variables.tf b/solutions/quickstart/variables.tf index 68f0bd99..05ce8b8b 100644 --- a/solutions/quickstart/variables.tf +++ b/solutions/quickstart/variables.tf @@ -87,3 +87,63 @@ variable "provider_visibility" { error_message = "Invalid visibility option. Allowed values are 'public', 'private', or 'public-and-private'." } } + +############################################################################# +# Secrets Manager Service Credentials +############################################################################# +variable "existing_secrets_manager_instance_crn" { + type = string + description = "The CRN of existing secrets manager to use to create service credential secrets for Event Streams instance." + default = null +} + +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_crn = 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) + })) + })) + nullable = false + default = [] + description = "Service credential secrets configuration for Event Streams. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-event-streams/tree/main/solutions/quickstart/DA-types.md#service-credential-secrets)." + validation { + # Service roles CRNs can be found at https://cloud.ibm.com/iam/roles, select Event Streams and select the role + condition = alltrue([ + for group in var.service_credential_secrets : alltrue([ + # crn:v?:bluemix; two non-empty segments; three possibly empty segments; :serviceRole or role: non-empty segment + for credential in group.service_credentials : can(regex("^crn:v[0-9]:bluemix(:..*){2}(:.*){3}:(serviceRole|role):..*$", credential.service_credentials_source_service_role_crn)) + ]) + ]) + error_message = "service_credentials_source_service_role_crn input variable must be a serviceRole CRN. See https://cloud.ibm.com/iam/roles" + } + validation { + condition = !(length(var.service_credential_secrets) > 0 && var.existing_secrets_manager_instance_crn == null) + error_message = "'existing_secrets_manager_instance_crn' is required when adding service credentials with the 'service_credential_secrets' input." + } +} + +variable "skip_event_streams_secrets_manager_auth_policy" { + type = bool + default = false + nullable = false + description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Event Streams.If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the Event Streams 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/quickstart/version.tf b/solutions/quickstart/version.tf index b38ea856..50c5bc98 100644 --- a/solutions/quickstart/version.tf +++ b/solutions/quickstart/version.tf @@ -1,9 +1,13 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { ibm = { source = "IBM-Cloud/ibm" version = "1.75.1" } + time = { + source = "hashicorp/time" + version = "0.12.1" + } } } diff --git a/tests/pr_test.go b/tests/pr_test.go index f865c3f9..db8d4357 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -2,6 +2,7 @@ package test import ( + "fmt" "log" "os" "testing" @@ -73,11 +74,32 @@ func TestRunQuickstartSolution(t *testing.T) { }) options.TerraformVars = map[string]interface{}{ - "ibmcloud_api_key": options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], - "resource_group_name": options.ResourceGroup, - "use_existing_resource_group": true, - "prefix": options.Prefix, - "provider_visibility": "public", + "ibmcloud_api_key": options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], + "resource_group_name": options.ResourceGroup, + "use_existing_resource_group": true, + "prefix": options.Prefix, + "provider_visibility": "public", + "existing_secrets_manager_instance_crn": permanentResources["secretsManagerCRN"], + "existing_secrets_manager_endpoint_type": "public", + "service_credential_secrets": []map[string]interface{}{ + { + "secret_group_name": fmt.Sprintf("%s-secret-group", options.Prefix), + "service_credentials": []map[string]string{ + { + "secret_name": fmt.Sprintf("%s-cred-config-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:ConfigReader", + }, + { + "secret_name": fmt.Sprintf("%s-cred-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Reader", + }, + { + "secret_name": fmt.Sprintf("%s-cred-key-manager", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:resource-controller::::role:KeyManager", + }, + }, + }, + }, } output, err := options.RunTestConsistency() @@ -110,6 +132,26 @@ func TestEnterpriseSolutionInSchematics(t *testing.T) { WaitJobCompleteMinutes: 180, }) + 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-config-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:ConfigReader", + }, + { + "secret_name": fmt.Sprintf("%s-cred-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Reader", + }, + { + "secret_name": fmt.Sprintf("%s-cred-key-manager", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:resource-controller::::role:KeyManager", + }, + }, + }, + } + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, {Name: "prefix", Value: options.Prefix, DataType: "string"}, @@ -119,8 +161,9 @@ func TestEnterpriseSolutionInSchematics(t *testing.T) { {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, {Name: "resource_tags", Value: options.Tags, DataType: "list(string)"}, + {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, + {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, } - err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") } diff --git a/variables.tf b/variables.tf index b838b21a..d44c195f 100644 --- a/variables.tf +++ b/variables.tf @@ -52,6 +52,10 @@ variable "throughput" { ]) error_message = "Supported throughput values are: 150, 300, 450." } + validation { + condition = !((var.plan == "lite" || var.plan == "standard") && var.throughput != 150) + error_message = "Throughput value cannot be changed in lite and standard plan. Default value is 150." + } } variable "storage_size" { @@ -69,6 +73,10 @@ variable "storage_size" { ]) error_message = "Supported throughput values are: 2048, 4096, 6144, 8192, 10240, 12288." } + validation { + condition = !((var.plan == "lite" || var.plan == "standard") && var.storage_size != 2048) + error_message = "Storage size value cannot be changed in lite and standard plan. Default value is 2048." + } } variable "service_endpoints" { @@ -79,6 +87,10 @@ variable "service_endpoints" { condition = contains(["public", "public-and-private", "private"], var.service_endpoints) error_message = "The specified service endpoint is not valid. Supported options are public, public-and-private, or private." } + validation { + condition = !((var.plan == "lite" || var.plan == "standard") && var.service_endpoints != "public") + error_message = "Service endpoint cannot be changed in lite and standard plan. Default is public." + } } variable "skip_kms_iam_authorization_policy" { @@ -119,6 +131,10 @@ variable "schema_global_rule" { condition = var.schema_global_rule == null || contains(["NONE", "FULL", "FULL_TRANSITIVE", "FORWARD", "FORWARD_TRANSITIVE", "BACKWARD", "BACKWARD_TRANSITIVE"], coalesce(var.schema_global_rule, "NONE")) error_message = "The schema_global_rule must be null or one of 'NONE', 'FULL', 'FULL_TRANSITIVE', 'FORWARD', 'FORWARD_TRANSITIVE', 'BACKWARD', 'BACKWARD_TRANSITIVE'." } + validation { + condition = !(var.plan != "enterprise-3nodes-2tb" && var.schema_global_rule != null) + error_message = "Schema global rule is only supported for enterprise plan." + } } variable "topics" { @@ -137,6 +153,18 @@ variable "kms_encryption_enabled" { 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 + validation { + condition = !(var.kms_encryption_enabled && var.kms_key_crn == null) + error_message = "When setting kms_encryption_enabled to true, a value must be passed for kms_key_crn input variables." + } + validation { + condition = !(!var.kms_encryption_enabled && var.kms_key_crn != null) + error_message = "When passing values for kms_key_crn, you must set kms_encryption_enabled to true. Otherwise unset them to use default encryption." + } + validation { + condition = !(var.kms_encryption_enabled && var.skip_kms_iam_authorization_policy == false && var.kms_key_crn == null) + error_message = "When var.skip_kms_iam_authorization_policy is set to false, and var.kms_encryption_enabled to true, a value must be passed for var.kms_key_crn in order to create the auth policy." + } } variable "kms_key_crn" { @@ -151,6 +179,10 @@ variable "kms_key_crn" { ]) error_message = "Must be the root key CRN from either the Key Protect or Hyper Protect Crypto Service." } + validation { + condition = !(var.plan != "enterprise-3nodes-2tb" && var.kms_key_crn != null) + error_message = "KMS encryption is only supported for enterprise plan." + } } variable "create_timeout" { @@ -205,11 +237,15 @@ variable "service_credential_names" { variable "metrics" { type = list(string) description = "Enhanced metrics to activate, as list of strings. Only allowed for enterprise plans. Allowed values: 'topic', 'partition', 'consumers'." + default = [] validation { condition = alltrue([for name in var.metrics : contains(["topic", "partition", "consumers"], name)]) error_message = "The specified metrics are not valid. The following values are valid for metrics: 'topic', 'partition', 'consumers'." } - default = [] + validation { + condition = !(var.plan != "enterprise-3nodes-2tb" && length(var.metrics) > 0) + error_message = "Metrics are only supported for enterprise plan." + } } variable "quotas" { @@ -224,12 +260,24 @@ variable "quotas" { condition = alltrue([for v in var.quotas : v.entity != "" && (v.producer_byte_rate >= 0 || v.consumer_byte_rate >= 0)]) error_message = "The quota entity must be defined, and at least one of producer_byte_rate or consumer_byte_rate must be set to a non-negative value" } + validation { + condition = !(var.plan != "enterprise-3nodes-2tb" && length(var.quotas) > 0) + error_message = "Quotas are only supported for enterprise plan." + } } variable "mirroring_topic_patterns" { type = list(string) description = "The list of the topics to set in instance. Required only if creating mirroring instance." default = null + validation { + condition = !(var.mirroring == null && var.mirroring_topic_patterns != null) + error_message = "When passing values for mirroring_topic_patterns, values must also be passed for mirroring." + } + validation { + condition = !(var.mirroring != null && var.mirroring_topic_patterns == null) + error_message = "When passing values for mirroring, values must also be passed for mirroring_topic_patterns." + } } variable "mirroring" { diff --git a/version.tf b/version.tf index ae953f3d..3ff7988e 100644 --- a/version.tf +++ b/version.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3.0" + required_version = ">= 1.9.0" required_providers { # Use "greater than or equal to" range in modules ibm = {