Skip to content

Commit 3171580

Browse files
feat: added support to the DA to create service credentials and manage them in Secrets Manager (#266)
1 parent e71ef68 commit 3171580

File tree

7 files changed

+298
-8
lines changed

7 files changed

+298
-8
lines changed

.secrets.baseline

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,18 @@
7676
"name": "TwilioKeyDetector"
7777
}
7878
],
79-
"results": {},
79+
"results": {
80+
"solutions/standard/DA-types.md": [
81+
{
82+
"hashed_secret": "1e5c2f367f02e47a8c160cda1cd9d91decbac441",
83+
"is_secret": false,
84+
"is_verified": false,
85+
"line_number": 89,
86+
"type": "Secret Keyword",
87+
"verified_result": null
88+
}
89+
]
90+
},
8091
"version": "0.13.1+ibm.62.dss",
8192
"word_list": {
8293
"file": null,

solutions/standard/DA-types.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Configuring complex inputs in Event Notifications
2+
3+
Several optional input variables in the IBM Cloud [Event Notifications deployable architecture](https://cloud.ibm.com/catalog/7a4d68b4-cf8b-40cd-a3d1-f49aff526eb3/architecture/deploy-arch-ibm-event-notifications-c7ac3ee6-4f48-4236-b974-b0cd8c624a46-global) use complex object types. You specify these inputs when you configure you deployable architecture.
4+
5+
- [Service credentials](#svc-credential-name) (`service_credential_names`)
6+
- [Service credential secrets](#service-credential-secrets) (`service_credential_secrets`)
7+
8+
## Service credentials <a name="svc-credential-name"></a>
9+
10+
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)
11+
12+
- Variable name: `service_credential_names`.
13+
- Type: A map. The key is the name of the service credential. The value is the role that is assigned to that credential.
14+
- Default value: An empty map (`{}`).
15+
16+
### Options for service_credential_names
17+
18+
- Key (required): The name of the service credential.
19+
- Value (required): The IAM service role that is assigned to the credential. The following values are valid for service credential roles: 'Manager', 'Writer', 'Reader', 'Event Source Manager', 'Channel Editor', 'Event Notification Publisher', 'Status Reporter', 'Device Manager', 'Email Sender', 'Custom Email Status Reporter'. For more information, see [IBM Cloud IAM roles](https://cloud.ibm.com/docs/account?topic=account-userroles).
20+
21+
### Example service credentials
22+
23+
```hcl
24+
{
25+
"en_manager" : "Manager",
26+
"en_reader" : "Reader",
27+
"en_writer" : "Writer",
28+
"en_email_sender" : "Email Sender"
29+
}
30+
```
31+
32+
## Service credential secrets <a name="service-credential-secrets"></a>
33+
34+
When you add an IBM Event Notification 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.
35+
36+
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.
37+
38+
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.
39+
40+
[Learn more](https://cloud.ibm.com/docs/secrets-manager?topic=secrets-manager-getting-started#getting-started) about service credential secrets.
41+
42+
- Variable name: `service_credential_secrets`.
43+
- Type: A list of objects that represent service credential secret groups and secrets
44+
- Default value: An empty list (`[]`)
45+
46+
### Options for service_credential_secrets
47+
48+
- `secret_group_name` (required): A unique human-readable name that identifies this service credential secret group.
49+
- `secret_group_description` (optional, default = `null`): A human-readable description for this secret group.
50+
- `existing_secret_group`: (optional, default = `false`): Set to true, if secret group name provided in the variable `secret_group_name` already exists.
51+
- `service_credentials`: (required): A list of object that represents a service credential secret.
52+
53+
#### Options for service_credentials
54+
55+
- `secret_name`: (required): A unique human-readable name of the secret to create.
56+
- `service_credentials_source_service_role`: (required): The role to give the service credential in the Event Notification service. Acceptable values are `Writer`, `Reader`, `Manager`, `None`, `Event Source Manager`, `Channel Editor`, `Event Notification Publisher`, `Status Reporter`, `Device Manager`, `Email Sender`, `Custom Email Status Reporter` , and `Pool ID Manager`
57+
- `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 (|).
58+
- `secret_auto_rotation`: (optional, default = `true`): Whether to configure automatic rotation of service credential.
59+
- `secret_auto_rotation_unit`: (optional, default = `day`): Specifies the unit of time for rotation of a secret. Acceptable values are `day` or `month`.
60+
- `secret_auto_rotation_interval`: (optional, default = `89`): Specifies the rotation interval for the rotation unit.
61+
- `service_credentials_ttl`: (optional, default = `7776000`): The time-to-live (TTL) to assign to generated service credentials (in seconds).
62+
- `service_credential_secret_description`: (optional, default = `null`): Description of the secret to create.
63+
64+
The following example includes all the configuration options for four service credentials and two secret groups.
65+
```hcl
66+
[
67+
{
68+
"secret_group_name": "sg-1"
69+
"existing_secret_group": true
70+
"service_credentials": [
71+
{
72+
"secret_name": "cred-1"
73+
"service_credentials_source_service_role": "Writer"
74+
"secret_labels": ["test-writer-1", "test-writer-2"]
75+
"secret_auto_rotation": true
76+
"secret_auto_rotation_unit": "day"
77+
"secret_auto_rotation_interval": 89
78+
"service_credentials_ttl": 7776000
79+
"service_credential_secret_description": "sample description"
80+
},
81+
{
82+
"secret_name": "cred-2"
83+
"service_credentials_source_service_role": "Reader"
84+
}
85+
]
86+
},
87+
{
88+
"secret_group_name": "sg-2"
89+
"service_credentials": [
90+
{
91+
"secret_name": "cred-3"
92+
"service_credentials_source_service_role": "Editor"
93+
},
94+
{
95+
"secret_name": "cred-4"
96+
"service_credentials_source_service_role": "None"
97+
}
98+
]
99+
}
100+
]
101+
```

solutions/standard/main.tf

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,25 @@ locals {
3232
cos_instance_guid = var.existing_cos_instance_crn != null ? element(split(":", var.existing_cos_instance_crn), length(split(":", var.existing_cos_instance_crn)) - 3) : null
3333
cos_kms_key_crn = var.existing_cos_bucket_name != null ? null : var.existing_kms_root_key_crn != null ? var.existing_kms_root_key_crn : var.existing_en_instance_crn == null ? module.kms[0].keys[format("%s.%s", local.cos_key_ring_name, local.cos_key_name)].crn : null
3434

35+
existing_en_instance_guid = var.existing_en_instance_crn != null ? element(split(":", var.existing_en_instance_crn), length(split(":", var.existing_en_instance_crn)) - 3) : null
36+
use_existing_en_instance = var.existing_en_instance_crn != null
37+
38+
eventnotification_guid = local.use_existing_en_instance ? data.ibm_resource_instance.existing_en_instance[0].guid : module.event_notifications[0].guid
39+
3540
kms_service_name = var.existing_kms_instance_crn != null ? (
3641
can(regex(".*kms.*", var.existing_kms_instance_crn)) ? "kms" : (
3742
can(regex(".*hs-crypto.*", var.existing_kms_instance_crn)) ? "hs-crypto" : null
3843
)
3944
) : null
45+
46+
# tflint-ignore: terraform_unused_declarations
47+
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
48+
49+
50+
existing_secrets_manager_instance_crn_split = var.existing_secrets_manager_instance_crn != null ? split(":", var.existing_secrets_manager_instance_crn) : null
51+
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
52+
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
53+
4054
}
4155

4256
# Data source to account settings for retrieving cross account id
@@ -124,7 +138,8 @@ locals {
124138
# If a bucket name is passed, or an existing EN CRN is passed; do not create bucket (or instance)
125139
create_cos_bucket = var.existing_cos_bucket_name != null || var.existing_en_instance_crn != null ? false : true
126140
# tflint-ignore: terraform_unused_declarations
127-
validate_cos_regions = var.cos_bucket_region != null && var.cross_region_location != null ? tobool("Cannot provide values for var.cos_bucket_region and var.cross_region_location") : true
141+
validate_cos_regions = var.cos_bucket_region != null && var.cross_region_location != null ? tobool("Cannot provide values for var.cos_bucket_region and var.cross_region_location") : true
142+
128143
cos_bucket_name = var.existing_cos_bucket_name != null ? var.existing_cos_bucket_name : local.create_cos_bucket ? (var.prefix != null ? "${var.prefix}-${var.cos_bucket_name}" : var.cos_bucket_name) : null
129144
cos_bucket_name_with_suffix = var.existing_cos_bucket_name != null ? var.existing_cos_bucket_name : local.create_cos_bucket ? module.cos[0].bucket_name : null
130145
cos_bucket_region = var.cos_bucket_region != null ? var.cos_bucket_region : var.cross_region_location != null ? null : var.region
@@ -176,7 +191,7 @@ data "ibm_resource_instance" "existing_en" {
176191
}
177192

178193
module "event_notifications" {
179-
count = var.existing_en_instance_crn != null ? 0 : 1
194+
count = local.use_existing_en_instance ? 0 : 1
180195
source = "../.."
181196
resource_group_id = module.resource_group[0].resource_group_id
182197
region = var.region
@@ -198,3 +213,62 @@ module "event_notifications" {
198213
skip_en_cos_auth_policy = var.skip_en_cos_auth_policy
199214
cos_endpoint = local.cos_endpoint
200215
}
216+
217+
# create a service authorization between Secrets Manager and the target service (Event Notification)
218+
resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" {
219+
count = var.skip_en_sm_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1
220+
source_service_name = "secrets-manager"
221+
source_resource_instance_id = local.existing_secrets_manager_instance_guid
222+
target_service_name = "event-notifications"
223+
target_resource_instance_id = local.eventnotification_guid
224+
roles = ["Key Manager"]
225+
description = "Allow Secrets Manager instance to manage key for the event-notification instance"
226+
}
227+
228+
# workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478
229+
resource "time_sleep" "wait_for_en_authorization_policy" {
230+
depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager]
231+
create_duration = "30s"
232+
}
233+
234+
locals {
235+
service_credential_secrets = [
236+
for service_credentials in var.service_credential_secrets : {
237+
secret_group_name = service_credentials.secret_group_name
238+
secret_group_description = service_credentials.secret_group_description
239+
existing_secret_group = service_credentials.existing_secret_group
240+
secrets = [
241+
for secret in service_credentials.service_credentials : {
242+
secret_name = secret.secret_name
243+
secret_labels = secret.secret_labels
244+
secret_auto_rotation = secret.secret_auto_rotation
245+
secret_auto_rotation_unit = secret.secret_auto_rotation_unit
246+
secret_auto_rotation_interval = secret.secret_auto_rotation_interval
247+
service_credentials_ttl = secret.service_credentials_ttl
248+
service_credential_secret_description = secret.service_credential_secret_description
249+
service_credentials_source_service_role = secret.service_credentials_source_service_role
250+
service_credentials_source_service_crn = local.use_existing_en_instance ? data.ibm_resource_instance.existing_en_instance[0].id : module.event_notifications[0].crn
251+
secret_type = "service_credentials" #checkov:skip=CKV_SECRET_6
252+
}
253+
]
254+
}
255+
]
256+
}
257+
258+
module "secrets_manager_service_credentials" {
259+
count = length(local.service_credential_secrets) > 0 ? 1 : 0
260+
depends_on = [time_sleep.wait_for_en_authorization_policy]
261+
source = "terraform-ibm-modules/secrets-manager/ibm//modules/secrets"
262+
version = "1.17.4"
263+
existing_sm_instance_guid = local.existing_secrets_manager_instance_guid
264+
existing_sm_instance_region = local.existing_secrets_manager_instance_region
265+
endpoint_type = var.existing_secrets_manager_endpoint_type
266+
secrets = local.service_credential_secrets
267+
}
268+
269+
# this extra block is needed when passing in an existing EN instance - the resource data block
270+
# requires an id to retrieve the data
271+
data "ibm_resource_instance" "existing_en_instance" {
272+
count = local.use_existing_en_instance ? 1 : 0
273+
identifier = local.existing_en_instance_guid
274+
}

solutions/standard/outputs.tf

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ output "event_notification_instance_name" {
99

1010
output "crn" {
1111
description = "Event Notification crn"
12-
value = var.existing_en_instance_crn == null ? module.event_notifications[0].crn : var.existing_en_instance_crn
12+
value = local.use_existing_en_instance ? var.existing_en_instance_crn : module.event_notifications[0].crn
1313
}
1414

1515
output "guid" {
@@ -19,12 +19,22 @@ output "guid" {
1919

2020
output "service_credentials_json" {
2121
description = "Service credentials json map"
22-
value = var.existing_en_instance_crn == null ? module.event_notifications[0].service_credentials_json : null
22+
value = local.use_existing_en_instance ? null : module.event_notifications[0].service_credentials_json
2323
sensitive = true
2424
}
2525

2626
output "service_credentials_object" {
2727
description = "Service credentials object"
28-
value = var.existing_en_instance_crn == null ? module.event_notifications[0].service_credentials_object : null
28+
value = local.use_existing_en_instance ? null : module.event_notifications[0].service_credentials_object
2929
sensitive = true
3030
}
31+
32+
output "service_credential_secrets" {
33+
description = "Service credential secrets"
34+
value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null
35+
}
36+
37+
output "service_credential_secret_groups" {
38+
description = "Service credential secret groups"
39+
value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secret_groups : null
40+
}

solutions/standard/variables.tf

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ variable "prefix" {
4444

4545
variable "service_credential_names" {
4646
type = map(string)
47-
description = "The mapping of names and roles for service credentials that you want to create for the Event Notifications instance."
47+
description = "The mapping of names and roles for service credentials that you want to create for the Event Notifications instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-event-notifications/tree/main/solutions/standard/DA-types.md#service-credential-secrets"
4848
default = {}
4949

5050
validation {
@@ -280,3 +280,62 @@ variable "existing_cos_endpoint" {
280280
description = "The endpoint URL for your bucket region. [Learn more](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-endpoints). Only required if using an existing bucket with the `existing_cos_bucket_name` variable."
281281
default = null
282282
}
283+
284+
##############################################################################
285+
## Secrets Manager Service Credentials
286+
##############################################################################
287+
288+
variable "existing_secrets_manager_instance_crn" {
289+
type = string
290+
default = null
291+
description = "The CRN of existing secrets manager to use to create service credential secrets for Event Notification instance."
292+
}
293+
294+
variable "existing_secrets_manager_endpoint_type" {
295+
type = string
296+
description = "The endpoint type to use if `existing_secrets_manager_instance_crn` is specified. Possible values: public, private."
297+
default = "private"
298+
validation {
299+
condition = contains(["public", "private"], var.existing_secrets_manager_endpoint_type)
300+
error_message = "Only \"public\" and \"private\" are allowed values for 'existing_secrets_endpoint_type'."
301+
}
302+
}
303+
304+
variable "service_credential_secrets" {
305+
type = list(object({
306+
secret_group_name = string
307+
secret_group_description = optional(string)
308+
existing_secret_group = optional(bool)
309+
service_credentials = list(object({
310+
secret_name = string
311+
service_credentials_source_service_role = string
312+
secret_labels = optional(list(string))
313+
secret_auto_rotation = optional(bool)
314+
secret_auto_rotation_unit = optional(string)
315+
secret_auto_rotation_interval = optional(number)
316+
service_credentials_ttl = optional(string)
317+
service_credential_secret_description = optional(string)
318+
319+
}))
320+
}))
321+
default = []
322+
description = "Service credential secrets configuration for Event Notification. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-event-notifications/tree/main/solutions/standard/DA-types.md#service-credential-secrets)."
323+
324+
validation {
325+
condition = alltrue([
326+
for group in var.service_credential_secrets : alltrue([
327+
for credential in group.service_credentials : contains(
328+
["Writer", "Reader", "Manager", "None", "Event Source Manager", "Channel Editor", "Event Notification Publisher", "Status Reporter", "Device Manager", "Email Sender", "Custom Email Status Reporter", "Pool ID Manager"], credential.service_credentials_source_service_role
329+
)
330+
])
331+
])
332+
error_message = "service_credentials_source_service_role role must be one of 'Writer', 'Reader', 'Manager', 'None', 'Event Source Manager', 'Channel Editor', 'Event Notification Publisher', 'Status Reporter', 'Device Manager', 'Email Sender', 'Custom Email Status Reporter' and 'Pool ID Manager'."
333+
334+
}
335+
}
336+
337+
variable "skip_en_sm_auth_policy" {
338+
type = bool
339+
default = false
340+
description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Event Notification.If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the Event Notifications 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."
341+
}

solutions/standard/version.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ terraform {
66
source = "IBM-Cloud/ibm"
77
version = "1.70.0"
88
}
9+
time = {
10+
source = "hashicorp/time"
11+
version = "0.12.0"
12+
}
913
}
1014
}

0 commit comments

Comments
 (0)