From ae752de086b9ab6f9a1178024d1e99dce9852d37 Mon Sep 17 00:00:00 2001
From: Melissa Greenbaum <69476188+magreenbaum@users.noreply.github.com>
Date: Mon, 4 Mar 2024 20:00:16 -0500
Subject: [PATCH] feat: Secretsmanager secret rotation for master user password
(#433)
---
README.md | 7 +++++++
examples/multi-az/README.md | 1 +
examples/multi-az/main.tf | 3 +++
examples/multi-az/outputs.tf | 9 +++++++++
examples/mysql/README.md | 1 +
examples/mysql/main.tf | 3 +++
examples/mysql/outputs.tf | 9 +++++++++
main.tf | 17 +++++++++++++++++
outputs.tf | 9 +++++++++
variables.tf | 34 ++++++++++++++++++++++++++++++++++
10 files changed, 93 insertions(+)
diff --git a/README.md b/README.md
index abe8dc9..a8ba29d 100644
--- a/README.md
+++ b/README.md
@@ -253,6 +253,7 @@ No modules.
| [aws_rds_cluster_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_instance) | resource |
| [aws_rds_cluster_parameter_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource |
| [aws_rds_cluster_role_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_role_association) | resource |
+| [aws_secretsmanager_secret_rotation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation) | resource |
| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
| [aws_iam_policy_document.monitoring_rds_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
@@ -341,7 +342,12 @@ No modules.
| [is\_primary\_cluster](#input\_is\_primary\_cluster) | Determines whether cluster is primary cluster with writer instance (set to `false` for global cluster and replica clusters) | `bool` | `true` | no |
| [kms\_key\_id](#input\_kms\_key\_id) | The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to `true` | `string` | `null` | no |
| [manage\_master\_user\_password](#input\_manage\_master\_user\_password) | Set to true to allow RDS to manage the master user password in Secrets Manager. Cannot be set if `master_password` is provided | `bool` | `true` | no |
+| [manage\_master\_user\_password\_rotation](#input\_manage\_master\_user\_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 |
| [master\_password](#input\_master\_password) | Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file. Required unless `manage_master_user_password` is set to `true` or unless `snapshot_identifier` or `replication_source_identifier` is provided or unless a `global_cluster_identifier` is provided when the cluster is the secondary cluster of a global database | `string` | `null` | no |
+| [master\_user\_password\_rotate\_immediately](#input\_master\_user\_password\_rotate\_immediately) | Specifies whether to rotate the secret immediately or wait until the next scheduled rotation window. | `bool` | `null` | no |
+| [master\_user\_password\_rotation\_automatically\_after\_days](#input\_master\_user\_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\_user\_password\_rotation\_duration](#input\_master\_user\_password\_rotation\_duration) | The length of the rotation window in hours. For example, 3h for a three hour window. | `string` | `null` | no |
+| [master\_user\_password\_rotation\_schedule\_expression](#input\_master\_user\_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\_user\_secret\_kms\_key\_id](#input\_master\_user\_secret\_kms\_key\_id) | The Amazon Web Services KMS key identifier is the key ARN, key ID, alias ARN, or alias name for the KMS key | `string` | `null` | no |
| [master\_username](#input\_master\_username) | Username for the master DB user. Required unless `snapshot_identifier` or `replication_source_identifier` is provided or unless a `global_cluster_identifier` is provided when the cluster is the secondary cluster of a global database | `string` | `null` | no |
| [monitoring\_interval](#input\_monitoring\_interval) | The interval, in seconds, between points when Enhanced Monitoring metrics are collected for instances. Set to `0` to disable. Default is `0` | `number` | `0` | no |
@@ -401,6 +407,7 @@ No modules.
| [db\_cluster\_cloudwatch\_log\_groups](#output\_db\_cluster\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
| [db\_cluster\_parameter\_group\_arn](#output\_db\_cluster\_parameter\_group\_arn) | The ARN of the DB cluster parameter group created |
| [db\_cluster\_parameter\_group\_id](#output\_db\_cluster\_parameter\_group\_id) | The ID of the DB cluster parameter group created |
+| [db\_cluster\_secretsmanager\_secret\_rotation\_enabled](#output\_db\_cluster\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret |
| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the DB parameter group created |
| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The ID of the DB parameter group created |
| [db\_subnet\_group\_name](#output\_db\_subnet\_group\_name) | The db subnet group name |
diff --git a/examples/multi-az/README.md b/examples/multi-az/README.md
index 61b425b..2d03c4e 100644
--- a/examples/multi-az/README.md
+++ b/examples/multi-az/README.md
@@ -66,6 +66,7 @@ No inputs.
| [db\_cluster\_cloudwatch\_log\_groups](#output\_db\_cluster\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
| [db\_cluster\_parameter\_group\_arn](#output\_db\_cluster\_parameter\_group\_arn) | The ARN of the DB cluster parameter group created |
| [db\_cluster\_parameter\_group\_id](#output\_db\_cluster\_parameter\_group\_id) | The ID of the DB cluster parameter group created |
+| [db\_cluster\_secretsmanager\_secret\_rotation\_enabled](#output\_db\_cluster\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret |
| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the DB parameter group created |
| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The ID of the DB parameter group created |
| [db\_subnet\_group\_name](#output\_db\_subnet\_group\_name) | The db subnet group name |
diff --git a/examples/multi-az/main.tf b/examples/multi-az/main.tf
index 7a1201c..35c58a1 100644
--- a/examples/multi-az/main.tf
+++ b/examples/multi-az/main.tf
@@ -33,6 +33,9 @@ module "aurora" {
vpc_id = module.vpc.vpc_id
db_subnet_group_name = module.vpc.database_subnet_group_name
+ manage_master_user_password_rotation = true
+ master_user_password_rotation_automatically_after_days = 30
+
enabled_cloudwatch_logs_exports = ["postgresql"]
# Multi-AZ
diff --git a/examples/multi-az/outputs.tf b/examples/multi-az/outputs.tf
index 2deaba7..bc39159 100644
--- a/examples/multi-az/outputs.tf
+++ b/examples/multi-az/outputs.tf
@@ -157,3 +157,12 @@ output "db_cluster_cloudwatch_log_groups" {
description = "Map of CloudWatch log groups created and their attributes"
value = module.aurora.db_cluster_cloudwatch_log_groups
}
+
+################################################################################
+# Managed Secret Rotation
+################################################################################
+
+output "db_cluster_secretsmanager_secret_rotation_enabled" {
+ description = "Specifies whether automatic rotation is enabled for the secret"
+ value = module.aurora.db_cluster_secretsmanager_secret_rotation_enabled
+}
diff --git a/examples/mysql/README.md b/examples/mysql/README.md
index 7738de8..b081cf0 100644
--- a/examples/mysql/README.md
+++ b/examples/mysql/README.md
@@ -69,6 +69,7 @@ No inputs.
| [db\_cluster\_cloudwatch\_log\_groups](#output\_db\_cluster\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
| [db\_cluster\_parameter\_group\_arn](#output\_db\_cluster\_parameter\_group\_arn) | The ARN of the DB cluster parameter group created |
| [db\_cluster\_parameter\_group\_id](#output\_db\_cluster\_parameter\_group\_id) | The ID of the DB cluster parameter group created |
+| [db\_cluster\_secretsmanager\_secret\_rotation\_enabled](#output\_db\_cluster\_secretsmanager\_secret\_rotation\_enabled) | Specifies whether automatic rotation is enabled for the secret |
| [db\_parameter\_group\_arn](#output\_db\_parameter\_group\_arn) | The ARN of the DB parameter group created |
| [db\_parameter\_group\_id](#output\_db\_parameter\_group\_id) | The ID of the DB parameter group created |
| [db\_subnet\_group\_name](#output\_db\_subnet\_group\_name) | The db subnet group name |
diff --git a/examples/mysql/main.tf b/examples/mysql/main.tf
index 45c108c..d513874 100644
--- a/examples/mysql/main.tf
+++ b/examples/mysql/main.tf
@@ -151,6 +151,9 @@ module "aurora" {
create_db_cluster_activity_stream = true
db_cluster_activity_stream_kms_key_id = module.kms.key_id
+ manage_master_user_password_rotation = true
+ master_user_password_rotation_schedule_expression = "rate(15 days)"
+
# https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/DBActivityStreams.Overview.html#DBActivityStreams.Overview.sync-mode
db_cluster_activity_stream_mode = "async"
diff --git a/examples/mysql/outputs.tf b/examples/mysql/outputs.tf
index f882e4e..6549f8b 100644
--- a/examples/mysql/outputs.tf
+++ b/examples/mysql/outputs.tf
@@ -166,3 +166,12 @@ output "db_cluster_activity_stream_kinesis_stream_name" {
description = "The name of the Amazon Kinesis data stream to be used for the database activity stream"
value = module.aurora.db_cluster_activity_stream_kinesis_stream_name
}
+
+################################################################################
+# Managed Secret Rotation
+################################################################################
+
+output "db_cluster_secretsmanager_secret_rotation_enabled" {
+ description = "Specifies whether automatic rotation is enabled for the secret"
+ value = module.aurora.db_cluster_secretsmanager_secret_rotation_enabled
+}
diff --git a/main.tf b/main.tf
index 6bb295b..c1b6fda 100644
--- a/main.tf
+++ b/main.tf
@@ -436,3 +436,20 @@ resource "aws_rds_cluster_activity_stream" "this" {
depends_on = [aws_rds_cluster_instance.this]
}
+
+################################################################################
+# Managed Secret Rotation
+################################################################################
+
+resource "aws_secretsmanager_secret_rotation" "this" {
+ count = local.create && var.manage_master_user_password && var.manage_master_user_password_rotation ? 1 : 0
+
+ secret_id = aws_rds_cluster.this[0].master_user_secret[0].secret_arn
+ rotate_immediately = var.master_user_password_rotate_immediately
+
+ rotation_rules {
+ automatically_after_days = var.master_user_password_rotation_automatically_after_days
+ duration = var.master_user_password_rotation_duration
+ schedule_expression = var.master_user_password_rotation_schedule_expression
+ }
+}
diff --git a/outputs.tf b/outputs.tf
index 5a46173..ec24ee8 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -179,3 +179,12 @@ output "db_cluster_activity_stream_kinesis_stream_name" {
description = "The name of the Amazon Kinesis data stream to be used for the database activity stream"
value = try(aws_rds_cluster_activity_stream.this[0].kinesis_stream_name, null)
}
+
+################################################################################
+# Managed Secret Rotation
+################################################################################
+
+output "db_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 ad5746f..68d8cd8 100644
--- a/variables.tf
+++ b/variables.tf
@@ -729,3 +729,37 @@ variable "engine_native_audit_fields_included" {
type = bool
default = false
}
+
+################################################################################
+# Managed Secret Rotation
+################################################################################
+
+variable "manage_master_user_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_user_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_user_password_rotation_automatically_after_days" {
+ 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
+}
+
+variable "master_user_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_user_password_rotation_schedule_expression" {
+ 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
+}