每月342美元的不必要出口流量:如何通过Security Group审计识别绕过CDN的客户
发布于 2026年5月3日
背景:强制使用CDN的多租户平台
该平台在共享和专用EC2实例上托管数十个WordPress站点。安全和成本模型依赖于所有站点通过CDN接收流量——GoCache、Cloudflare或Akamai,取决于客户。每个实例的Security Group只允许使用中的CDN的IP范围访问80和443端口。
当DNS正确指向CDN且Security Group对齐时,EC2出口流量极少——内容由CDN从缓存提供,无需在每次请求时都访问源站。AWS数据传出费用只在源站被访问时产生,而不是在缓存命中时。
CDN配置正确 = EC2出口接近零。CDN已配置但DNS绕过了它 = EC2全量出口,CDN缓存费用,同一内容付了两次钱。
信号:2月份单个域名花了342美元
通过CloudWatch NetworkOut(比Cost Explorer在隔离实例方面更精确)进行的月度数据传输分析,在排名中发现了异常:
# CloudWatch NetworkOut数据——2026年2月
# 基础设施总计:12,095 GB / $1,088.65
出口流量排名:
#1 cliente-loja.com.br 3,803 GB $342.29 (占总量31%)
#2 clientportal.example.com 1,775 GB $159.75
#3 ...
...
#66(最后一个实例) 0.1 GB $0.01第二名有1,775 GB。第一名有3,803 GB——是第二名的两倍多,占66个实例全部出口流量的31%。对于一个没有任何实例应该有这种流量的多租户平台来说,这个数字是最明确的信号,表明有问题。
确认:GoCache服务了0 GB
下一步检查了域名cliente-loja.com.br的GoCache分析数据:
# GoCache分析API查询——2026年2月
# 域名:cliente-loja.com.br
GoCache提供的带宽:0.00 GB
CDN请求数:0
缓存命中率:N/A零字节。该域名在GoCache上有配置——账号存在,规则都在——但整个月CDN没有提供过一个字节。所有流量都直接发往EC2,完全绕过了CDN。
两种可能的原因可以解释这种模式:要么DNS没有指向GoCache,要么实例的Security Group中除了CDN SG外还保留着World SG(0.0.0.0/0),使源站可以被直接访问。
根本原因:World SG与CDN SG并存
Security Group审计从分析每个实例的SG组合开始。平台上相关的SG:
# 平台Security Groups
sg-0aaaa1111aaaa1111 "World" → 对0.0.0.0/0开放80/443(不受限访问)
sg-0bbbb2222bbbb2222 "GoCache" → 只允许GoCache IP访问80/443
sg-0cccc3333cccc3333 "Cloudflare" → 只允许Cloudflare IP访问80/443
sg-0dddd4444dddd4444 "Akamai" → 只允许Akamai IP访问80/443受CDN保护的实例的预期状态是:只有使用中的CDN SG,没有World SG。cliente-loja.com.br实例同时拥有两者:GoCache SG + World SG。有了World SG,互联网上的任何IP都可以直接访问EC2的443端口——由于DNS指向EC2(而非GoCache),所有流量都直接到达。
DNS验证确认了绕过:
# 检查域名DNS
dig cliente-loja.com.br +short
# 返回:198.51.100.106 ← EC2公网IP,不是GoCache IP(198.51.100.x)
# GoCache IP以198.51.100.x开头
# 如果DNS解析为EC2 IP,则CDN被绕过完整审计:22个带GoCache SG的实例
审计没有止步于一个实例。该流程被应用于所有22个配置了GoCache SG的实例,将SG与实际DNS解析进行交叉对比:
# 列出所有带GoCache SG的实例
aws ec2 describe-instances --filters "Name=instance.group-id,Values=sg-0bbbb2222bbbb2222" --query 'Reservations[].Instances[].[InstanceId,PublicIpAddress,Tags[?Key==`Name`].Value|[0]]' --output table
# 对每个实例,检查DNS是否指向GoCache或直接EC2 IP
for DOMAIN in $(域名列表); do
DNS_IP=$(dig +short $DOMAIN | head -1)
EC2_IP=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=*${DOMAIN}*" --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
if [ "$DNS_IP" = "$EC2_IP" ]; then
echo "绕过:$DOMAIN → $DNS_IP(直接访问EC2)"
elif echo "$DNS_IP" | grep -q "^198\.51\.100\."; then
echo "正常:$DOMAIN → $DNS_IP(GoCache)"
else
echo "检查:$DOMAIN → $DNS_IP(需核实)"
fi
done22个带GoCache SG的实例审计结果:
19个实例: DNS指向GoCache(198.51.100.x)——配置正确。
3个实例: DNS指向Cloudflare(104.21.x.x,172.67.x.x),但SG配置为GoCache——错误的SG允许来自GoCache(实际上并不到达)的流量,而Cloudflare SG不存在。
除了CDN绕过,审计还发现了6个有冗余World SG的实例:
# 有World SG + CDN SG的实例(冗余World SG)
# 验证DNS并确认CDN处于活跃状态后:
域名 EC2 IP DNS 操作
webserver-app01.example 203.0.113.99 GoCache 移除World SG
webserver-app02.example 203.0.113.117 GoCache 移除World SG
webserver-app03.example 203.0.113.184 GoCache 移除World SG
webserver-app04.example 203.0.113.70 GoCache 移除World SG
webserver-app05.example 203.0.113.52 GoCache 移除World SG
webserver-app06.example 203.0.113.236 Cloudflare 移除World SG + GoCache SG,添加Cloudflare SG执行的修复措施
对于5个有冗余World SG的实例(DNS指向GoCache,World SG不必要),批量移除:
# 从CDN活跃的实例中移除World SG
for INSTANCE in i-0aaaa1111aaaa1111 i-0bbbb2222bbbb2222 i-0cccc3333cccc3333 i-0dddd4444dddd4444 i-0eeee5555eeee5555; do
# 列出当前SG,排除World SG
NEW_SGs=$(aws ec2 describe-instances --instance-ids $INSTANCE --query 'Reservations[].Instances[].NetworkInterfaces[0].Groups[].GroupId' --output text | tr ' ' '
' | grep -v 'sg-0aaaa1111aaaa1111')
# 应用新SG列表(不含World SG)
aws ec2 modify-instance-attribute --instance-id $INSTANCE --groups $(echo $NEW_SGs | tr '
' ' ')
echo "$INSTANCE:World SG已移除"
done对于有GoCache SG但DNS指向Cloudflare的实例,修复方案是将GoCache SG换成Cloudflare SG:
# 将GoCache SG换成Cloudflare SG
INSTANCE="i-0ffff6666ffff6666"
# 移除World SG(sg-0aaaa1111aaaa1111)和GoCache SG(sg-0bbbb2222bbbb2222)
# 添加Cloudflare SG(sg-0cccc3333cccc3333)
aws ec2 modify-instance-attribute --instance-id $INSTANCE --groups sg-0cccc3333cccc3333额外发现:GoCache SG中的过时IP
在审计过程中,还验证了GoCache SG本身的IP列表的时效性。结果揭示了第二层风险:
# 比较SG中的IP与官方列表
SG中的IP数: 31
官方IP数: 25
差异: 6个额外IP未在GoCache官网列出
# 发现的额外IP(GoCache支持确认为过时)
198.51.100.13/32 — 旧的单独IP
198.51.100.152/29 — 旧的/29
198.51.100.72/29 — 旧的/29
198.51.100.64/26 — 旧的/26
198.51.100.192/29 — 旧的/29
198.51.100.24/29 — 旧的/29CDN SG中的过时IP是一个隐患:如果其中一个IP块被重新分配给第三方,该范围内的任何IP都可以像CDN一样直接访问源站。GoCache支持确认这6个IP块确实已过时可以移除。对应的11条规则(6个IP × 80和443端口)被撤销:
# 撤销GoCache SG中的过时规则
aws ec2 revoke-security-group-ingress --group-id sg-0bbbb2222bbbb2222 --security-group-rule-ids sgr-xxx1 sgr-xxx2 sgr-xxx3 sgr-xxx4 sgr-xxx5 sgr-xxx6 sgr-xxx7 sgr-xxx8 sgr-xxx9 sgr-xxx10 sgr-xxx11
# 验证最终数量
aws ec2 describe-security-groups --group-ids sg-0bbbb2222bbbb2222 --query 'SecurityGroups[0].IpPermissions | length(@)'
# 25 — 与GoCache官方列表一致方法论:CloudWatch vs CDN分析数据
这里有效的检测模式适用于任何使用CDN的平台——Cloudflare、CloudFront、Fastly、GoCache:
1. 测量实际EC2出口流量: 按实例统计CloudWatch NetworkOut。Cost Explorer按账户汇总,不按实例——直接使用API获取精细数据。
2. 与CDN分析数据交叉对比: 对每个出口流量高的域名,检查同期CDN提供的带宽。CDN带宽0 GB + EC2高出口 = 绕过。
3. 通过DNS确认: 如果DNS解析为EC2 IP(而非CDN IP),则绕过已在DNS层面得到确认。
4. 检查Security Groups: 即使DNS指向CDN,开放的World SG也允许直接访问。机器人和扫描器总是测试直接IP——如果SG允许,它们就在不经过CDN的情况下提供流量。
# 绕过检测脚本——CloudWatch vs CDN分析数据
# 适用于GoCache、Cloudflare、CloudFront
for DOMAIN in $(cdn域名列表); do
EC2_EGRESS=$(get_cloudwatch_network_out $DOMAIN $MONTH) # GB
CDN_BANDWIDTH=$(get_cdn_bandwidth $DOMAIN $MONTH) # GB
RATIO=$(echo "$CDN_BANDWIDTH / $EC2_EGRESS" | bc -l)
if (( $(echo "$RATIO < 0.1" | bc -l) )); then
echo "绕过警告:$DOMAIN — CDN仅提供了$(echo "$RATIO * 100" | bc -l)%的流量"
echo " EC2出口: ${EC2_EGRESS} GB"
echo " CDN带宽:${CDN_BANDWIDTH} GB"
fi
done财务影响与问题规模
对于cliente-loja.com.br,识别出的直接成本是每月342.29美元的不必要出口流量。在拥有数十个客户的平台上,乘数效应是显著的。即使不单独计算每个案例,审计中发现的3个SG配置错误的域名也代表类似的潜在流失。
没有正确DNS的CDN是双重成本:你为不提供服务的CDN付费,也为提供所有服务的EC2出口流量付费。Security Group审计是第二步——但第一步是将CloudWatch NetworkOut与CDN分析数据进行交叉对比。
多租户平台的经验教训
在相同安全模型下托管多个客户的平台需要定期审计DNS、CDN和Security Groups之间的对齐情况。这三个层面的决策是独立做出的——客户更换CDN,DNS被更新,但SG被遗忘。或者World SG为诊断目的临时添加后从未被移除。
建议频率: 每月进行一次DNS × SG × CDN分析对齐审计。识别一个绕过的成本是一小时工作。不识别的成本体现在下个月的账单上。
