diff --git a/.secrets.baseline b/.secrets.baseline
index e551109a..b3ef0a0d 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -3,7 +3,7 @@
"files": "go.sum|^.secrets.baseline$",
"lines": null
},
- "generated_at": "2024-07-23T12:23:38Z",
+ "generated_at": "2024-09-02T14:42:20Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
@@ -98,11 +98,19 @@
}
],
"solutions/standard/DA-types.md": [
+ {
+ "hashed_secret": "1e5c2f367f02e47a8c160cda1cd9d91decbac441",
+ "is_secret": false,
+ "is_verified": false,
+ "line_number": 92,
+ "type": "Secret Keyword",
+ "verified_result": null
+ },
{
"hashed_secret": "44cdfc3615970ada14420caaaa5c5745fca06002",
"is_secret": false,
"is_verified": false,
- "line_number": 59,
+ "line_number": 130,
"type": "Secret Keyword",
"verified_result": null
},
@@ -110,7 +118,7 @@
"hashed_secret": "bd0d0d73a240c29656fb8ae0dfa5f863077788dc",
"is_secret": false,
"is_verified": false,
- "line_number": 64,
+ "line_number": 135,
"type": "Secret Keyword",
"verified_result": null
}
diff --git a/ibm_catalog.json b/ibm_catalog.json
index 65a88849..65a3d601 100644
--- a/ibm_catalog.json
+++ b/ibm_catalog.json
@@ -381,6 +381,28 @@
{
"key": "auto_scaling"
},
+ {
+ "key": "existing_secrets_manager_instance_crn"
+ },
+ {
+ "key": "existing_secrets_manager_endpoint_type",
+ "options": [
+ {
+ "displayname": "public",
+ "value": "public"
+ },
+ {
+ "displayname": "private",
+ "value": "private"
+ }
+ ]
+ },
+ {
+ "key": "service_credential_secrets"
+ },
+ {
+ "key": "skip_redis_sm_auth_policy"
+ },
{
"key": "backup_crn"
}
diff --git a/solutions/standard/DA-types.md b/solutions/standard/DA-types.md
index 7d2c618e..81204ff6 100644
--- a/solutions/standard/DA-types.md
+++ b/solutions/standard/DA-types.md
@@ -3,13 +3,14 @@
Several optional input variables in the IBM Cloud [Databases for Redis 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).
+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.
@@ -18,7 +19,7 @@ You can specify a set of IAM credentials to connect to the database with the `se
### 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. For more information, see [IBM Cloud IAM roles](https://cloud.ibm.com/docs/account?topic=account-userroles).
+- 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
@@ -31,6 +32,76 @@ You can specify a set of IAM credentials to connect to the database with the `se
}
```
+## Service credential secrets
+
+When you add an IBM Database for Redis 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 Redis 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": [
+ {
+ "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": [
+ {
+ "secret_name": "cred-3"
+ "service_credentials_source_service_role": "Editor"
+ },
+ {
+ "secret_name": "cred-4"
+ "service_credentials_source_service_role": "None"
+ }
+ ]
+ }
+]
+```
## Users
diff --git a/solutions/standard/README.md b/solutions/standard/README.md
index 21ec8b28..96e2a95f 100644
--- a/solutions/standard/README.md
+++ b/solutions/standard/README.md
@@ -6,6 +6,7 @@ This architecture creates an instance of IBM Cloud Databases for Redis and suppo
- A KMS root key, if one is not passed in.
- An IBM Cloud Databases for Redis instance with KMS encryption.
- Autoscaling rules for the database instance, if provided.
+- Service credential secrets and store them in secret manager.
![fscloud-redis](../../reference-architecture/deployable-architecture-redis.svg)
diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf
index 646b35d2..0d355c73 100644
--- a/solutions/standard/main.tf
+++ b/solutions/standard/main.tf
@@ -8,8 +8,8 @@ locals {
kms_key_crn = var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.key_ring_name, local.key_name)].crn
create_cross_account_auth_policy = !var.skip_iam_authorization_policy && var.ibmcloud_kms_api_key != null
- kms_service_name = var.existing_kms_instance_crn != null ? module.kms_instance_crn_parser[0].service_name : null
-
+ create_sm_auth_policy = var.skip_redis_sm_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1
+ kms_service_name = var.existing_kms_instance_crn != null ? module.kms_instance_crn_parser[0].service_name : null
}
#######################################################################################################################
@@ -181,3 +181,63 @@ module "redis" {
backup_encryption_key_crn = local.backup_kms_key_crn
backup_crn = var.backup_crn
}
+
+# create a service authorization between Secrets Manager and the target service (Databases for Redis)
+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-redis"
+ target_resource_instance_id = module.redis.guid
+ roles = ["Key Manager"]
+ description = "Allow Secrets Manager with instance id ${local.existing_secrets_manager_instance_guid} to manage key for the databases-for-redis instance"
+}
+
+# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478
+resource "time_sleep" "wait_for_redis_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.redis.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_redis_authorization_policy]
+ source = "terraform-ibm-modules/secrets-manager/ibm//modules/secrets"
+ version = "1.17.8"
+ 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
index 7ce6c14d..6428bddb 100644
--- a/solutions/standard/outputs.tf
+++ b/solutions/standard/outputs.tf
@@ -59,3 +59,8 @@ output "certificate_base64" {
value = module.redis.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/variables.tf b/solutions/standard/variables.tf
index 425c0e96..dc2391e2 100644
--- a/solutions/standard/variables.tf
+++ b/solutions/standard/variables.tf
@@ -209,6 +209,64 @@ variable "auto_scaling" {
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 Redis 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 Redis. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-redis/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_redis_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 Redis. If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the Redis 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."
+}
##############################################################
# Backup
diff --git a/tests/pr_test.go b/tests/pr_test.go
index b9e47b70..76119eb0 100644
--- a/tests/pr_test.go
+++ b/tests/pr_test.go
@@ -2,6 +2,7 @@
package test
import (
+ "encoding/json"
"fmt"
"io/fs"
"log"
@@ -141,6 +142,17 @@ func TestRunStandardSolutionSchematics(t *testing.T) {
},
}
+ 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)"},
@@ -148,12 +160,11 @@ func TestRunStandardSolutionSchematics(t *testing.T) {
{Name: "kms_endpoint_type", Value: "private", DataType: "string"},
{Name: "redis_version", Value: "7.2", DataType: "string"}, // Always lock this test into the latest supported Redis version
{Name: "resource_group_name", Value: options.Prefix, DataType: "string"},
- {Name: "service_credential_names", Value: "{\"admin_test\": \"Administrator\", \"editor_test\": \"Editor\"}", DataType: "map(string)"},
{Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"},
{Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"},
- {Name: "existing_backup_kms_key_crn", Value: permanentResources["hpcs_south_root_key_crn"], DataType: "string"},
+ {Name: "service_credential_names", Value: string(serviceCredentialNamesJSON), DataType: "map(string)"},
}
- err := options.RunSchematicTest()
+ err = options.RunSchematicTest()
assert.Nil(t, err, "This should not have errored")
}