AWS Training
Modules Listen All tracks

← Fundamentals

SAAF Lab — see the boundaries in your own account

You will need: AWS CLI v2, jq, dig, and permission to call EC2, Account, S3 and SSM describe APIs, and to create a VPC, subnets, an internet gateway, route tables, an EBS volume and an S3 bucket.

Write your answers down. Questions are numbered Q1…Q16. The lab's point is that your output is the evidence — where a question asks what you observed, write what you saw, not what the lesson predicted. If they disagree, that is a finding.

⚠️ Safety: everything here is small and reversible. Run the teardown today.

export LAB=saaf-$(date +%Y%m%d)-$RANDOM
export REGION=$(aws configure get region)
echo "LAB=$LAB REGION=$REGION"

Part 1 — Regions and opt-in (10 min, read-only)

aws account list-regions --region-opt-status-contains ENABLED_BY_DEFAULT \
  --query 'Regions[*].RegionName' --output text | tr '\t' '\n' | sort | wc -l
aws account list-regions --region-opt-status-contains ENABLED ENABLED_BY_DEFAULT \
  --query 'Regions[*].RegionName' --output text | tr '\t' '\n' | sort
aws account get-region-opt-status --region-name af-south-1

Q1. How many Regions are enabled by default for your account? Does that match the 17 the AWS Regions page listed on 2026-09-25? If not, write down both numbers and today's date — that's the point of dating facts.

Q2. What is the opt-in status of af-south-1? Quote the sentence from lesson 2 that explains why a Region can be DISABLED.

Q3. Get the long name of your working Region from the public SSM parameter:

aws ssm get-parameters-by-path \
  --path /aws/service/global-infrastructure/regions/$REGION \
  --query 'Parameters[?Name.contains(@,`longName`)].Value' --output text

Pick a scenario — "customer data must remain in Germany" — and say which Region code satisfies it and which lesson-2 sentence makes residency a filter rather than a trade-off.


Part 2 — AZ names vs AZ IDs (10 min, read-only)

aws ec2 describe-availability-zones --filters Name=zone-type,Values=availability-zone \
  --query "AvailabilityZones[].{Name:ZoneName,Id:ZoneId,State:State}" --output table
aws ec2 describe-availability-zones --all-availability-zones \
  --query "AvailabilityZones[].{Name:ZoneName,Type:ZoneType,OptIn:OptInStatus}" --output table

Q4. Record the name→ID mapping for your Region. How many AZs can your account use here? The AZ page says "Each Region has at least three" — does your account see at least three? If not, which lesson-2 sentence explains it?

Q5. The second command includes Local Zones and Wavelength Zones. List any with ZoneType other than availability-zone, and their OptInStatus. If there are none in your Region, say so.

Q6. If you have access to a second account, run the first command there and compare. Is …a the same AZ ID in both? State your account creation dates if you know them, and quote the November 2025 sentence. (If you only have one account, write down what you would compare and why.)


Part 3 — Zonal resources, public vs private (30 min)

Create a VPC and try to resize it:

VPC=$(aws ec2 create-vpc --cidr-block 10.42.0.0/16 \
  --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=$LAB}]" \
  --query Vpc.VpcId --output text)
echo $VPC
aws ec2 associate-vpc-cidr-block --vpc-id $VPC --cidr-block 10.42.0.0/15 2>&1 | tail -2
aws ec2 associate-vpc-cidr-block --vpc-id $VPC --cidr-block 10.43.0.0/16 \
  --query 'CidrBlockAssociation.{Cidr:CidrBlock,State:CidrBlockState.State}'

Q7. Paste the error from the /15 attempt, and the result of the 10.43.0.0/16 association. In one sentence each: why did the first fail, and why did the second succeed? Quote the VPC CIDR page.

Now two subnets in two AZs:

read AZ1 AZ2 <<<$(aws ec2 describe-availability-zones \
  --filters Name=zone-type,Values=availability-zone \
  --query 'AvailabilityZones[0:2].ZoneName' --output text)
SUB_PUB=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.42.1.0/24 \
  --availability-zone $AZ1 --query Subnet.SubnetId --output text)
SUB_PRV=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.42.2.0/28 \
  --availability-zone $AZ2 --query Subnet.SubnetId --output text)
