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 +}