Security best practices

KMS, TLS, WAF/Shield, secrets, detection.

Advanced30 min · lesson 13 of 15

Securing an AWS (Amazon Web Services) workload works a lot like securing an office building. Locks on the filing cabinets: that is encryption at rest. Documents sealed in envelopes before they go in the post: encryption in transit. A doorman who turns away the faces on his troublemaker list: WAF (Web Application Firewall) and Shield. Master keys kept in a safe instead of under the doormat: Secrets Manager. Cameras recording everyone who walks in: CloudTrail and GuardDuty. None of those is enough on its own. A burglar who talks his way past the doorman should still hit locked cabinets, on camera. Stacking layers like that is called defense in depth, and it is the idea sitting underneath every security question on the SAA (Solutions Architect Associate) exam.

IAM (Identity and Access Management) least privilege, which you met earlier in this course, is the floor everything else stands on. What follows is the four families of control the exam stacks on top of it: encryption at rest and in transit, protection at the edge, secrets handling, and detection. For each one you will run the same CLI (command line interface) calls an architect runs on day one of a fresh account.

Encryption at rest: KMS and the envelope trick

AWS KMS (Key Management Service) is the safe. It creates cryptographic keys and keeps them locked inside tamper-resistant hardware called an HSM (hardware security module), and the raw key material never leaves that hardware. So how does KMS encrypt a 200 GB volume? It does not. A direct call to KMS encrypts at most 4 KB. Services get around that with envelope encryption. KMS mints a throwaway *data key*, the service uses that data key to encrypt your object or volume locally, then stores an encrypted copy of the data key right next to the data, like a sealed envelope taped to the parcel. Reading it back runs the same steps in reverse. Send the small encrypted data key to KMS, get the plaintext data key back, decrypt locally.

Every KMS-integrated service hands you an *AWS-managed key* (aws/s3, for example) with no monthly charge attached, but you cannot edit its key policy and you cannot disable it. A *customer-managed key* costs $1 a month and buys you the controls. First and most important, a key policy, which is the primary access control on the key itself. Read that twice, because it is where people lose marks: an IAM policy granting kms:Decrypt does nothing at all unless the key policy also lets that principal in. A customer-managed key also gives you cross-account grants, automatic rotation (yearly by default, now tunable anywhere from 90 to 2,560 days), and the emergency lever of disabling the key, which cuts off access to everything it protects at once.

create a customer-managed key
aws kms create-key --description "orders-app data key" \
--tags TagKey=app,TagValue=orders
{
"KeyMetadata": {
"AWSAccountId": "111122223333",
"KeyId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"Arn": "arn:aws:kms:eu-west-1:111122223333:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"CreationDate": "2026-07-13T08:02:41.113000+00:00",
"Enabled": true,
"Description": "orders-app data key",
"KeyUsage": "ENCRYPT_DECRYPT",
"KeyState": "Enabled",
"Origin": "AWS_KMS",
"KeyManager": "CUSTOMER",
"KeySpec": "SYMMETRIC_DEFAULT",
"EncryptionAlgorithms": [
"SYMMETRIC_DEFAULT"
],
"MultiRegion": false
}
}
# Turn on automatic rotation (no output on success) and verify it
aws kms enable-key-rotation --key-id 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
aws kms get-key-rotation-status --key-id 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
{
"KeyId": "arn:aws:kms:eu-west-1:111122223333:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"KeyRotationEnabled": true,
"NextRotationDate": "2027-07-13T08:03:12.456000+00:00",
"RotationPeriodInDays": 365
}

Now make that key the bucket default, so every new object gets encrypted whether or not the caller remembers to ask. BucketKeyEnabled tells S3 (Simple Storage Service) to reuse one bucket-level key rather than calling KMS for every single object. On a busy bucket that takes up to 99 percent off your KMS request bill.

make the key the bucket default
aws s3api put-bucket-encryption --bucket orders-data-prod \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"BucketKeyEnabled": true
}]}'
# Verify — this is also what auditors will ask you to show
aws s3api get-bucket-encryption --bucket orders-data-prod \
--query 'ServerSideEncryptionConfiguration.Rules[0]'
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"BucketKeyEnabled": true
}

Encryption in transit: certificates from ACM

