Run a first security review of a new project's AWS footprint
Task
A small team is about to put a project's documents into AWS and has asked you for a first security review. You will build the project's storage and network the safe way, then use the services from this domain to check them: S3 Block Public Access and default encryption, a bucket policy that refuses unencrypted connections, IAM Access Analyzer to prove nothing is shared outside the account, a security group and a network ACL that filter by address, Amazon GuardDuty with sample findings, AWS CloudTrail event history to see who made each change, and AWS Trusted Advisor's security checks. You finish with a short written review stored in the project bucket, and then remove every resource and confirm GuardDuty is off again.
Steps
- Record the starting point, so the clean-up removes only what you added. Run:
aws sts get-caller-identity --query Arn --output text
aws guardduty list-detectors --query 'length(DetectorIds)'
aws accessanalyzer list-analyzers --query 'length(analyzers)'
The first line must not end in :root; if it does, do not go on. Write down the other two numbers. If GuardDuty already prints 1, it was on before you started: in step 6, skip turning it on and skip the sample findings, and in the clean-up leave it alone. 2. Create the project bucket and confirm the two protections S3 gives a new bucket: Block Public Access on, and default encryption with S3 managed keys (SSE-S3).
aws s3 mb s3://clf-lab11-review-111122223333
aws s3api get-public-access-block --bucket clf-lab11-review-111122223333 --query 'PublicAccessBlockConfiguration'
aws s3api get-bucket-encryption --bucket clf-lab11-review-111122223333 --query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm' --output text
The second command lists all four Block Public Access settings as true; the third prints AES256. Under the shared responsibility model these are your settings to keep, not AWS's: Block Public Access stays on for the whole lab. 3. Add a bucket policy that refuses any request not made over TLS. It is a Deny statement, so it takes access away from everyone and grants nothing. Write it in CloudShell and apply it:
cat > clf-lab11-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::clf-lab11-review-111122223333",
"arn:aws:s3:::clf-lab11-review-111122223333/*"
],
"Condition": {"Bool": {"aws:SecureTransport": "false"}}
}
]
}
EOF
aws s3api put-bucket-policy --bucket clf-lab11-review-111122223333 --policy file://clf-lab11-policy.json
aws s3api get-bucket-policy-status --bucket clf-lab11-review-111122223333 --query 'PolicyStatus.IsPublic'
The last command prints false: S3 itself judges the policy not public. 4. Turn on IAM Access Analyzer and ask it whether anyone outside the account can reach the bucket:
aws accessanalyzer create-analyzer --analyzer-name clf-lab11c-analyzer --type ACCOUNT
ARN=$(aws accessanalyzer get-analyzer --analyzer-name clf-lab11c-analyzer --query 'analyzer.arn' --output text)
aws accessanalyzer get-analyzer --analyzer-name clf-lab11c-analyzer --query 'analyzer.status' --output text
Wait until the last command prints ACTIVE, give the first scan a few minutes, then run:
aws accessanalyzer list-findings --analyzer-arn "$ARN" --filter '{"resource": {"eq": ["arn:aws:s3:::clf-lab11-review-111122223333"]}, "status": {"eq": ["ACTIVE"]}}' --query 'length(findings)'
It prints 0: nothing outside the account can reach the bucket. Use only the ACCOUNT type; the unused access type is charged. If the account already ran an analyzer (step 1), the create may fail: then set ARN from aws accessanalyzer list-analyzers and skip that analyzer in the clean-up. 5. Build the project's network filter in a VPC of its own, so nothing you already run is touched. The security group allows HTTPS from one documentation address range only; the network ACL denies a second documentation range and allows the first. Neither range is a real network.
aws ec2 create-vpc --cidr-block 10.11.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=clf-lab11-vpc}]'
VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=clf-lab11-vpc --query 'Vpcs[0].VpcId' --output text)
aws ec2 create-security-group --group-name clf-lab11-web-sg --description "Lab 11 HTTPS from a documentation range" --vpc-id "$VPC_ID"
SG_ID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=clf-lab11-web-sg --query 'SecurityGroups[0].GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG_ID" --protocol tcp --port 443 --cidr 203.0.113.0/24
aws ec2 create-network-acl --vpc-id "$VPC_ID" --tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=clf-lab11-nacl}]'
NACL_ID=$(aws ec2 describe-network-acls --filters Name=tag:Name,Values=clf-lab11-nacl --query 'NetworkAcls[0].NetworkAclId' --output text)
aws ec2 create-network-acl-entry --network-acl-id "$NACL_ID" --ingress --rule-number 100 --protocol tcp --port-range From=443,To=443 --cidr-block 198.51.100.0/24 --rule-action deny
aws ec2 create-network-acl-entry --network-acl-id "$NACL_ID" --ingress --rule-number 200 --protocol tcp --port-range From=443,To=443 --cidr-block 203.0.113.0/24 --rule-action allow
aws ec2 describe-network-acls --network-acl-ids "$NACL_ID" --query 'NetworkAcls[0].Entries[].[Egress,RuleNumber,RuleAction,CidrBlock]' --output table
The table shows your two numbered rules and the final catch-all deny rules that every network ACL has. Notice the difference the lesson describes: the network ACL holds deny rules and is evaluated in number order, while the security group has only allow rules. The network ACL is not associated with any subnet, so it filters nothing yet. 6. Turn on Amazon GuardDuty with a single action, unless step 1 showed it was already on:
aws guardduty create-detector --enable
DET=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty get-detector --detector-id "$DET" --query 'Status' --output text
The last command prints ENABLED. You did not turn on VPC Flow Logs first: GuardDuty reads its sources itself. A new, quiet account has nothing to detect, so in the GuardDuty console open Settings and, under Sample findings, choose Generate sample findings. Then open Findings: each sample is marked [SAMPLE]. Open one and note its type, its severity and the resource it names. GuardDuty reported it; nothing was blocked. Then count them:
aws guardduty list-findings --detector-id "$DET" --query 'length(FindingIds)'
- See who made each change. CloudTrail event history records API calls without any set-up, though an event can take several minutes to appear:
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=PutBucketPolicy --max-results 5 --query 'Events[].[EventTime,Username]' --output text
The user name is your own identity: CloudTrail records the action and GuardDuty judges whether such actions look malicious. Then open AWS Trusted Advisor, choose Recommendations and the Security category, and note the status of the check Security Groups - Specific Ports Unrestricted. Port 443 from a narrow range is the kind of rule it does not flag. 8. Write the review and store it in the project bucket. Use your own words; the lines below are a starting point:
cat > review.txt <<'EOF'
First security review (invented sample project)
Storage: Block Public Access on, SSE-S3 default encryption, TLS-only bucket policy.
Shared outside the account: none, per IAM Access Analyzer.
Network: HTTPS from one range only; deny rule ready at the subnet boundary.
Threat detection: GuardDuty tested with sample findings, then turned off.
Who changed what: CloudTrail event history.
Not covered: AWS WAF and Shield Advanced, for when the project serves web traffic.
EOF
aws s3 cp review.txt s3://clf-lab11-review-111122223333/review.txt
Verify
Run these before the clean-up. Re-set the variables first if CloudShell restarted:
ARN=$(aws accessanalyzer get-analyzer --analyzer-name clf-lab11c-analyzer --query 'analyzer.arn' --output text)
DET=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws s3api get-bucket-policy-status --bucket clf-lab11-review-111122223333 --query 'PolicyStatus.IsPublic'
Expected output: false.
aws s3api get-bucket-encryption --bucket clf-lab11-review-111122223333 --query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm' --output text
Expected output: AES256.
aws accessanalyzer list-findings --analyzer-arn "$ARN" --filter '{"resource": {"eq": ["arn:aws:s3:::clf-lab11-review-111122223333"]}, "status": {"eq": ["ACTIVE"]}}' --query 'length(findings)'
Expected output: 0.
aws ec2 describe-network-acls --filters Name=tag:Name,Values=clf-lab11-nacl --query 'length(NetworkAcls[0].Entries)'
Expected output: 4 (your two rules, plus the catch-all deny for inbound and for outbound).
aws guardduty get-detector --detector-id "$DET" --query 'Status' --output text
Expected output: ENABLED.
aws s3api list-objects-v2 --bucket clf-lab11-review-111122223333 --query 'KeyCount'
Expected output: 1.
Cost and clean-up
Charges are possible. GuardDuty is the main one: its free trial applies the first time it is turned on in a Region, so if this account has used GuardDuty there before, the time it runs in this lab may be charged. S3 storage and requests for one small file may also be charged outside the Free Tier. The VPC, security group, network ACL, external access analyzer and CloudTrail event history are not charged for themselves. Do every clean-up step today, and check the GuardDuty and Amazon S3 pricing pages for current terms.
Created by this lab: the S3 bucket clf-lab11-review-111122223333, with its bucket policy; the object review.txt in it; the IAM Access Analyzer analyzer clf-lab11c-analyzer; the VPC clf-lab11-vpc, with the default route table, network ACL and security group AWS makes inside it; the security group clf-lab11-web-sg; the network ACL clf-lab11-nacl and its two rules; the GuardDuty detector in this Region and its sample findings, if GuardDuty was off when you started; the CloudShell files clf-lab11-policy.json and review.txt.
- Turn GuardDuty off, but only if this lab turned it on (step 1 printed
0). Deleting the detector also deletes its sample findings:
DET=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty delete-detector --detector-id "$DET"
- Delete the analyzer
clf-lab11c-analyzer(skip this if the create in step 4 failed and you used an analyzer that was already there):
aws accessanalyzer delete-analyzer --analyzer-name clf-lab11c-analyzer
- Empty and delete the bucket
clf-lab11-review-111122223333, which removesreview.txtand the bucket policy with it:
aws s3 rb s3://clf-lab11-review-111122223333 --force
- Remove the network pieces in order: the two rules of
clf-lab11-nacl, the network ACL itself, the security groupclf-lab11-web-sg, and finally the VPCclf-lab11-vpc, which takes its default route table, network ACL and security group with it:
VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=clf-lab11-vpc --query 'Vpcs[0].VpcId' --output text)
SG_ID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=clf-lab11-web-sg --query 'SecurityGroups[0].GroupId' --output text)
NACL_ID=$(aws ec2 describe-network-acls --filters Name=tag:Name,Values=clf-lab11-nacl --query 'NetworkAcls[0].NetworkAclId' --output text)
aws ec2 delete-network-acl-entry --network-acl-id "$NACL_ID" --ingress --rule-number 100
aws ec2 delete-network-acl-entry --network-acl-id "$NACL_ID" --ingress --rule-number 200
aws ec2 delete-network-acl --network-acl-id "$NACL_ID"
aws ec2 delete-security-group --group-id "$SG_ID"
aws ec2 delete-vpc --vpc-id "$VPC_ID"
- Remove the local files from CloudShell:
rm clf-lab11-policy.json review.txt
Check it is gone
aws guardduty list-detectors --query 'length(DetectorIds)'
Expected output: 0, GuardDuty is off. (If step 1 printed 1, the expected output is 1: the detector was there before you and stays.)
aws accessanalyzer list-analyzers --query "length(analyzers[?name=='clf-lab11c-analyzer'])"
Expected output: 0.
aws s3api list-buckets --query "length(Buckets[?Name=='clf-lab11-review-111122223333'])"
Expected output: 0.
aws ec2 describe-vpcs --filters Name=tag:Name,Values=clf-lab11-vpc --query 'length(Vpcs)'
Expected output: 0.
aws ec2 describe-security-groups --filters Name=group-name,Values=clf-lab11-web-sg --query 'length(SecurityGroups)'
Expected output: 0.
Notes
203.0.113.0/24 and 198.51.100.0/24 are address ranges reserved for documentation, so no real network sits behind them. Turning on GuardDuty and Access Analyzer makes the service-linked roles AWSServiceRoleForAmazonGuardDuty and AWSServiceRoleForAccessAnalyzer if they did not exist already; they cost nothing and are left in place. The review does not use AWS WAF: a web ACL only makes sense in front of something that serves web requests, and this project serves none yet. AWS Security Hub and Amazon Inspector are also left out on purpose, because both start free trials that become charged and Security Hub depends on AWS Config recording, which is charged too.
This is an independent study companion for the AWS Certified Cloud Practitioner (CLF-C02) exam and is not produced by or endorsed by Amazon Web Services. AWS, Amazon Web Services and AWS Certified Cloud Practitioner are trademarks of Amazon.com, Inc. or its affiliates.
This is an independent study companion for AWS Certified Cloud Practitioner CLF-C02 and is not produced by or endorsed by CompTIA.