Skip to content

Secure your Amazon RDS & Aurora admin passwords with AWS Secrets Manager

12 minute read
Content level: Advanced
0

Many production Amazon RDS and Aurora databases still use self-managed master passwords stored in templates, variables, or pipeline secrets, creating exposure and rotation risks. This article is a comprehensive, single-reference guide to migrating existing databases to the native RDS-managed Secrets Manager integration. It covers discovery, enablement via Console, CLI, CloudFormation, CDK, and Terraform, application credential retrieval, IAM guardrails, and a phased production rollout strategy.

Contributors: Rohit Panjala (AWS Security Specialist, Vinod Santhanam (AWS Sr. TPM)

Secure your Amazon RDS & Aurora admin passwords with AWS Secrets Manager

Console, CLI, CloudFormation, CDK & Terraform

Applies to: Amazon RDS (MySQL, PostgreSQL, MariaDB, Oracle, SQL Server), Aurora clusters, and RDS Multi-AZ DB clusters.

If you still set a MasterUserPassword by hand (in a CloudFormation parameter, a Terraform variable, a CI/CD secret, or a console field), this article shows you how to hand that responsibility to RDS + AWS Secrets Manager. RDS will generate the password, store it as an encrypted managed secret, and rotate it automatically. You never see or manage the password again.

Table of Contents

  1. Discover whether this applies to you
  2. Why migrate?
  3. Enable on an existing instance, no downtime, no reboot
  4. CloudFormation
  5. AWS CDK (v2)
  6. Terraform (AWS provider)
  7. Retrieve credentials in your application
  8. IAM permissions to enable the feature
  9. Key facts
  10. Limitations & where it is NOT supported
  11. Safe production rollout
  12. Next steps checklist

1. Discover whether this applies to you

Run these checks first to find RDS resources that are not yet using managed master credentials.

Find instances where you are managing the password yourself (no MasterUserSecret block in the output means it is self-managed):

# Instances WITHOUT a managed master secret
aws rds describe-db-instances \
  --query "DBInstances[?MasterUserSecret==\`null\`].[DBInstanceIdentifier,Engine,EngineVersion]" \
  --output table

# Aurora / Multi-AZ DB clusters WITHOUT a managed master secret
aws rds describe-db-clusters \
  --query "DBClusters[?MasterUserSecret==\`null\`].[DBClusterIdentifier,Engine,EngineVersion]" \
  --output table

Find hardcoded passwords in your infrastructure-as-code:

# CloudFormation / CDK synth output / Terraform: anything with a literal master password
grep -rniE "master_?user_?password|MasterUserPassword" . \
  --include=*.yaml --include=*.yml --include=*.json --include=*.tf --include=*.ts --include=*.py

If any instance/cluster shows up in the first commands, or you find a literal password in the second, you have a use case for this migration.


2. Why migrate?

Before (self-managed)After (RDS-managed in Secrets Manager)
Password in template / parameter / tfvars / pipeline secretNo password in code or state, ever
Manual rotation (or none)Auto-rotated every 7 days by default (configurable)
Risk of credential leak in version controlSecret never exposed in plaintext; retrieved via API at runtime
Rotation needs an app downtime planRotation managed by RDS, no app changes
"Who read the password?" with no answerEvery secret access logged in CloudTrail
You hold the KMS key storyEncrypted with aws/secretsmanager or your own customer-managed key (CMK)

📖 Blog: Improve security of Amazon RDS master database credentials using AWS Secrets Manager


3. Enable on an existing instance, no downtime, no reboot

Console

  1. RDS → Databases → select your instance/cluster → Modify
  2. Under Settings, select Manage master credentials in AWS Secrets Manager
  3. (Optional) Choose a customer-managed KMS key; otherwise aws/secretsmanager is used
  4. Continue → Apply immediately (or schedule for the maintenance window)

AWS CLI

# DB instance
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --manage-master-user-password \
  --apply-immediately
  # optional: --master-user-secret-kms-key-id <kms-key-id-or-arn>

# Aurora / Multi-AZ DB cluster
aws rds modify-db-cluster \
  --db-cluster-identifier my-aurora-cluster \
  --manage-master-user-password \
  --apply-immediately

⚠️ One-way KMS choice: once RDS is managing the secret, you cannot change the KMS key used to encrypt it. Pick your CMK now if you need one.

Find the secret ARN afterward:

aws rds describe-db-instances --db-instance-identifier my-database \
  --query "DBInstances[0].MasterUserSecret"
{
  "SecretArn": "arn:aws:secretsmanager:eu-central-1:111122223333:secret:rds!db-abcdef12-3456-7890",
  "SecretStatus": "active",
  "KmsKeyId": "arn:aws:kms:eu-central-1:111122223333:key/0987dcba-..."
}

Rotate immediately (optional; don't wait for the 7-day cycle):

aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --rotate-master-user-password \
  --apply-immediately

4. CloudFormation

Replace the hardcoded MasterUserPassword with ManageMasterUserPassword: true. These two properties are mutually exclusive; you cannot specify both.

Before (insecure):

Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: my-database
      Engine: mysql
      MasterUsername: admin
      MasterUserPassword: !Ref DatabasePassword   # ❌ password in template/parameters
      DBInstanceClass: db.t3.medium
      AllocatedStorage: 20

After (secure):

Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: my-database
      Engine: mysql
      MasterUsername: admin
      ManageMasterUserPassword: true               # ✅ RDS manages + rotates
      MasterUserSecretKmsKeyId: !Ref MyKmsKey      # optional CMK; omit for aws/secretsmanager
      DBInstanceClass: db.t3.medium
      AllocatedStorage: 20

Outputs:
  DBSecretArn:
    Value: !GetAtt MyDatabase.MasterUserSecret.SecretArn   # hand this ARN to your app's IAM policy

Aurora cluster (identical property on AWS::RDS::DBCluster):


Resources:
  MyCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: my-aurora-cluster
      Engine: aurora-mysql
      MasterUsername: admin
      ManageMasterUserPassword: true               # ✅ same property


5. AWS CDK (v2)

Heads-up: the L2 DatabaseInstance / DatabaseCluster constructs do not yet expose a native manageMasterUserPassword prop. Credentials.fromGeneratedSecret() creates a CDK-managed secret, not the RDS-managed, auto-rotating secret. To get the true RDS-managed feature, use an escape hatch onto the underlying L1 resource.

TypeScript:

import * as rds from 'aws-cdk-lib/aws-rds';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

const instance = new rds.DatabaseInstance(this, 'Instance', {
  engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_39 }),
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM),
  vpc,
  // do NOT pass `credentials` with a password
});