TLS (Transport Layer Security) is the protocol that encrypts traffic between a client and your endpoint. It is the envelope around data while the data is moving. AWS Certificate Manager (ACM) issues public TLS certificates for free, proves you own the domain by checking for a DNS (Domain Name System) record you add, and renews the certificate on its own for as long as that record stays in place. That kills the classic 3 a.m. outage caused by a human forgetting an expiry date. There is a catch. The free public certificates cannot be exported, so they only work on endpoints AWS terminates for you: an ALB (Application Load Balancer), CloudFront, API Gateway. You cannot install one on an EC2 (Elastic Compute Cloud) instance. (Since mid-2025 ACM can also issue paid *exportable* public certificates for EC2 or on-premises servers, but the exam still tests the classic rule.) One more trap, and it is a favorite: a CloudFront distribution accepts a certificate only if it was issued in us-east-1, no matter which Region your origin lives in.

request a TLS certificate
aws acm request-certificate --domain-name api.example.com \
--validation-method DNS \
--subject-alternative-names "*.api.example.com"
{
"CertificateArn": "arn:aws:acm:eu-west-1:111122223333:certificate/91adc8f4-3f9a-46b2-8d1e-0c7b2a9f4e11"
}
# Create the validation CNAME in Route 53, wait a few minutes, then:
aws acm describe-certificate \
--certificate-arn arn:aws:acm:eu-west-1:111122223333:certificate/91adc8f4-3f9a-46b2-8d1e-0c7b2a9f4e11 \
--query 'Certificate.Status'
"ISSUED"

At the edge: WAF and Shield

AWS WAF (Web Application Firewall) is that doorman with the list. It works at layer 7, the application layer, which means it can read inside an HTTP request and check it against a *web ACL* (access control list), an ordered set of rules. Matches get blocked, counted, or challenged before your application ever sees them. You rarely write those rules yourself. AWS ships *managed rule groups* covering SQL injection, XSS (cross-site scripting), known-bad IP addresses, and bot signatures, and a *rate-based rule* blocks any single IP that sends more than, say, 2,000 requests in five minutes. WAF attaches to CloudFront, ALB, API Gateway, AppSync, Cognito user pools, and App Runner. It does not attach to an NLB (Network Load Balancer) or straight to an EC2 instance, which is exactly the line exam questions like to probe.

AWS Shield Standard is already switched on in your account, costs nothing, and soaks up layer 3 and layer 4 floods (SYN floods, UDP reflection) with no configuration from you. Shield Advanced costs $3,000 a month per organization on a one-year commitment, plus usage fees on protected data transfer. For that you get the Shield Response Team on call, much deeper attack visibility, and the phrase worth memorizing: *cost protection*. When an attack drives your Auto Scaling group and your data transfer through the roof, AWS credits those charges back. Any scenario that mentions a response team, or a refund for attack-driven scaling, is pointing at Shield Advanced.

Secrets: keep them out of code and out of AMIs

A database password baked into an AMI (Amazon Machine Image) or committed to a Git repository is a breach that has not found its audience yet. AWS Secrets Manager stores secrets encrypted under KMS for $0.40 per secret per month, and the feature that defines it is native rotation. A Lambda function runs on a schedule, changes the credential at the source (in the database itself), then updates the stored copy, so the password in the database and the password in the vault never drift apart. SSM Parameter Store (Systems Manager Parameter Store) will hold SecureString values for free, but it has no rotation of its own. The exam shortcut: *automatic rotation* points to Secrets Manager, a free configuration value points to Parameter Store. (Cross-account sharing used to belong to Secrets Manager alone, but since 2024 Parameter Store can share paid advanced-tier parameters through AWS RAM (Resource Access Manager). Rotation is the discriminator you can still trust.)

Your workload pulls the secret at boot, or on demand, using its IAM role. Nothing sensitive ships inside the image, the user-data script, or an environment variable in a template.

