Encrypt everything at rest and in transit, manage the keys properly with a KMS, and make sure no data store is ever public. Understand envelope encryption and why key access is the real control.
Encryption at rest protects data if the underlying storage is ever exposed; encryption in transit (TLS) protects it on the wire. In the cloud both are one setting away and should be mandatory defaults, enforced by policy so a new bucket or database cannot be created unencrypted. The Applied Cryptography course covers how these primitives work; here you turn them on and enforce them.
# enforce encryption at rest on creation
aws s3api put-bucket-encryption --bucket app-data \
--server-side-encryption-configuration '{
"Rules":[{"ApplyServerSideEncryptionByDefault":
{"SSEAlgorithm":"aws:kms","KMSMasterKeyID":"alias/app-data"}}]}'
# enforce encryption in transit with a bucket policy (deny non-TLS):
# "Condition": { "Bool": { "aws:SecureTransport": "false" } }, "Effect":"Deny"
# make encryption non-optional via org policy / IaC scan, so an
# unencrypted store can't even be created.A Key Management Service (KMS) holds master keys you never see and performs cryptographic operations on request. Cloud storage uses envelope encryption: data is encrypted with a data key, and that data key is itself encrypted by the KMS master key. This means the real access control is "who may call the KMS to decrypt" — an IAM decision — not the ciphertext itself.
Envelope encryption:
plaintext --encrypt with--> DATA KEY --> ciphertext (stored)
DATA KEY --encrypt with--> KMS MASTER KEY --> encrypted data key (stored)
the master key never leaves the KMS (often HSM-backed).
to read data you must call KMS to decrypt the data key ->
so DECRYPT PERMISSION on the KMS key is the true access control.
Consequence: guard the KMS key policy like the crown jewels. Whoever
can decrypt can read the data, regardless of who "owns" the bucket.The KMS key policy decides who can use and who can manage a key — keep these separate so an app that decrypts data cannot also delete the key or grant others access. Enable automatic rotation so keys change on a schedule without re-encrypting data, and scope each key to a purpose so blast radius stays small.
# key usage (decrypt) and key administration are DIFFERENT principals:
{
"Statement": [
{ "Sid": "Admins-manage", "Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123:role/kms-admins" },
"Action": ["kms:Create*","kms:Put*","kms:ScheduleKeyDeletion"],
"Resource": "*" },
{ "Sid": "App-use-only", "Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123:role/app-runtime" },
"Action": ["kms:Decrypt","kms:GenerateDataKey"], "Resource": "*" }
]
}
# and: aws kms enable-key-rotation --key-id <id> (annual auto-rotation)The single most common cloud breach is a storage bucket exposed to the public internet. Turn on account-level public-access blocks so no bucket can be made public even by mistake, and continuously check for any store (buckets, snapshots, databases) that is world-readable. This is the first thing a CSPM scan flags and the first thing you should prevent.
# block ALL public access at the account level (belt and suspenders)
aws s3control put-public-access-block --account-id 123456789012 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# then continuously verify nothing slipped through:
aws s3api get-public-access-block --bucket app-data
# also audit: public EBS/RDS snapshots, public AMIs, world-readable
# databases. "Is anything public that shouldn't be?" is a daily question.