T Techclick ← All lessons
Amazon · AWS security · Lesson 5 of 10

KMS + secrets: use a key you can audit, never user-data

The AMI user-data still has the database password. S3 default encryption is an AWS-owned key nobody in 111122223333 can set a policy on. You need a customer managed key you can audit in CloudTrail, Secrets Manager or SSM SecureString for the secret, and AppEC2Role granted to use both. User-data is not a vault.

Updated 2026-08-18·18 min read·L2 primary·Quiz at end

After this page you can

The ticket

Contractor with ec2:DescribeInstanceAttribute dumped user-data and got the DB password. The S3 bucket is “encrypted” but the key is AWS-owned — you cannot read a useful key policy or restrict who decrypts.

Quick interview answer

AWS owned keys: AWS owns them, used on your behalf, you do not manage or deeply audit them. AWS managed keys (e.g. aws/s3, aws/ebs): live in your account, AWS sets the key policy, you can see use in CloudTrail. Customer managed keys (CMK): you create, you write the key policy, you control rotation/aliases, you can grant AppEC2Role kms:Decrypt. Encrypt S3/EBS with a key you can audit. Secrets Manager is the rotation-first secret store. SSM Parameter Store SecureString is fine for parameters/secrets wrapped with KMS. Never put secrets in EC2 user-data — it is an instance attribute, not a vault.

Hero · envelope, not notepad
KMS customer managed key wrapping a secret instead of user-data
If describe-instance-attribute can print it, it is not a secret store.
Lab data · dummy only

Account 111122223333 · OUs Security / Workloads · hub VPC 10.50.0.0/16 · app VPC 10.51.0.0/16 · SG sg-web · NACL acl-app · bucket tc-lab-app-logs · role AppEC2Role · TGW tgw-lab · VGW public 203.0.113.60 · on-prem FortiGate WAN 203.0.113.10. Dummy account ID. Not yours.

Mental model

ThingWho controls the key policyUse here
AWS ownedAWS — you do not manage itDefault-ish encryption with no audit story
AWS managed (aws/s3)AWS in your accountBetter than nothing; limited policy control
Customer managed (CMK)YouS3 tc-lab-app-logs, EBS, Secrets/SSM
Secrets Manager / SSM SecureStringWrapped by KMS + resource policy / IAMDB password for the app role
Who can decrypt
CMK key policy AppEC2Role Secret / EBS / S3 user-data ✕

KMS key policy and IAM must both allow. User-data bypasses neither — it just leaks.

https://111122223333.signin.aws.amazon.com/console
Training mock · not live
VPCIAMS3CloudTrail
KMS → Customer managed keys

alias/tc-lab-app

Customer managed (not AWS owned)
Allow AppEC2Role kms:Decrypt / GenerateDataKey
SSE-KMS · alias/tc-lab-app · tc-lab-app-logs
Secrets Manager / SSM SecureString — not user-data
Enable where the engine supports it (Secrets Manager)
Encrypt with a key you can audit. Training mock.

How to choose

NeedStore / keyTrap
Audit who decrypted app dataCMK + CloudTrailAWS-owned key with no policy story
DB password with rotationSecrets ManagerUser-data, AMI, or a ticket wiki
App config + a few secretsSSM SecureString + CMKPlain SSM String for a password
S3 / EBS at restSSE-KMS with the CMK“Encrypted” checkbox with a key you cannot name

Runbook

  1. Side A — key

    Create alias/tc-lab-app. Key policy: admin + AppEC2Role decrypt/generate. Default-encrypt tc-lab-app-logs and the app EBS volume with that key.

  2. Side B — secret

    Put the DB password in Secrets Manager (or SSM SecureString). Grant the role secretsmanager:GetSecretValue / ssm:GetParameter plus kms:Decrypt.

  3. Side C — prove

    Instance retrieves the secret. describe-instance-attribute --attribute userData has no password. CloudTrail shows Decrypt from AppEC2Role, not from a random IAM user.

aws cli · KMS + secret
aws kms list-aliases --query "Aliases[?AliasName=='alias/tc-lab-app']"

aws s3api get-bucket-encryption --bucket tc-lab-app-logs
# SSEAlgorithm: aws:kms   KMSMasterKeyID: alias/tc-lab-app

aws secretsmanager get-secret-value --secret-id tc-lab/app/db --query Name
# (from AppEC2Role — not from your laptop as root)

aws ec2 describe-instance-attribute --instance-id i-labapp01 --attribute userData
# UserData empty of secrets — that is the proof

Four failures

1 · Secret in user-data

Readable as an instance attribute. Rotate the secret, then delete it from user-data.

2 · CMK without a grant to the role

S3/EBS/Secrets say AccessDenied on decrypt. Fix the key policy + IAM, do not switch back to plaintext.

3 · AWS-owned key called “we encrypt”

You cannot put a least-privilege key policy on it. Use a CMK when you must audit and restrict.

4 · Key in account A, role in account B, no grant

Cross-account KMS needs key policy for the other account. Silent AccessDenied is usually this.

How to prove it

Close only when

1) Bucket and volume name a CMK. 2) The role can decrypt; random users cannot. 3) User-data has no secret. 4) You can show a CloudTrail Decrypt event for the role.

Traps

SymptomFirst check
AccessDenied on GetObject of encrypted objectIAM + key policy, not the bucket ACL
“Encryption is on” but no aliasAWS owned / managed — say so honestly
Password in launch templateSame leak as user-data

Knowledge check

Judgment items. One best answer. Reasons send you back to the matching section.

Q1

Which key type lets you write the key policy and audit use in this account?

Correct: b. CMK.
Q2

Where must the DB password not live?

Correct: c. User-data is not a vault.
Q3

AppEC2Role gets AccessDenied on an SSE-KMS object. Often missing…

Correct: a. KMS + IAM both.
Q4

AWS-owned key is a weak interview answer for “can you audit decrypt?” because…

Correct: a. AWS owned.
Q5

Secrets Manager vs putting a password in the AMI?

Correct: b. Snapshots persist.
Q6

Default-encrypt tc-lab-app-logs with…

Correct: a. CMK on the bucket.

AWS security class series: Shared + IAM · Org / OU · SG vs NACL · Role not key · KMS + Secrets · VPC endpoint · Trail + GuardDuty · TGW hub · S3 public · Interview

Sources

Related: AWS session factory · Migration series.