// Escape hatch: enable RDS-managed master password
const cfnInstance = instance.node.defaultChild as rds.CfnDBInstance;
cfnInstance.manageMasterUserPassword = true;
// optional CMK:
// cfnInstance.masterUserSecretKmsKeyId = myKey.keyArn;

Python:

from aws_cdk import aws_rds as rds, aws_ec2 as ec2

instance = rds.DatabaseInstance(self, "Instance",
    engine=rds.DatabaseInstanceEngine.mysql(version=rds.MysqlEngineVersion.VER_8_0_39),
    instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM),
    vpc=vpc,
)

cfn_instance = instance.node.default_child
cfn_instance.manage_master_user_password = True
# cfn_instance.master_user_secret_kms_key_id = my_key.key_arn  # optional CMK

For an Aurora cluster, cast defaultChild to rds.CfnDBCluster and set the same properties.


6. Terraform (AWS provider)

Set manage_master_user_password = true and remove password (they conflict). The generated secret ARN is exposed on the master_user_secret attribute.

aws_db_instance:

resource "aws_db_instance" "my_database" {
  identifier        = "my-database"
  engine            = "mysql"
  instance_class    = "db.t3.medium"
  allocated_storage = 20
  username          = "admin"

  manage_master_user_password   = true               # ✅ RDS manages + rotates
  master_user_secret_kms_key_id = aws_kms_key.rds.arn # optional CMK; omit for aws/secretsmanager
  # password = "..."  # ❌ remove; conflicts with manage_master_user_password
}

