Enable VPC Flow Logs on a live instance, generate ACCEPT/REJECT traffic, run CloudWatch Insights queries to detect port scanners and attack traffic, and configure a metric-based alarm with SNS alerting.
EC2 (t3.micro) VPC Flow Logs CloudWatch Logs
flow-logs-lab ──► fl-0bcc5ba005292bacc ──► /aws/vpc/flowlogs
44.205.11.73 (traffic: ALL) │
│
┌───────────┼───────────┐
▼ ▼ ▼
Insights Metric Filter Alarm
Queries (SSH REJECT) rejected-ssh-attempts
│
▼
SNS → email
| Resource | ID / Name |
|---|---|
| VPC | vpc-09e98b66b7c5fb1ae |
| EC2 Instance | i-044be33b753db7385 |
| Flow Log | fl-0bcc5ba005292bacc |
| IAM Role | flowlogsRole |
| Log Group | /aws/vpc/flowlogs |
| SNS Topic | vpc-security-alerts |
| CloudWatch Alarm | rejected-ssh-attempts |
cat > flow-logs-trust-policy.json << 'EOF'
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow",
"Principal": { "Service": "vpc-flow-logs.amazonaws.com" },
"Action": "sts:AssumeRole" }] }
EOF
aws iam create-role \
--role-name flowlogsRole \
--assume-role-policy-document file://flow-logs-trust-policy.json
aws iam put-role-policy \
--role-name flowlogsRole \
--policy-name flowlogsPermissions \
--policy-document file://flow-logs-permissions-policy.jsonACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/flowlogsRole"
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-09e98b66b7c5fb1ae \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /aws/vpc/flowlogs \
--deliver-logs-permission-arn $ROLE_ARNaws ssm start-session --target i-044be33b753db7385
# Inside the session:
curl -s https://checkip.amazonaws.com
curl -s https://aws.amazon.com > /dev/null
ping -c 5 8.8.8.8
telnet 44.205.11.73 23 # REJECT — port blocked
curl -m 3 http://44.205.11.73:8080 || true # REJECT
curl -m 3 http://44.205.11.73:3389 || true # REJECTQuery 1 — Top destinations (ACCEPT traffic):
parse @message "* * * * * * * * * * * * * *" as version, accountid, interfaceid,
srcaddr, dstaddr, srcport, dstport, protocol, packets, bytes,
windowstart, windowend, action, logstatus
| filter action = "ACCEPT"
| stats sum(bytes) as totalBytes by dstaddr
| sort totalBytes desc | limit 10
Query 2 — Rejected traffic by source:
parse @message "* * * * * * * * * * * * * *" as version, accountid, interfaceid,
srcaddr, dstaddr, srcport, dstport, protocol, packets, bytes,
windowstart, windowend, action, logstatus
| filter action = "REJECT"
| stats count() as attempts by srcaddr, dstport
| sort attempts desc | limit 20
Query 3 — Port scan detection:
parse @message "* * * * * * * * * * * * * *" as version, accountid, interfaceid,
srcaddr, dstaddr, srcport, dstport, protocol, packets, bytes,
windowstart, windowend, action, logstatus
| filter action = "REJECT"
| stats count_distinct(dstport) as uniquePorts by srcaddr
| filter uniquePorts > 1
| sort uniquePorts desc
TOPIC_ARN=$(aws sns create-topic --name vpc-security-alerts \
--query 'TopicArn' --output text)
aws sns subscribe --topic-arn $TOPIC_ARN \
--protocol email --notification-endpoint your@email.com
aws logs put-metric-filter \
--log-group-name /aws/vpc/flowlogs \
--filter-name RejectedSSHAttempts \
--filter-pattern '[version, account, eni, src, dst, srcport, dstport="22", protocol, packets, bytes, windowstart, windowend, action="REJECT", ...]' \
--metric-transformations metricName=RejectedSSHAttempts,metricNamespace=VPCFlowLogs,metricValue=1,defaultValue=0
aws cloudwatch put-metric-alarm \
--alarm-name rejected-ssh-attempts \
--metric-name RejectedSSHAttempts \
--namespace VPCFlowLogs \
--statistic Sum --period 300 \
--evaluation-periods 1 --threshold 10 \
--comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions $TOPIC_ARN \
--treat-missing-data notBreachingWithin 30 minutes of launching a public EC2 instance, real-world attack traffic appeared in flow logs:
| Threat | Source IPs | Finding |
|---|---|---|
| Telnet bots (port 23) | 206.183.111.36, 112.140.184.197 | 10+ attempts each |
| Port scanner | 172.110.223.185 | 4 unique ports probed |
| Multi-port scanner | 91.11.68.2 | Ports 23 + 3389 + others |
See flow-log-analysis.md for the full report with screenshots.
| # | File | Description |
|---|---|---|
| 1 | 01-ec2-instance-launch.png | EC2 instance launched |
| 2 | 02-iam-policy-files-creation.png | IAM policy files created |
| 3 | 03-iam-role-creation.png | IAM role created |
| 4 | 04-flow-logs-enabled.png | VPC Flow Logs enabled |
| 5 | 05-ssm-traffic-generation.png | Traffic generated via SSM |
| 6 | 06-sns-topic-creation.png | SNS topic created |
| 7 | 07-sns-subscription-email.png | SNS confirmation email |
| 8 | 08-sns-subscription-confirmed.png | SNS subscription confirmed |
| 9 | 09-cloudwatch-metric-filter-alarm.png | Metric filter + alarm CLI |
| 10 | 10-flow-logs-log-streams.png | Flow logs arriving in CloudWatch |
| 11 | 11-query1-top-destinations-cli.png | Query 1 — CLI results |
| 12 | 12-query2-rejected-traffic-cli.png | Query 2 — CLI results |
| 13 | 13-query3-port-scan-cli.png | Query 3 — CLI results |
| 14 | 14-query1-top-destinations-console.png | Query 1 — Console |
| 15 | 15-query2-rejected-traffic-console.png | Query 2 — Console |
| 16 | 16-query3-port-scan-console.png | Query 3 — Console |
| 17 | 17-cloudwatch-alarm-console.png | CloudWatch Alarm — Console |