aws ec2 describe-subnets --subnet-ids $SUB_PUB $SUB_PRV \
  --query 'Subnets[].{Id:SubnetId,AZ:AvailabilityZone,Cidr:CidrBlock,Free:AvailableIpAddressCount}' \
  --output table

Q8. Record Free for the /24 and the /28. Show the arithmetic that predicts both numbers. Did the output match?

Q9. Look at the AvailabilityZone field in the table. How many values can one subnet have — and is there any create-subnet option to give it two? Quote the subnet page.

Make one subnet public by routing only:

IGW=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
RT=$(aws ec2 create-route-table --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW >/dev/null
ASSOC=$(aws ec2 associate-route-table --route-table-id $RT --subnet-id $SUB_PUB \
  --query AssociationId --output text)
aws ec2 describe-route-tables --route-table-ids $RT \
  --query 'RouteTables[0].Routes[].{Dest:DestinationCidrBlock,Target:GatewayId}' --output table
aws ec2 describe-subnets --subnet-ids $SUB_PUB \
  --query 'Subnets[0].MapPublicIpOnLaunch'

Q10. Which of your two subnets is now public, and what single thing made it so? What does MapPublicIpOnLaunch show, and why is that a separate setting from being public?

Now the zonal EBS fact:

VOL=$(aws ec2 create-volume --availability-zone $AZ2 --size 1 --volume-type gp3 \
  --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$LAB}]" \
  --query VolumeId --output text)
aws ec2 describe-volumes --volume-ids $VOL \
  --query 'Volumes[0].{AZ:AvailabilityZone,Encrypted:Encrypted,State:State}'
aws ec2 get-ebs-encryption-by-default

Q11. Record the volume's AZ and Encrypted value, and your account's encryption-by-default setting. Lesson 5 flags that the default state isn't stated on the page it fetched — you now have your account's answer. What does it mean for the next volume someone creates?

Q12. Without launching an instance: an instance in $AZ1 needs this volume's data. Write the steps, and quote the sentence that rules out attaching it directly.


Part 4 — Encryption defaults and DNSSEC (15 min)

BUCKET=$LAB-bucket
aws s3api create-bucket --bucket $BUCKET \
  $( [ "$REGION" != "us-east-1" ] && echo "--create-bucket-configuration LocationConstraint=$REGION" )
aws s3api get-bucket-encryption --bucket $BUCKET

Q13. Paste the encryption configuration of a bucket you did nothing to. Which algorithm is it, and which lesson-5 date explains it? Does the output say anything about SSE-C? (The April 2026 change is described on the S3 security page; record what your output shows, not what the page says.)

Q14. Lesson 3 says CreateBucket depends on us-east-1 even when the bucket is elsewhere. Explain in one sentence why, and name one consequence for a DR runbook.

DNSSEC, read-only:

dig +dnssec +multi amazon.com SOA | grep -E "RRSIG|flags" | head -5
dig DS amazon.com +short

Q15. Record whether you saw RRSIG records and a DS record. ⚠️ I have not checked whether this domain is DNSSEC-signed — that is what you are finding out. Pick a second domain you care about and repeat. For a signed zone, which record lives in the parent zone, and what does it complete?

Q16. Without creating anything: list the three requirements Route 53 places on the KMS key behind a KSK, and say which key Route 53 manages for you. Why is this lab not creating one?


Teardown — today

aws s3api delete-bucket --bucket $BUCKET
aws ec2 delete-volume --volume-id $VOL
aws ec2 disassociate-route-table --association-id $ASSOC
aws ec2 delete-route-table --route-table-id $RT
aws ec2 detach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
aws ec2 delete-internet-gateway --internet-gateway-id $IGW
aws ec2 delete-subnet --subnet-id $SUB_PUB
aws ec2 delete-subnet --subnet-id $SUB_PRV
aws ec2 delete-vpc --vpc-id $VPC

Verify it — don't assume it:

aws ec2 describe-vpcs --filters Name=tag:Name,Values=$LAB --query 'Vpcs[].VpcId'
aws ec2 describe-volumes --filters Name=tag:Name,Values=$LAB --query 'Volumes[].VolumeId'
aws s3api head-bucket --bucket $BUCKET 2>&1 | tail -1

The first two should be empty and the third should report the bucket doesn't exist. If delete-vpc fails, a dependency remains — re-run the earlier deletes and check for leftover ENIs.


Done when you can

Facilitator notes