Skip to content

RDS database instances should use a custom administrator username

Default administrator usernames are documented in AWS guides, vendor manuals, and attack toolkits. Using them gives attackers half of the credential pair for free, reducing a brute-force attack to password guessing alone. Automated scanners specifically probe for "admin" and "postgres" against publicly or VPC-exposed endpoints.

A custom username adds obscurity that, combined with strong passwords and secrets rotation, meaningfully raises the cost of unauthorized access. It is a low-effort change at provisioning time but painful to retrofit on production databases that already have application connection strings baked in.

Retrofit consideration

Changing the master username on an existing RDS instance requires recreating the instance. All application connection strings, IAM database authentication bindings, and Secrets Manager entries referencing the old username must be updated simultaneously to avoid downtime.

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  = "nistcsfv11.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  = "pcidssv321.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"]
}

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 = ["general", "slowquery"]
  engine                          = "mysql"
  identifier                      = "pofix-abc123"
  instance_class                  = "db.t3.micro"
  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

For aws_db_instance resources, username must not be a known engine default. Failing values include "admin" (MySQL, MariaDB, Oracle, and some SQL Server configurations) and "postgres" (PostgreSQL). The list of disallowed values is engine-specific, but any non-default value passes. For Aurora clusters (aws_rds_cluster), the same logic applies to master_username. Set username or master_username to a custom string such as "app_dba", or source the value from a variable or Secrets Manager reference. There is no boolean flag to toggle; the control evaluates the literal value of the username attribute against known defaults.

Common pitfalls

  • Engine-specific default lists vary

    The set of disallowed usernames differs by engine. "postgres" fails only for PostgreSQL instances, while "admin" fails for MySQL and MariaDB. If you standardize on a single custom username across engines, verify it does not collide with any engine's reserved words by checking engine documentation.

  • Terraform replace on username change

    username on aws_db_instance is a force-new attribute. Changing it on an existing resource triggers a destroy-and-recreate cycle, which means production data loss if you are not careful. Set lifecycle { prevent_destroy = true } on production instances and plan a blue-green migration rather than an in-place change.

  • Aurora clusters need master_username too

    With aws_rds_cluster and aws_rds_cluster_instance, the master username is set on the cluster resource via master_username, not on the instance. Applying a custom username only at the instance level has no effect because Aurora instances inherit credentials from the cluster.

  • Hardcoded usernames in Terraform state

    Even custom usernames are stored in plaintext in Terraform state. The username itself can be generated with random_string or sourced from an external process, then passed in via a variable marked sensitive = true to limit exposure in logs and plan output. Secrets Manager can store the final value, but username generation must be handled separately before that reference is possible.

Audit evidence

Compliance evaluation output from a custom Config rule or equivalent policy-as-code control, showing all RDS instances as COMPLIANT, is the primary artifact. Supporting evidence includes AWS CLI output from aws rds describe-db-instances showing the MasterUsername field for each instance, confirming none use default values like "admin" or "postgres".

For continuous compliance, an exported report across all accounts and regions is strongest. Screenshots of the RDS console showing the master username column are acceptable for point-in-time audits.

Framework-specific interpretation

NIST Cybersecurity Framework v1.1: PR.AC-1 covers identity and credential management for authorized users and devices. A default master username is a publicly known credential, not a managed one, which undermines the subcategory's intent. Replacing it with a custom value is one of the lowest-effort ways to satisfy the credential management expectation here.

PCI DSS v3.2.1: Requirement 2.1 is unambiguous: change all vendor-supplied defaults before putting a system on the network. An RDS instance provisioned with "admin" or "postgres" as the master username fails this requirement on first inspection.

Tool mappings

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

  • Compliance.tf Control: rds_db_instance_no_default_admin_name

  • AWS Config Managed Rule: RDS_INSTANCE_DEFAULT_ADMIN_CHECK

  • Powerpipe Control: aws_compliance.control.rds_db_instance_no_default_admin_name

  • Prowler Checks: rds_cluster_default_admin, rds_instance_default_admin

  • AWS Security Hub Controls: RDS.24, RDS.25

Last reviewed: 2026-03-09