From 5aa8a9bb581957225af0e2eab986db43b3d716ac Mon Sep 17 00:00:00 2001 From: sebas_correa Date: Fri, 27 Mar 2026 14:05:46 -0300 Subject: [PATCH] fix(security): use CMK for RDS storage and Secrets Manager encryption Replace AWS-managed key with a Customer Managed KMS Key (CMK) to satisfy security audit finding (Estandar row 87). The CMK includes automatic key rotation, explicit service principal grants for RDS and Secrets Manager, and a dedicated IAM policy in requirements so agents can manage the key. Co-Authored-By: Claude Sonnet 4.6 --- .../rds-postgres-server/deployment/data.tf | 2 + .../rds-postgres-server/deployment/main.tf | 64 +++++++++++++++++++ .../rds-postgres-server/deployment/outputs.tf | 5 ++ .../rds-postgres-server/requirements/main.tf | 41 ++++++++++++ 4 files changed, 112 insertions(+) diff --git a/databases/rds-postgres-server/deployment/data.tf b/databases/rds-postgres-server/deployment/data.tf index 9854479..b66f892 100644 --- a/databases/rds-postgres-server/deployment/data.tf +++ b/databases/rds-postgres-server/deployment/data.tf @@ -1,5 +1,7 @@ # Discover shared RDS infrastructure (tagged by nullplatform during setup) +data "aws_caller_identity" "current" {} + data "aws_vpc" "main" { id = var.vpc_id } diff --git a/databases/rds-postgres-server/deployment/main.tf b/databases/rds-postgres-server/deployment/main.tf index 24f05dc..f66dde8 100644 --- a/databases/rds-postgres-server/deployment/main.tf +++ b/databases/rds-postgres-server/deployment/main.tf @@ -1,3 +1,65 @@ +# --------------------------------------------------------------------------- +# Customer Managed KMS Key for RDS storage and Secrets Manager encryption +# --------------------------------------------------------------------------- + +resource "aws_kms_key" "rds" { + description = "CMK for RDS instance ${var.instance_name} storage and secrets" + deletion_window_in_days = 7 + enable_key_rotation = true + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "RootFullAccess" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + } + Action = "kms:*" + Resource = "*" + }, + { + Sid = "RDSServiceAccess" + Effect = "Allow" + Principal = { + Service = "rds.amazonaws.com" + } + Action = [ + "kms:GenerateDataKey*", + "kms:Decrypt", + "kms:CreateGrant", + "kms:DescribeKey" + ] + Resource = "*" + }, + { + Sid = "SecretsManagerServiceAccess" + Effect = "Allow" + Principal = { + Service = "secretsmanager.amazonaws.com" + } + Action = [ + "kms:GenerateDataKey*", + "kms:Decrypt", + "kms:DescribeKey" + ] + Resource = "*" + } + ] + }) + + tags = { + "managed-by" = "nullplatform" + "service-id" = var.service_id + } +} + +resource "aws_kms_alias" "rds" { + name = "alias/nullplatform/rds/${var.instance_name}" + target_key_id = aws_kms_key.rds.key_id +} + # --------------------------------------------------------------------------- # Security group for RDS (allows PostgreSQL traffic from within the VPC) # --------------------------------------------------------------------------- @@ -39,6 +101,7 @@ resource "random_password" "master" { resource "aws_secretsmanager_secret" "master" { name = "nullplatform/rds/${var.instance_name}/master" recovery_window_in_days = 0 + kms_key_id = aws_kms_key.rds.arn tags = { "managed-by" = "nullplatform" @@ -77,6 +140,7 @@ resource "aws_db_instance" "main" { allocated_storage = var.allocated_storage storage_type = "gp3" storage_encrypted = true + kms_key_id = aws_kms_key.rds.arn db_name = "postgres" username = "master" diff --git a/databases/rds-postgres-server/deployment/outputs.tf b/databases/rds-postgres-server/deployment/outputs.tf index 31bd6ae..f6f0d39 100644 --- a/databases/rds-postgres-server/deployment/outputs.tf +++ b/databases/rds-postgres-server/deployment/outputs.tf @@ -17,3 +17,8 @@ output "master_secret_arn" { value = aws_secretsmanager_secret.master.arn description = "ARN of the Secrets Manager secret for master credentials" } + +output "kms_key_arn" { + value = aws_kms_key.rds.arn + description = "ARN of the CMK used for RDS storage and secrets encryption" +} diff --git a/databases/rds-postgres-server/requirements/main.tf b/databases/rds-postgres-server/requirements/main.tf index c5cb268..13b7839 100644 --- a/databases/rds-postgres-server/requirements/main.tf +++ b/databases/rds-postgres-server/requirements/main.tf @@ -26,6 +26,12 @@ resource "aws_iam_role_policy_attachment" "rds_s3" { policy_arn = aws_iam_policy.nullplatform_rds_s3_policy.arn } +resource "aws_iam_role_policy_attachment" "rds_kms" { + count = var.role_name != null ? 1 : 0 + role = var.role_name + policy_arn = aws_iam_policy.nullplatform_rds_kms_policy.arn +} + ################################################################################ # RDS IAM policy ################################################################################ @@ -135,6 +141,41 @@ resource "aws_iam_policy" "nullplatform_rds_s3_policy" { }) } +################################################################################ +# KMS IAM policy +################################################################################ + +# Grant permissions to create and manage the CMK used for RDS encryption +resource "aws_iam_policy" "nullplatform_rds_kms_policy" { + name = "nullplatform_${var.name}_rds_kms_policy" + description = "Policy for managing the CMK used for RDS storage and secrets encryption" + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Action" : [ + "kms:CreateKey", + "kms:DescribeKey", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListResourceTags", + "kms:PutKeyPolicy", + "kms:EnableKeyRotation", + "kms:ScheduleKeyDeletion", + "kms:CreateAlias", + "kms:DeleteAlias", + "kms:ListAliases", + "kms:TagResource", + "kms:UntagResource" + ], + "Resource" : "*" + } + ] + }) +} + ################################################################################ # Secrets Manager IAM policy ################################################################################