From cd4d29cdd47781d230c0d748ca982063729ce740 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sun, 3 Mar 2024 18:26:10 -0500 Subject: [PATCH 1/4] manage master password rotation --- README.md | 7 +++++++ main.tf | 17 +++++++++++++++++ outputs.tf | 9 +++++++++ variables.tf | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/README.md b/README.md index 4e8a9df..7168386 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ No modules. | [aws_redshift_snapshot_schedule_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/redshift_snapshot_schedule_association) | resource | | [aws_redshift_subnet_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/redshift_subnet_group) | resource | | [aws_redshift_usage_limit.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/redshift_usage_limit) | resource | +| [aws_secretsmanager_secret_rotation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation) | resource | | [random_password.master_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [aws_iam_policy_document.scheduled_action](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.scheduled_action_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -262,8 +263,13 @@ No modules. | [logging](#input\_logging) | Logging configuration for the cluster | `any` | `{}` | no | | [maintenance\_track\_name](#input\_maintenance\_track\_name) | The name of the maintenance track for the restored cluster. When you take a snapshot, the snapshot inherits the MaintenanceTrack value from the cluster. The snapshot might be on a different track than the cluster that was the source for the snapshot. Default value is `current` | `string` | `null` | no | | [manage\_master\_password](#input\_manage\_master\_password) | Whether to use AWS SecretsManager to manage the cluster admin credentials. Conflicts with `master_password`. One of `master_password` or `manage_master_password` is required unless `snapshot_identifier` is provided | `bool` | `false` | no | +| [manage\_master\_password\_rotation](#input\_manage\_master\_password\_rotation) | Whether to manage the master user password rotation. Setting this value to false after previously having been set to true will disable automatic rotation. | `bool` | `false` | no | | [manual\_snapshot\_retention\_period](#input\_manual\_snapshot\_retention\_period) | The default number of days to retain a manual snapshot. If the value is -1, the snapshot is retained indefinitely. This setting doesn't change the retention period of existing snapshots. Valid values are between `-1` and `3653`. Default value is `-1` | `number` | `null` | no | | [master\_password](#input\_master\_password) | Password for the master DB user. (Required unless a `snapshot_identifier` is provided). Must contain at least 8 chars, one uppercase letter, one lowercase letter, and one number | `string` | `null` | no | +| [master\_password\_rotate\_immediately](#input\_master\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no | +| [master\_password\_rotation\_automatically\_after\_days](#input\_master\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either automatically\_after\_days or schedule\_expression must be specified. | `number` | `null` | no | +| [master\_password\_rotation\_duration](#input\_master\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no | +| [master\_password\_rotation\_schedule\_expression](#input\_master\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically\_after\_days or schedule\_expression must be specified. | `string` | `null` | no | | [master\_password\_secret\_kms\_key\_id](#input\_master\_password\_secret\_kms\_key\_id) | ID of the KMS key used to encrypt the cluster admin credentials secret | `string` | `null` | no | | [master\_username](#input\_master\_username) | Username for the master DB user (Required unless a `snapshot_identifier` is provided). Defaults to `awsuser` | `string` | `"awsuser"` | no | | [multi\_az](#input\_multi\_az) | Specifies if the Redshift cluster is multi-AZ | `bool` | `null` | no | @@ -320,6 +326,7 @@ No modules. | [cluster\_preferred\_maintenance\_window](#output\_cluster\_preferred\_maintenance\_window) | The backup window | | [cluster\_public\_key](#output\_cluster\_public\_key) | The public key for the cluster | | [cluster\_revision\_number](#output\_cluster\_revision\_number) | The specific revision number of the database in the cluster | +| [cluster\_secretsmanager\_secret\_rotation\_enabled](#output\_cluster\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret | | [cluster\_subnet\_group\_name](#output\_cluster\_subnet\_group\_name) | The name of a cluster subnet group to be associated with this cluster | | [cluster\_type](#output\_cluster\_type) | The Redshift cluster type | | [cluster\_version](#output\_cluster\_version) | The version of Redshift engine software | diff --git a/main.tf b/main.tf index 6a8c57d..80dd9e5 100644 --- a/main.tf +++ b/main.tf @@ -336,3 +336,20 @@ resource "aws_cloudwatch_log_group" "this" { tags = merge(var.tags, var.cloudwatch_log_group_tags) } + +################################################################################ +# Managed Secret Rotation +################################################################################ + +resource "aws_secretsmanager_secret_rotation" "this" { + count = var.create && var.manage_master_password && var.manage_master_password_rotation ? 1 : 0 + + secret_id = aws_redshift_cluster.this[0].master_password_secret_arn + rotate_immediately = var.master_password_rotate_immediately + + rotation_rules { + automatically_after_days = var.master_password_rotation_automatically_after_days + duration = var.master_password_rotation_duration + schedule_expression = var.master_password_rotation_schedule_expression + } +} diff --git a/outputs.tf b/outputs.tf index d0ed8e5..589abe7 100644 --- a/outputs.tf +++ b/outputs.tf @@ -227,3 +227,12 @@ output "master_password_secret_arn" { description = "ARN of managed master password secret" value = try(aws_redshift_cluster.this[0].master_password_secret_arn, null) } + +################################################################################ +# Managed Secret Rotation +################################################################################ + +output "cluster_secretsmanager_secret_rotation_enabled" { + description = "Specifies whether automatic rotation is enabled for the secret" + value = try(aws_secretsmanager_secret_rotation.this[0].rotation_enabled, null) +} diff --git a/variables.tf b/variables.tf index 809ed74..cd1589e 100644 --- a/variables.tf +++ b/variables.tf @@ -508,3 +508,37 @@ variable "cloudwatch_log_group_tags" { type = map(string) default = {} } + +################################################################################ +# Managed Secret Rotation +################################################################################ + +variable "manage_master_password_rotation" { + description = "Whether to manage the master user password rotation. Setting this value to false after previously having been set to true will disable automatic rotation." + type = bool + default = false +} + +variable "master_password_rotate_immediately" { + description = "Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window." + type = bool + default = null +} + +variable "master_password_rotation_automatically_after_days" { + description = "Specifies the number of days between automatic scheduled rotations of the secret. Either automatically_after_days or schedule_expression must be specified." + type = number + default = null +} + +variable "master_password_rotation_duration" { + description = "The length of the rotation window in hours. For example, 3h for a three hour window." + type = string + default = null +} + +variable "master_password_rotation_schedule_expression" { + description = "A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically_after_days or schedule_expression must be specified." + type = string + default = null +} From dbb87bfe85259bcfe23d16d5981d228487171e21 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Tue, 5 Mar 2024 20:33:16 -0500 Subject: [PATCH 2/4] update example --- examples/complete/README.md | 1 + examples/complete/main.tf | 3 +++ examples/complete/outputs.tf | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/examples/complete/README.md b/examples/complete/README.md index 46996aa..2e1a916 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -92,6 +92,7 @@ No inputs. | [endpoint\_access\_port](#output\_endpoint\_access\_port) | The port number on which the cluster accepts incoming connections | | [endpoint\_access\_vpc\_endpoint](#output\_endpoint\_access\_vpc\_endpoint) | The connection endpoint for connecting to an Amazon Redshift cluster through the proxy. See details below | | [master\_password\_secret\_arn](#output\_master\_password\_secret\_arn) | ARN of managed master password secret | +| [master\_password\_secretsmanager\_secret\_rotation\_enabled](#output\_master\_password\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret | | [parameter\_group\_arn](#output\_parameter\_group\_arn) | Amazon Resource Name (ARN) of the parameter group created | | [parameter\_group\_id](#output\_parameter\_group\_id) | The name of the Redshift parameter group created | | [scheduled\_action\_iam\_role\_arn](#output\_scheduled\_action\_iam\_role\_arn) | Scheduled actions IAM role ARN | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index d2e7fc7..f61c8f8 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -45,6 +45,9 @@ module "redshift" { # Or make Redshift manage it in secrets manager manage_master_password = true + manage_master_password_rotation = true + master_password_rotation_schedule_expression = "rate(90 days)" + encrypted = true kms_key_arn = aws_kms_key.redshift.arn diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index a2ecfb1..9f11387 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -222,3 +222,8 @@ output "master_password_secret_arn" { description = "ARN of managed master password secret" value = module.redshift.master_password_secret_arn } + +output "master_password_secretsmanager_secret_rotation_enabled" { + description = "Specifies whether automatic rotation is enabled for the secret" + value = module.redshift.cluster_secretsmanager_secret_rotation_enabled +} From df522b21950baf8b137b4df7ee4dded5c4a7bab3 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Wed, 6 Mar 2024 19:56:17 -0500 Subject: [PATCH 3/4] update variable description --- README.md | 2 +- variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7168386..4f34d7f 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ No modules. | [master\_password\_rotate\_immediately](#input\_master\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no | | [master\_password\_rotation\_automatically\_after\_days](#input\_master\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either automatically\_after\_days or schedule\_expression must be specified. | `number` | `null` | no | | [master\_password\_rotation\_duration](#input\_master\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no | -| [master\_password\_rotation\_schedule\_expression](#input\_master\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically\_after\_days or schedule\_expression must be specified. | `string` | `null` | no | +| [master\_password\_rotation\_schedule\_expression](#input\_master\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either `master_user_password_rotation_automatically_after_days` or `master_user_password_rotation_schedule_expression` must be specified. | `string` | `null` | no | | [master\_password\_secret\_kms\_key\_id](#input\_master\_password\_secret\_kms\_key\_id) | ID of the KMS key used to encrypt the cluster admin credentials secret | `string` | `null` | no | | [master\_username](#input\_master\_username) | Username for the master DB user (Required unless a `snapshot_identifier` is provided). Defaults to `awsuser` | `string` | `"awsuser"` | no | | [multi\_az](#input\_multi\_az) | Specifies if the Redshift cluster is multi-AZ | `bool` | `null` | no | diff --git a/variables.tf b/variables.tf index cd1589e..c4bc8dd 100644 --- a/variables.tf +++ b/variables.tf @@ -538,7 +538,7 @@ variable "master_password_rotation_duration" { } variable "master_password_rotation_schedule_expression" { - description = "A cron() or rate() expression that defines the schedule for rotating your secret. Either automatically_after_days or schedule_expression must be specified." + description = "A cron() or rate() expression that defines the schedule for rotating your secret. Either `master_user_password_rotation_automatically_after_days` or `master_user_password_rotation_schedule_expression` must be specified." type = string default = null } From 72aba682e8ef5cb850a0cc9c06d0be8fbf3ffc11 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Thu, 7 Mar 2024 19:38:21 -0500 Subject: [PATCH 4/4] update variable description --- README.md | 2 +- variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f34d7f..d4b5798 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,7 @@ No modules. | [manual\_snapshot\_retention\_period](#input\_manual\_snapshot\_retention\_period) | The default number of days to retain a manual snapshot. If the value is -1, the snapshot is retained indefinitely. This setting doesn't change the retention period of existing snapshots. Valid values are between `-1` and `3653`. Default value is `-1` | `number` | `null` | no | | [master\_password](#input\_master\_password) | Password for the master DB user. (Required unless a `snapshot_identifier` is provided). Must contain at least 8 chars, one uppercase letter, one lowercase letter, and one number | `string` | `null` | no | | [master\_password\_rotate\_immediately](#input\_master\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no | -| [master\_password\_rotation\_automatically\_after\_days](#input\_master\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either automatically\_after\_days or schedule\_expression must be specified. | `number` | `null` | no | +| [master\_password\_rotation\_automatically\_after\_days](#input\_master\_password\_rotation\_automatically\_after\_days) | Specifies the number of days between automatic scheduled rotations of the secret. Either `master_user_password_rotation_automatically_after_days` or `master_user_password_rotation_schedule_expression` must be specified. | `number` | `null` | no | | [master\_password\_rotation\_duration](#input\_master\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no | | [master\_password\_rotation\_schedule\_expression](#input\_master\_password\_rotation\_schedule\_expression) | A cron() or rate() expression that defines the schedule for rotating your secret. Either `master_user_password_rotation_automatically_after_days` or `master_user_password_rotation_schedule_expression` must be specified. | `string` | `null` | no | | [master\_password\_secret\_kms\_key\_id](#input\_master\_password\_secret\_kms\_key\_id) | ID of the KMS key used to encrypt the cluster admin credentials secret | `string` | `null` | no | diff --git a/variables.tf b/variables.tf index c4bc8dd..f9cfc55 100644 --- a/variables.tf +++ b/variables.tf @@ -526,7 +526,7 @@ variable "master_password_rotate_immediately" { } variable "master_password_rotation_automatically_after_days" { - description = "Specifies the number of days between automatic scheduled rotations of the secret. Either automatically_after_days or schedule_expression must be specified." + description = "Specifies the number of days between automatic scheduled rotations of the secret. Either `master_user_password_rotation_automatically_after_days` or `master_user_password_rotation_schedule_expression` must be specified." type = number default = null }