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.
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.
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
| Thing | Who controls the key policy | Use here |
|---|---|---|
| AWS owned | AWS — you do not manage it | Default-ish encryption with no audit story |
AWS managed (aws/s3) | AWS in your account | Better than nothing; limited policy control |
| Customer managed (CMK) | You | S3 tc-lab-app-logs, EBS, Secrets/SSM |
| Secrets Manager / SSM SecureString | Wrapped by KMS + resource policy / IAM | DB password for the app role |
KMS key policy and IAM must both allow. User-data bypasses neither — it just leaks.
alias/tc-lab-app
How to choose
| Need | Store / key | Trap |
|---|---|---|
| Audit who decrypted app data | CMK + CloudTrail | AWS-owned key with no policy story |
| DB password with rotation | Secrets Manager | User-data, AMI, or a ticket wiki |
| App config + a few secrets | SSM SecureString + CMK | Plain SSM String for a password |
| S3 / EBS at rest | SSE-KMS with the CMK | “Encrypted” checkbox with a key you cannot name |
Runbook
Side A — key
Create
alias/tc-lab-app. Key policy: admin +AppEC2Roledecrypt/generate. Default-encrypttc-lab-app-logsand the app EBS volume with that key.Side B — secret
Put the DB password in Secrets Manager (or SSM SecureString). Grant the role
secretsmanager:GetSecretValue/ssm:GetParameterpluskms:Decrypt.Side C — prove
Instance retrieves the secret.
describe-instance-attribute --attribute userDatahas no password. CloudTrail showsDecryptfromAppEC2Role, not from a random IAM user.
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
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
| Symptom | First check |
|---|---|
| AccessDenied on GetObject of encrypted object | IAM + key policy, not the bucket ACL |
| “Encryption is on” but no alias | AWS owned / managed — say so honestly |
| Password in launch template | Same leak as user-data |
Knowledge check
Judgment items. One best answer. Reasons send you back to the matching section.
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.