store, fetch, rotate
aws secretsmanager create-secret --name prod/orders/db \
--secret-string '{"username":"orders_app","password":"tW9!xK#2mQvL"}'
{
"ARN": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:prod/orders/db-Ab1XyZ",
"Name": "prod/orders/db",
"VersionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
# What the app runs at boot, via its IAM role — no credential in the AMI
aws secretsmanager get-secret-value --secret-id prod/orders/db \
--query SecretString --output text
{"username":"orders_app","password":"tW9!xK#2mQvL"}
# Hand rotation to a Lambda function, every 30 days
# (this call also kicks off an immediate first rotation)
aws secretsmanager rotate-secret --secret-id prod/orders/db \
--rotation-lambda-arn arn:aws:lambda:eu-west-1:111122223333:function:rotate-rds-secret \
--rotation-rules AutomaticallyAfterDays=30
{
"ARN": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:prod/orders/db-Ab1XyZ",
"Name": "prod/orders/db",
"VersionId": "f0e9d8c7-b6a5-4321-9876-543210fedcba"
}

Detection: CloudTrail, GuardDuty, Security Hub and Config

CloudTrail is the camera above the door. It records every management API (application programming interface) call in the account: who made it, what they called, when, and from which IP address. Switch it on before anything else, because without it you can neither investigate an incident nor prove compliance to an auditor. (Data-plane calls, such as reading an S3 object, need *data events* enabled separately, and those cost extra.) Management events sit free in Event history for 90 days. A *trail* copies them somewhere durable, an S3 bucket, and keeps them there. --is-multi-region-trail covers every Region, --is-organization-trail covers every member account, and --enable-log-file-validation writes signed digest files, so you can prove nobody edited the tapes. GuardDuty then reads those same CloudTrail records plus VPC (Virtual Private Cloud) Flow Logs and DNS logs, runs them against threat intelligence and machine learning, and raises findings such as crypto-mining or credentials being used from somewhere they should not be. No agents to install for those foundational sources. Security Hub gathers findings from GuardDuty, Config, and dozens of partner tools into a single posture score spanning all your accounts. AWS Config records what every resource looked like over time and evaluates rules such as "every bucket must be encrypted."

turn on the audit trail and threat detection
# Bucket policy must already allow cloudtrail.amazonaws.com to write;
# organization trails are created from the management or delegated-admin account
aws cloudtrail create-trail --name org-trail \
--s3-bucket-name central-audit-logs-111122223333 \
--is-multi-region-trail --is-organization-trail \
--enable-log-file-validation
{
"Name": "org-trail",
"S3BucketName": "central-audit-logs-111122223333",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"TrailARN": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/org-trail",
"LogFileValidationEnabled": true,
"IsOrganizationTrail": true
}
aws cloudtrail start-logging --name org-trail
aws guardduty create-detector --enable \
--finding-publishing-frequency FIFTEEN_MINUTES
{
"DetectorId": "12abc34d567e8fa901bc2d34e56789f0"
}
# Ask the trail a question: who signed in to the console today?
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin \
--max-results 1 --query 'Events[].{user:Username,time:EventTime}'
[
{
"user": "sofia.admin",
"time": "2026-07-13T07:41:19+00:00"
}
]

The exam mapping is mercifully clean. *Who made this API call* means CloudTrail. *Detect a compromised instance* means GuardDuty. *Aggregate security posture across accounts* means Security Hub. *Track configuration drift and compliance* means Config.

The four control families of AWS defense in depth
Encryption
KMS - at rest
Envelope encryption; a customer-managed key adds key-policy control, rotation, and the disable lever
ACM / TLS - in transit
Free auto-renewing certificates; CloudFront wants the certificate in us-east-1
Edge protection
WAF (layer 7)
Web ACL, managed rule groups, rate-based rules; attaches to CloudFront/ALB/API GW, never NLB or EC2
Shield
Standard is free at layers 3 and 4; Advanced at $3k/mo adds the response team and cost protection
Secrets handling
Secrets Manager
$0.40/secret; native Lambda rotation keeps vault and source in step
SSM Parameter Store
Free SecureString values, but no rotation of its own
Detection
CloudTrail
Who, what and when for every API call; the first control to switch on
GuardDuty
Machine-learning findings from CloudTrail, VPC Flow and DNS logs; no agents to install
Security Hub + Config
Posture aggregated across accounts; configuration drift and compliance rules
IAM least privilege is the floor all four families stand on. No single layer is enough on its own; each one assumes the layer before it can fail.
Delete a KMS key and you delete the data with it
KMS refuses to delete a key on the spot. aws kms schedule-key-deletion forces a waiting period of 7 to 30 days, and that delay exists for a reason. Once the key is gone, every object, snapshot, and backup encrypted under it is unreadable forever, and AWS cannot bring it back. So before you schedule a deletion, disable the key instead and watch CloudTrail for a few weeks for kms:Decrypt calls that start failing. A key that looks orphaned is very often still quietly decrypting a backup pipeline somebody set up and forgot about.

Every control in this lesson has a meter running behind it. KMS bills per API request. GuardDuty bills by the volume of logs and events it chews through. Shield Advanced is a flat $36,000 a year before usage fees even start. Security spending that nobody measures turns into security spending that gets cut in the next budget round, so the visibility you have applied to threats needs applying to your bill as well. That is where the next lesson goes: cost optimization, and the design habits that keep this architecture affordable as it grows.

Encryption without discipline in the key policy is theater. Customer-managed keys (CMKs) need least-privilege key policies. S3 bucket keys cut your KMS request volume. And TLS everywhere beats the old excuse of terminating at the load balancer and trusting whatever happens inside the VPC.

GuardDuty and Security Hub only pay off when their findings become a queue somebody actually works through. A CloudTrail with no integrity validation, sitting in a bucket anyone can write to, is how an intruder erases their footprints once they are inside.

Try this

List your KMS key aliases, check that CloudTrail is really logging, and look at the state of your GuardDuty detector. All read-only calls; nothing here changes anything.

terminal
aws kms list-aliases --query 'Aliases[?starts_with(AliasName, `alias/`)].[AliasName]' --output text | head
aws cloudtrail describe-trails --query 'trailList[].{Name:Name,MultiRegion:IsMultiRegionTrail,LogGroup:CloudWatchLogsLogGroupArn}' --output table
aws guardduty list-detectors --output text
aws guardduty get-detector --detector-id 12abc34def --query '{Status:Status,Stage:FindingPublishingFrequency}' --output table
output
alias/aws/s3
alias/aws/rds
alias/app-data
---------------------------------------------
| DescribeTrails |
+-----------+-------------+-----------------+
| MultiRegion| Name | LogGroup |
+-----------+-------------+-----------------+
| True | org-trail | arn:aws:logs...|
+-----------+-------------+-----------------+
12abc34def
ENABLED | FIFTEEN_MINUTES

Takeaway

Defense in depth means stacking encryption, filtering at the edge, proper secret storage, and detection so that each one covers the failure of the one before it. IAM on its own is a permissions model, not a security program.

Your next move in a real account: pick one finding source, GuardDuty or Security Hub, and wire it to a ticket queue or a chat channel, so a human sees a finding within minutes instead of at the monthly review.

Quick check
01A developer's IAM role carries an IAM policy granting kms:Decrypt on a customer-managed key, yet every decrypt call comes back with AccessDenied. What is the most likely cause?
Correct — The lesson flags this as a classic exam nuance: the key policy is the primary access control on a KMS key, and an IAM grant does nothing unless the key policy allows that principal too.
Incorrect — No. Rotation only swaps the backing key material on a schedule. It never blocks decryption, and older data keys keep working.
Incorrect — No. AWS-managed keys decrypt perfectly well. What you cannot do is edit their key policy or disable them.
Incorrect — No. BucketKeyEnabled is an S3 cost optimization that reuses a bucket-level key. It has nothing to do with decrypt permissions.
02You need a free, auto-renewing public Transport Layer Security (TLS) certificate for a CloudFront distribution whose origin runs in eu-west-1. Which statement about the AWS Certificate Manager (ACM) certificate is correct?
Incorrect — No. CloudFront pays no attention to the origin's Region when it picks a certificate; it wants the certificate in us-east-1.
Correct — CloudFront accepts ACM certificates only from us-east-1, a favorite exam trap, no matter which Region the origin runs in.
Incorrect — No. The free public certificates from ACM cannot be exported. They work only on integrated endpoints such as CloudFront, ALB, and API Gateway.
Incorrect — No. CloudFront plugs straight into ACM. The only constraint is the issuing Region, us-east-1.
03A volumetric distributed denial-of-service (DDoS) attack blew up a company's Auto Scaling group and spiked its data-transfer bill. Next time they want those attack-driven scaling and transfer charges credited back, and they want a specialist team they can call while the attack is running. Which option BEST meets these needs?
Incorrect — No. Shield Standard is free and automatic at layers 3 and 4, but it comes with neither cost protection nor a response team.
Incorrect — No. A rate-based rule throttles abusive IP addresses at layer 7. It refunds nothing and gives you nobody to call.
Correct — Shield Advanced adds the Shield Response Team plus cost protection, which credits back the Auto Scaling and data-transfer charges an attack caused. Response team plus refunds points straight here.
Incorrect — No. GuardDuty spots threats in your logs. It neither mitigates a flood nor refunds what the flood cost you.

Related