output "db_secret_arn" {
  value = aws_db_instance.my_database.master_user_secret[0].secret_arn
}

aws_rds_cluster (Aurora / Multi-AZ DB cluster):

resource "aws_rds_cluster" "my_cluster" {
  cluster_identifier = "my-aurora-cluster"
  engine             = "aurora-mysql"
  master_username    = "admin"

  manage_master_user_password   = true
  master_user_secret_kms_key_id = aws_kms_key.rds.arn  # optional
}

7. Retrieve credentials in your application

The RDS-managed secret stores a JSON document with username and password. Resolve the secret ARN from the DB description, then fetch the value:

import boto3, json

rds_client = boto3.client('rds')
secrets_client = boto3.client('secretsmanager')

desc = rds_client.describe_db_instances(DBInstanceIdentifier='my-database')
secret_arn = desc['DBInstances'][0]['MasterUserSecret']['SecretArn']

value = secrets_client.get_secret_value(SecretId=secret_arn)
creds = json.loads(value['SecretString'])
username = creds['username']
password = creds['password']
# combine with the DB endpoint from describe_db_instances for your connection string

Grant your application's IAM role secretsmanager:GetSecretValue on that secret ARN (and kms:Decrypt on the CMK if you used one).


8. IAM permissions to enable the feature

The principal performing the create/modify needs:

  • secretsmanager:CreateSecret, secretsmanager:TagResource
  • kms:DescribeKey
  • If using a CMK: kms:Decrypt, kms:GenerateDataKey, kms:CreateGrant
  • To rotate on demand: secretsmanager:RotateSecret

Enforce it org-wide with an IAM condition key so no one can create/restore a DB with a self-managed password:

{
  "Effect": "Deny",
  "Action": ["rds:CreateDBInstance", "rds:CreateDBCluster",
             "rds:RestoreDBInstanceFromS3", "rds:RestoreDBClusterFromS3"],
  "Resource": "*",
  "Condition": { "Bool": { "rds:ManageMasterUserPassword": "false" } }
}

9. Key facts

ItemDetail
Rotation frequencyEvery 7 days by default (configurable); rotated password is 28 chars
Downtime to enableNone; no reboot
Supported enginesMySQL, PostgreSQL, MariaDB, Oracle (including CDB tenant databases), SQL Server
Aurora & Multi-AZ DB clusters✅ Supported
KMS encryptionaws/secretsmanager default, or your own CMK (cannot be changed later)
MasterUserPassword + ManageMasterUserPassword❌ Mutually exclusive
CostStandard Secrets Manager pricing (~$0.40 / secret / month + API calls)
Region/version availabilityVaries; see Supported Regions and DB engines for the Secrets Manager integration with Amazon RDS

10. Limitations & where it is NOT supported

Read this before you roll out; these are the cases that generate surprises.

Not supported with these features (per the RDS User Guide):

  • Read replicas: creating a read replica when the source DB/cluster manages credentials in Secrets Manager (applies to all engines except RDS for SQL Server). If you rely on read replicas, plan for this.
  • Amazon RDS Blue/Green Deployments
  • Amazon RDS Custom (Custom for Oracle / SQL Server)
  • Oracle Data Guard switchover: note that RDS for Oracle itself is supported (it has the broadest coverage, including CDB tenant databases); only the Data Guard switchover operation is excluded.

