compliance.tf

RDS for SQL Server DB instances should publish logs to CloudWatch Logs

SQL Server database logs contain error messages, agent job history, and optionally SQL Server Audit records. Without CloudWatch Logs integration, these logs exist only on the RDS instance's local storage, where they rotate and eventually disappear. You lose the ability to set CloudWatch alarms on error patterns, run Logs Insights queries across your fleet, or retain logs beyond the instance's lifecycle.

Forwarding logs to CloudWatch decouples log retention from instance operations. If someone deletes or resizes an instance, the logs survive independently. That matters during incident investigations where you need weeks or months of history.

Retrofit consideration

Enabling CloudWatch Logs exports on an existing RDS SQL Server instance triggers a modification that may cause a brief log delivery delay but does not require a reboot. If the instance lacks the required IAM service-linked role permissions or the target CloudWatch log group does not exist, the modification can fail silently.

Implementation

Choose the approach that matches how you manage Terraform.

Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.

module "rds" {
  source  = "soc2.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "hipaa.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "iso27001.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "gdpr.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "nis2.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "nist80053.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "fedrampmoderate.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "nist800171.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "awswellarchitected.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "cisacyberessentials.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "ffiec.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "cfrpart11.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "rbicybersecurity.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "fedramplow.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
  source  = "nist80053rev4.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

This control is enforced automatically with Compliance.tf modules. Start free trial

If you use terraform-aws-modules/rds/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.

module "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

Use AWS provider resources directly. See docs for the resources involved: aws_db_instance.

resource "aws_db_instance" "this" {
  allocated_storage               = 20
  enabled_cloudwatch_logs_exports = ["agent", "error"]
  engine                          = "sqlserver-ex"
  identifier                      = "pofix-abc123"
  instance_class                  = "db.t3.small"
  license_model                   = "license-included"
  monitoring_interval             = 60
  monitoring_role_arn             = "arn:aws:iam::123456789012:role/example-role"
  password                        = "ChangeMe123!"
  skip_final_snapshot             = true
  username                        = "dbadmin"
}

What this control checks

This control validates that the aws_db_instance resource for a SQL Server engine family has enabled_cloudwatch_logs_exports set with at least one valid log type. For SQL Server engines (sqlserver-ee, sqlserver-se, sqlserver-ex, sqlserver-web), the accepted log types are "error" and "agent". An instance passes when enabled_cloudwatch_logs_exports contains one or both values. It fails when the argument is omitted entirely or set to an empty list. The control may optionally require specific log types depending on the parameter configuration. No additional IAM or CloudWatch resources need to be declared in Terraform; RDS automatically creates the /aws/rds/instance/<instance-id>/<log-type> log groups.

Common pitfalls

Empty list still fails

Terraform treats enabled_cloudwatch_logs_exports = [] the same as omitting the argument entirely. No log types are sent to the API, and the control reports a failure. You must include at least one valid value, for example ["error"].

SQL Server Audit logs require additional setup

If your organization needs SQL Server Audit, configure it through a custom option group with the SQLSERVER_AUDIT option (via aws_db_option_group) and the relevant SQL Server settings. Don't add "audit" to enabled_cloudwatch_logs_exports for RDS SQL Server. The only valid CloudWatch export types are "error" and "agent".

CloudWatch log group retention defaults to never expire

RDS auto-creates CloudWatch log groups with no expiration policy. Left alone, logs accumulate indefinitely and CloudWatch Logs storage costs grow with them. Pre-create the log groups in Terraform using aws_cloudwatch_log_group with retention_in_days set before enabling exports.

Engine filter matters

Get the engine check wrong in a conditional module and this control silently misapplies. All four SQL Server variants must be handled explicitly: sqlserver-ee, sqlserver-se, sqlserver-ex, and sqlserver-web. A prefix match on sqlserver alone can collide with future engine names and produce unexpected behavior.

Audit evidence

Auditors expect AWS Config rule evaluation results showing each SQL Server RDS instance as COMPLIANT. Supporting evidence includes the RDS instance detail page in the AWS Console (under the "Logs & events" or "Configuration" tab) showing the published log types, or the output of aws rds describe-db-instances where the EnabledCloudwatchLogsExports array contains "error" and/or "agent".

CloudWatch Logs console screenshots showing active log groups under /aws/rds/instance/ with recent log streams confirm log delivery is working. Retention policy settings on those log groups show logs are preserved for the required period.

Framework-specific interpretation

SOC 2: CC7.2 says you need to monitor system components for anomalies. SQL Server error and agent logs in CloudWatch give you the raw material for that monitoring, and the log group becomes evidence that the capability exists when your auditor walks through the CC7 criteria.

PCI DSS v4.0: Requirement 10.2 specifies which events must be logged for all system components in the cardholder data environment; Requirement 10.3 requires those logs be protected from destruction. Exporting SQL Server logs to CloudWatch separates log storage from the instance itself, satisfying the protection requirement independently of what happens to the database.

HIPAA Omnibus Rule 2013: Under 45 CFR 164.312(b), systems that store or process ePHI must implement audit controls and retain activity logs for review. CloudWatch Logs gives you durable, access-controlled storage for database logs, which is what investigators ask to see when an incident involves a regulated database.

ISO/IEC 27001:2022: CloudWatch Logs provides the centralized, retained, and access-controlled log storage that A.8.15 (Logging) and A.8.16 (Monitoring activities) ask for. Pointing RDS SQL Server at CloudWatch covers both Annex A controls with a single configuration.

GDPR: Articles 5(2) and 32 together require controllers to demonstrate accountability and implement technical measures to detect unauthorized access to personal data. Database-level logging exported to CloudWatch provides an evidence trail for breach detection and post-incident forensics, which is one way to satisfy the technical security obligations under Article 32.

NIS2 Directive (EU 2022/2555): Article 21 calls for appropriate technical measures for incident handling and detection. Database logs centralized in CloudWatch cover both.

NIST SP 800-53 Rev 5: AU-2, AU-3, and AU-6 collectively cover event logging, the required content of audit records, and ongoing review. Exporting SQL Server logs to CloudWatch can support centralized collection and correlation within AWS, though meeting AU-3 fully also depends on what the logs themselves contain.

FedRAMP Moderate Baseline Rev 4: AU-2 and AU-6 require that systems at the Moderate baseline generate audit records for defined events and make those records available for centralized review. Publishing SQL Server error and agent logs to CloudWatch satisfies the aggregation side of both controls.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: rds_db_instance_sql_server_logging_enabled
  • AWS Config Managed Rule: RDS_SQL_SERVER_LOGS_TO_CLOUDWATCH
  • Checkov Check: CKV_AWS_129
  • Powerpipe Control: aws_compliance.control.rds_db_instance_sql_server_logging_enabled
  • Prowler Check: rds_instance_integration_cloudwatch_logs
  • AWS Security Hub Controls: RDS.40, RDS.9
  • KICS Query: 8d7f7b8c-6c7c-40f8-baa6-62006c6c7b56

Last reviewed: 2026-03-09

On this page

Ask AI about this

Help improve this page