Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support to store service credentials in sm #411

Merged
merged 20 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -98,19 +98,27 @@
}
],
"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
},
{
"hashed_secret": "bd0d0d73a240c29656fb8ae0dfa5f863077788dc",
"is_secret": false,
"is_verified": false,
"line_number": 64,
"line_number": 135,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
2 changes: 1 addition & 1 deletion common-dev-assets
75 changes: 73 additions & 2 deletions solutions/standard/DA-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a name="svc-credential-name"></a>

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.
Expand All @@ -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

Expand All @@ -31,6 +32,76 @@ You can specify a set of IAM credentials to connect to the database with the `se
}
```

## Service credential secrets <a name="service-credential-secrets"></a>

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 <a name="users"></a>

Expand Down
1 change: 1 addition & 0 deletions solutions/standard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
59 changes: 59 additions & 0 deletions solutions/standard/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,62 @@ module "redis" {
service_credential_names = var.service_credential_names
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 = var.skip_rd_sm_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
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 to manage key for the databases-for-redis instance"
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
}

# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478
resource "time_sleep" "wait_for_rd_authorization_policy" {
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
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_rd_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
}
10 changes: 10 additions & 0 deletions solutions/standard/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,13 @@ output "certificate_base64" {
value = module.redis.certificate_base64
sensitive = true
}

output "service_credential_secrets" {
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
description = "Service credential secrets"
value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null
}

output "service_credential_secret_groups" {
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
description = "Service credential secret groups"
value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secret_groups : null
}
59 changes: 59 additions & 0 deletions solutions/standard/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,65 @@ variable "auto_scaling" {
default = null
}

##############################################################################
## Secrets Manager Service Credentials
##############################################################################

variable "skip_rd_sm_auth_policy" {
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
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."
}
Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved

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'."

}
}


##############################################################
# Backup
Expand Down
15 changes: 14 additions & 1 deletion tests/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package test

import (
"encoding/json"
"fmt"
"io/fs"
"log"
Expand Down Expand Up @@ -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)
}

Aayush-Abhyarthi marked this conversation as resolved.
Show resolved Hide resolved
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)"},
Expand All @@ -151,8 +163,9 @@ func TestRunStandardSolutionSchematics(t *testing.T) {
{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: "service_credential_names", Value: string(serviceCredentialNamesJSON), DataType: "map(string)"},
}
err := options.RunSchematicTest()
err = options.RunSchematicTest()
assert.Nil(t, err, "This should not have errored")
}

Expand Down