The compliance review asks “are your databases encrypted at rest?” and the answer is yes — RDS turns it on by default with the AWS-managed key aws/rds. The follow-up is “with a customer-managed key?” and the answer is no, because the original instance was created two years ago when nobody was thinking about KMS key custody. The next question is “can we move the existing data to a customer-managed key?” and the honest answer is “not in place.” Encryption status on RDS is set at instance creation and cannot be changed — there is no modify-db-instance that toggles it. The workaround is a snapshot, copy with a new KMS key, and restore. The right answer is to use a customer-managed key from the start.
What’s happening
RDS and Aurora encrypt the storage volume, automated backups, snapshots, and read replicas with a KMS key chosen at creation time. Three things to understand:
The encryption flag is immutable. StorageEncrypted: false cannot become true on a running instance. Same the other way — an encrypted instance cannot be turned unencrypted. The only path to change encryption status is snapshot → copy-with-different-key → restore-from-copy. This is a multi-hour operation for large databases and involves cutover downtime.
The KMS key is bound to the instance. Each encrypted RDS instance has a single KMS key associated. Read replicas, automated backups, and snapshots inherit it (with a wrinkle — see cross-region below). You can change the key by going through the same snapshot → copy → restore path, but not in-place.
KMS key rotation is automatic, but it does not re-encrypt data. When AWS-managed or customer-managed key auto-rotation is enabled, KMS generates a new key version every year. The new version is used for new encryptions. Existing ciphertext stays encrypted with the older version, which KMS keeps around to decrypt it. “Rotating the key” does not mean “all the data is re-encrypted with the new version.” That distinction matters for compliance frameworks that ask for evidence of re-keying.
The choice of customer-managed key (CMK) versus AWS-managed key (aws/rds) matters for three operational reasons:
- Audit trail. CloudTrail records every
EncryptandDecryptcall against your CMK. The AWS-managed key calls also appear, but they are noisier and harder to filter. - Revocability. A CMK can be disabled or scheduled for deletion. Disabling the key makes the database unable to perform IO — the instance stops working. This is desirable in some incident-response scenarios (the legal team asks for “freeze the data”) and disastrous in others (someone accidentally disables the key). The AWS-managed key cannot be disabled by you.
- Cross-account sharing. Sharing a snapshot to another AWS account requires a CMK with a key policy granting the other account’s access. The AWS-managed key cannot be shared across accounts — the snapshot stays in the source account.
CMKs cost a small monthly fee per key plus per-use API charges. The cost is negligible for production workloads; do not let it drive the decision.
The cross-region story has its own quirk. A snapshot encrypted with a source-region CMK cannot be copied to a different region as-is. The copy operation requires you to specify a destination-region CMK, and KMS re-encrypts the data envelope during the copy. The destination CMK can be a CMK in the destination region that grants the source account access (typical), or a multi-region CMK (newer AWS feature, the same logical key with replicas in multiple regions).
How to verify
Check the current encryption status:
aws rds describe-db-instances \
--db-instance-identifier prod-db-1 \
--query 'DBInstances[].[DBInstanceIdentifier,StorageEncrypted,KmsKeyId]' \
--output table
If StorageEncrypted is false, the instance is not encrypted at rest. There is no in-place fix — see the migration plan below.
If StorageEncrypted is true and KmsKeyId ends in :key/aws/rds (or shows the alias alias/aws/rds), it is using the AWS-managed key. To check the key:
aws kms describe-key --key-id <KmsKeyId> \
--query 'KeyMetadata.[KeyManager,Description,KeyState,Origin]' \
--output table
KeyManager is AWS for AWS-managed and CUSTOMER for customer-managed.
Check the key’s rotation status:
aws kms get-key-rotation-status --key-id <KmsKeyId>
KeyRotationEnabled: true means annual auto-rotation is on. If the value is false, the key has not rotated automatically since creation. Enable it unless you have a specific reason not to:
aws kms enable-key-rotation --key-id <KmsKeyId>
For cross-account snapshot sharing, look at the key policy:
aws kms get-key-policy --key-id <KmsKeyId> --policy-name default \
--query 'Policy' --output text | jq .
A key policy that allows another account to use the key has a statement like:
{
"Sid": "AllowExternalAccountToUseKey",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::OTHER-ACCOUNT-ID:root"},
"Action": [
"kms:Decrypt",
"kms:CreateGrant",
"kms:DescribeKey"
],
"Resource": "*"
}
Without that statement, snapshot sharing to the other account fails with a vague “access denied” error.
The fix
Three scenarios, each with its own runbook.
Scenario 1: New instance with CMK from day one
The right path. Create the CMK first:
aws kms create-key \
--description "RDS encryption key for prod-db-1" \
--tags TagKey=Project,TagValue=production-platform
# Output includes KeyId; save it.
aws kms create-alias \
--alias-name alias/rds-prod-db-1 \
--target-key-id <KeyId>
aws kms enable-key-rotation --key-id <KeyId>
Use a key policy that allows RDS to use the key and your operations role to manage it:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableRootPermissions",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowRDSToUseTheKey",
"Effect": "Allow",
"Principal": {"Service": "rds.amazonaws.com"},
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:Encrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:CreateGrant"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "rds.us-east-1.amazonaws.com",
"kms:CallerAccount": "123456789012"
}
}
}
]
}
The kms:ViaService condition restricts the key to being used by RDS in that region, which prevents cross-service misuse. The kms:CallerAccount condition belt-and-suspenders the account.
Create the database with the CMK:
aws rds create-db-instance \
--db-instance-identifier prod-db-1 \
--engine postgres \
--engine-version 15.5 \
--db-instance-class db.r6g.xlarge \
--master-username postgres \
--master-user-password REDACTED \
--allocated-storage 100 \
--storage-encrypted \
--kms-key-id alias/rds-prod-db-1 \
--vpc-security-group-ids sg-db \
--db-subnet-group-name prod-db-subnets \
--multi-az \
--backup-retention-period 14 \
--no-publicly-accessible
Scenario 2: Migrate existing unencrypted instance to CMK
The instance is StorageEncrypted: false. Direct conversion is not possible. The migration:
# 1. Take a final snapshot of the unencrypted instance.
aws rds create-db-snapshot \
--db-snapshot-identifier prod-db-1-pre-encryption \
--db-instance-identifier prod-db-1
# 2. Copy the snapshot, applying encryption with the new CMK.
aws rds copy-db-snapshot \
--source-db-snapshot-identifier prod-db-1-pre-encryption \
--target-db-snapshot-identifier prod-db-1-encrypted \
--kms-key-id alias/rds-prod-db-1
# 3. Restore the encrypted snapshot to a new instance.
aws rds restore-db-snapshot \
--db-instance-identifier prod-db-1-new \
--db-snapshot-identifier prod-db-1-encrypted \
--db-instance-class db.r6g.xlarge \
--multi-az \
--vpc-security-group-ids sg-db \
--db-subnet-group-name prod-db-subnets
# 4. After verification, cutover the application to the new instance,
# rename the old one out of the way, rename the new one into place.
The cutover requires application downtime — the snapshot is point-in-time-of-snapshot, so any writes after the snapshot need to be replayed (using Postgres logical replication or a brief lock window). For a small database, the whole cutover is 5–15 minutes; for a multi-TB database, a logical replication catch-up window is required.
Scenario 3: Cross-account snapshot share with CMK
The other account needs a copy of the snapshot for DR or compliance purposes. The steps:
# 1. Update the source key policy to allow the destination account.
aws kms put-key-policy \
--key-id alias/rds-prod-db-1 \
--policy-name default \
--policy file://updated-key-policy.json
# Where updated-key-policy.json adds the destination-account statement:
# {
# "Sid": "AllowDRAccount",
# "Effect": "Allow",
# "Principal": {"AWS": "arn:aws:iam::DR-ACCOUNT:root"},
# "Action": ["kms:Decrypt","kms:CreateGrant","kms:DescribeKey"],
# "Resource": "*"
# }
# 2. Share the snapshot.
aws rds modify-db-snapshot-attribute \
--db-snapshot-identifier prod-db-1-encrypted-snap \
--attribute-name restore \
--values-to-add DR-ACCOUNT-ID
# 3. In the DR account, copy the snapshot (re-encrypting with a DR-account CMK).
# This step is run as the DR account.
aws rds copy-db-snapshot \
--source-db-snapshot-identifier arn:aws:rds:us-east-1:SOURCE-ACCOUNT:snapshot:prod-db-1-encrypted-snap \
--target-db-snapshot-identifier prod-db-1-dr-copy \
--kms-key-id alias/rds-dr-key
Three things that bite during encryption work:
The “kms:GrantIsForAWSResource” condition. Some RDS operations require KMS grants. The key policy must allow kms:CreateGrant with the kms:GrantIsForAWSResource: true condition. Without it, automated snapshot copy across regions fails silently.
Replica encryption keys differ. A cross-region read replica or cross-region snapshot uses a destination-region key. The source-region CMK does not work in another region. Build the destination-region CMK first, reference it explicitly.
Disabling a key in use is a real outage. A CMK that an RDS instance depends on, if disabled or scheduled for deletion, causes the instance to lose access to its storage within minutes. The database goes into a state where reads and writes fail. CloudTrail catches the disable event, but recovery requires re-enabling the key. Keep CMK deletion authority tightly scoped — the operations team should not be one click away from killing a database.
The encryption-and-key strategy is one of the few RDS decisions that is genuinely difficult to reverse cheaply. Pick the CMK posture at instance creation, document the key policy, and put guardrails on key deletion. Re-encrypting after the fact is possible but operationally expensive.
Stack Harbor designs and operates production RDS / Aurora topologies as part of Managed AWS and the ongoing Managed Cloud Ops layer. If your database posture is “Multi-AZ checkbox + hope,” start with an infrastructure consult.