Behavioral limitations to design around:

  • KMS key is permanent. Once RDS manages the secret, you cannot change the KMS key that encrypts it. Choose your CMK up front if you need key separation, cross-account access, or a specific key policy.
  • Enabling generates a new password. When you turn this on for an existing instance, RDS creates a brand-new master password in the secret; the old password you were using stops working. Anything still hardcoded to the old value will fail to authenticate. (No reboot or dropped connections, but new connections must use the new credential.)
  • Region/engine-version gaps. Availability varies by engine version and Region. Verify against Supported Regions and DB engines before assuming a given combo works.
  • impaired secret status. If RDS later loses access to the secret or its KMS key (for example, due to a key-policy change), the secret goes impaired; it still serves the current credential but stops rotating. Monitor for this (see rollout below).
  • Cross-account KMS requires the key ARN or alias ARN (not a bare key ID/alias name).
  • IAM database authentication is a separate feature. This integration manages the password; it does not replace IAM auth. You can use both.

11. Safe production rollout

A staged approach that avoids the two real failure modes: stale credentials and broken rotation.

Phase 0: Pre-flight (non-prod first)

  1. Confirm the engine version + Region support the feature (table above).
  2. Confirm the target is not using an unsupported feature: not RDS Custom, not mid-Blue/Green, and you don't depend on creating read replicas from it (except SQL Server).
  3. Reproduce the change on a test/staging instance end-to-end before touching prod.
  4. Decide your KMS story now (default aws/secretsmanager vs CMK); it's irreversible.

Phase 1: Prepare the consumers (do this BEFORE enabling)

  1. Make every application/job read the password from Secrets Manager at runtime (Section 7), while still on the old password. Grant the app role secretsmanager:GetSecretValue (+ kms:Decrypt if CMK). This way, the moment RDS swaps the password, apps follow automatically.
  2. Inventory and remove any remaining hardcoded copies of the old master password (pipeline variables, .env, parameter store, runbooks). Enabling the feature invalidates the old password.

Phase 2: Enable

  1. Enable during a low-traffic window. No reboot occurs, existing connections aren't dropped, but new logins need the new credential; any app still holding the old password will start failing on reconnect.
  2. Use --apply-immediately if you want it now; otherwise it lands in the maintenance window. Roll out one instance at a time, not the whole fleet at once.

Phase 3: Verify

  1. Confirm MasterUserSecret.SecretStatus is active:

    aws rds describe-db-instances --db-instance-identifier my-database \
      --query "DBInstances[0].MasterUserSecret.[SecretStatus,SecretArn]"
    
  2. Have an app actually fetch the secret and open a fresh DB connection. Verify auth succeeds.

  3. Optionally force a rotation to prove rotation works before you trust the 7-day cycle:

    aws rds modify-db-instance --db-instance-identifier my-database \
      --rotate-master-user-password --apply-immediately
    

Phase 4: Guardrails & monitoring

  1. Add a CloudWatch alarm / EventBridge rule for SecretStatus = impaired so you catch broken rotation (usually a KMS key-policy regression) before it becomes an incident.
  2. Apply the IAM condition-key Deny (Section 8) so new databases can't be created with self-managed passwords.
  3. Keep CloudTrail on Secrets Manager GetSecretValue for audit.

Rollback: if needed, you can run modify-db-instance --no-manage-master-user-password --master-user-password <new> to return to a self-managed password, but this re-introduces the manual-management risk and is a one-way step back. Prefer fixing forward (for example, correcting a KMS policy) over rolling back.


12. Next steps checklist

  • [ ] ✅ Run the discovery commands in Section 1 to list self-managed instances and find hardcoded passwords.
  • [ ] ✅ Enable ManageMasterUserPassword on each existing instance/cluster (Console or CLI; no downtime).
  • [ ] ✅ Update CloudFormation / CDK / Terraform to remove hardcoded passwords and enable the managed feature for all new deployments.
  • [ ] ✅ Update application IAM roles to read the secret (secretsmanager:GetSecretValue, plus kms:Decrypt for a CMK).
  • [ ] ✅ (Optional) Add the IAM condition-key guardrail so future databases can't be created with self-managed passwords.

Documentation