VPC 安全控制
深入学习 VPC 的两大安全机制:安全组(Security Groups)和网络 ACL(Network Access Control Lists)。
安全组(Security Groups)
安全组基础
什么是安全组:
安全组是虚拟防火墙:
├─ 控制实例级别的流量
├─ 有状态防火墙(Stateful)
├─ 只支持允许规则(无拒绝规则)
├─ 默认拒绝所有入站流量
├─ 默认允许所有出站流量
└─ 可以引用其他安全组
工作层级:
├─ 附加到 ENI(弹性网络接口)
├─ 一个实例可以有多个安全组(最多 5 个)
└─ 一个安全组可以附加到多个实例
有状态防火墙特性:
有状态(Stateful)含义:
├─ 允许入站流量会自动允许响应出站
├─ 允许出站流量会自动允许响应入站
├─ 无需为响应流量创建规则
└─ 简化配置
示例:
入站规则:允许 80 端口
效果:
├─ 外部可以访问实例的 80 端口
└─ 实例的响应自动允许返回(无需出站规则)
出站规则:允许 443 端口
效果:
├─ 实例可以发起到外部 443 端口的连接
└─ 外部的响应自动允许进入(无需入站规则)
安全组规则
规则组成:
每条规则包含:
├─ 类型(Type):协议类型
├─ 协议(Protocol):TCP、UDP、ICMP 等
├─ 端口范围(Port Range):单个或范围
├─ 源/目标(Source/Destination):
│ ├─ CIDR 块(IP 地址范围)
│ ├─ 安全组 ID
│ ├─ 前缀列表 ID
│ └─ IPv4 或 IPv6
└─ 描述(Description):规则说明
示例规则:
类型:HTTP
协议:TCP
端口:80
源:0.0.0.0/0
描述:Allow HTTP from anywhere
常见规则配置:
# 创建 Web 服务器安全组
WEB_SG=$(aws ec2 create-security-group \
--group-name web-servers-sg \
--description "Security group for web servers" \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
# 允许 HTTP 流量
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0 \
--group-rule-description "Allow HTTP from internet"
# 允许 HTTPS 流量
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0 \
--group-rule-description "Allow HTTPS from internet"
# 允许 SSH(仅来自跳板机)
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG \
--protocol tcp \
--port 22 \
--source-group $BASTION_SG \
--group-rule-description "Allow SSH from bastion"
引用安全组
安全组引用的优势:
使用安全组 ID 而不是 IP 地址:
优势:
├─ 动态更新:实例变化时无需修改规则
├─ 简化管理:不需要追踪 IP 地址
├─ 自动扩展:Auto Scaling 自动适配
└─ 清晰的层次关系
示例:
Web 层 → App 层 → 数据库层
三层架构示例:
# 1. Web 层安全组
WEB_SG=$(aws ec2 create-security-group \
--group-name web-tier-sg \
--description "Web tier security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 允许来自 Internet 的 HTTP/HTTPS
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG \
--ip-permissions \
IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0}]' \
IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0}]'
# 2. 应用层安全组
APP_SG=$(aws ec2 create-security-group \
--group-name app-tier-sg \
--description "Application tier security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 只允许来自 Web 层的流量(引用安全组)
aws ec2 authorize-security-group-ingress \
--group-id $APP_SG \
--protocol tcp \
--port 8080 \
--source-group $WEB_SG \
--group-rule-description "Allow from web tier"
# 3. 数据库层安全组
DB_SG=$(aws ec2 create-security-group \
--group-name db-tier-sg \
--description "Database tier security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 只允许来自应用层的数据库连接
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG \
--protocol tcp \
--port 3306 \
--source-group $APP_SG \
--group-rule-description "Allow MySQL from app tier"
架构图示:
Internet
│ (HTTP/HTTPS)
▼
┌─────────────────┐
│ Web Tier SG │
│ Allow: 80, 443 │
│ From: 0.0.0.0/0 │
└────────┬────────┘
│ (8080)
▼
┌─────────────────┐
│ App Tier SG │
│ Allow: 8080 │
│ From: WEB_SG │
└────────┬────────┘
│ (3306)
▼
┌─────────────────┐
│ DB Tier SG │
│ Allow: 3306 │
│ From: APP_SG │
└─────────────────┘
优势:
├─ 清晰的安全边界
├─ 最小权限原则
├─ 易于管理和审计
└─ 自动适应扩缩容
高级安全组场景
跳板机(Bastion Host)访问:
# 跳板机安全组
BASTION_SG=$(aws ec2 create-security-group \
--group-name bastion-sg \
--description "Bastion host security group" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 只允许来自公司 IP 的 SSH
aws ec2 authorize-security-group-ingress \
--group-id $BASTION_SG \
--protocol tcp \
--port 22 \
--cidr 203.0.113.0/24 \
--group-rule-description "Allow SSH from office"
# 私有实例安全组 - 允许来自 Bastion 的 SSH
aws ec2 authorize-security-group-ingress \
--group-id $PRIVATE_SG \
--protocol tcp \
--port 22 \
--source-group $BASTION_SG \
--group-rule-description "Allow SSH from bastion"
负载均衡器配置:
# ALB 安全组
ALB_SG=$(aws ec2 create-security-group \
--group-name alb-sg \
--description "Application Load Balancer SG" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 允许来自 Internet 的流量
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG \
--ip-permissions \
IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0}]' \
IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0}]'
# 后端实例安全组 - 只允许来自 ALB
aws ec2 authorize-security-group-ingress \
--group-id $WEB_SG \
--protocol tcp \
--port 80 \
--source-group $ALB_SG \
--group-rule-description "Allow from ALB"
自引用规则(集群通信):
# 创建集群安全组
CLUSTER_SG=$(aws ec2 create-security-group \
--group-name cluster-sg \
--description "Cluster internal communication" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
# 允许集群内所有实例互相通信
aws ec2 authorize-security-group-ingress \
--group-id $CLUSTER_SG \
--protocol -1 \
--source-group $CLUSTER_SG \
--group-rule-description "Allow all traffic within cluster"
# 解释:
# 集群内任何附加此安全组的实例都可以:
# ├─ 接收来自其他集群成员的所有流量
# └─ 发送流量到其他集群成员
安全组最佳实践
设计原则:
1. 最小权限原则
├─ 仅开放必需的端口
├─ 限制源 IP 范围
├─ 定期审查规则
└─ 删除未使用的规则
2. 命名规范
├─ 使用描述性名称
├─ 包含环境信息(prod/dev)
├─ 包含用途(web/app/db)
└─ 示例:prod-web-tier-sg
3. 规则描述
├─ 每条规则添加描述
├─ 说明规则的用途
├─ 便于审计和排查
└─ 示例:"Allow HTTPS from CloudFront"
4. 使用标签
├─ Environment: Production
├─ Application: MyApp
├─ ManagedBy: Terraform
└─ CostCenter: Engineering
常见错误避免:
❌ 不要做:
├─ 开放 0.0.0.0/0 到管理端口(SSH/RDP)
├─ 使用过于宽泛的规则(如 0-65535 端口)
├─ 忘记删除测试规则
├─ 不添加规则描述
└─ 混用不同用途的安全组
✅ 应该做:
├─ SSH/RDP 仅允许跳板机或 VPN
├─ 精确指定需要的端口
├─ 定期审查和清理
├─ 使用引用而非 IP 地址
└─ 按层次和用途划分安全组
网络 ACL (NACLs)
NACL 基础
什么是 NACL:
网络访问控制列表:
├─ 子网级别的防火墙
├─ 无状态防火墙(Stateless)
├─ 支持允许和拒绝规则
├─ 按规则编号顺序评估
├─ 默认 NACL 允许所有流量
└─ 自定义 NACL 默认拒绝所有流量
工作层级:
├─ 附加到子网
├─ 一个子网只能有一个 NACL
├─ 一个 NACL 可以附加到多个子网
└─ 先于安全组评估
无状态防火墙特性:
无状态(Stateless)含义:
├─ 入站和出站规则独立
├─ 必须显式配置双向规则
├─ 不追踪连接状态
└─ 更复杂但更灵活
示例:
允许 HTTP 访问需要:
├─ 入站规则:允许 TCP 80 从 0.0.0.0/0
└─ 出站规则:允许 TCP 1024-65535 到 0.0.0.0/0
(临时端口用于响应)
对比安全组(有状态):
└─ 仅需入站规则:允许 TCP 80
响应自动允许
NACL vs 安全组
详细对比:
┌──────────────────┬──────────────────┬──────────────────┐
│ 特性 │ 安全组 │ NACL │
├──────────────────┼──────────────────┼──────────────────┤
│ 工作层级 │ 实例级别 │ 子网级别 │
│ 状态 │ 有状态 │ 无状态 │
│ 规则类型 │ 仅允许 │ 允许 + 拒绝 │
│ 规则评估 │ 所有规则 │ 按编号顺序 │
│ 应用范围 │ 指定实例 │ 子网内所有实例 │
│ 响应流量 │ 自动允许 │ 需要显式规则 │
│ 规则数量 │ 60 入站 + 60出站 │ 20 入站 + 20出站 │
│ 引用其他规则 │ 支持(安全组ID)│ 不支持 │
│ 默认行为 │ 拒绝所有入站 │ 自定义NACL拒绝全部│
└──────────────────┴──────────────────┴──────────────────┘
使用场景对比:
安全组:
├─ 实例级别的精细控制
├─ 应用层访问控制
├─ 引用其他安全组
└─ 大多数场景的首选
NACL:
├─ 子网级别的额外防护
├─ 阻止特定 IP 地址
├─ 合规要求的网络层控制
└─ 纵深防御的一部分
NACL 规则配置
创建自定义 NACL:
# 创建 NACL
NACL_ID=$(aws ec2 create-network-acl \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=Web-Subnet-NACL}]' \
--query 'NetworkAcl.NetworkAclId' \
--output text)
# 入站规则 100:允许 HTTP
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol tcp \
--port-range From=80,To=80 \
--cidr-block 0.0.0.0/0 \
--egress false \
--rule-action allow
# 入站规则 110:允许 HTTPS
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 110 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--egress false \
--rule-action allow
# 入站规则 120:允许 SSH(仅来自办公室)
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 120 \
--protocol tcp \
--port-range From=22,To=22 \
--cidr-block 203.0.113.0/24 \
--egress false \
--rule-action allow
# 入站规则 130:允许临时端口(用于响应)
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 130 \
--protocol tcp \
--port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 \
--egress false \
--rule-action allow
# 出站规则 100:允许 HTTP
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol tcp \
--port-range From=80,To=80 \
--cidr-block 0.0.0.0/0 \
--egress true \
--rule-action allow
# 出站规则 110:允许 HTTPS
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 110 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--egress true \
--rule-action allow
# 出站规则 120:允许临时端口(用于响应)
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 120 \
--protocol tcp \
--port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 \
--egress true \
--rule-action allow
# 关联到子网
aws ec2 replace-network-acl-association \
--association-id $ASSOC_ID \
--network-acl-id $NACL_ID
规则编号策略:
规则编号建议:
├─ 使用 100 的倍数(100, 200, 300...)
├─ 预留空间插入新规则
├─ 低编号 = 高优先级
└─ 最后规则:* (隐式拒绝)
示例:
100 - 允许 HTTP
110 - 允许 HTTPS
200 - 允许 SSH
300 - 拒绝特定 IP
...
32766 - 最后一条自定义规则
32767 - 默认拒绝(AWS 自动添加)
NACL 实战场景
场景 1:阻止恶意 IP:
# 在最前面添加拒绝规则
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 10 \
--protocol -1 \
--cidr-block 198.51.100.0/24 \
--egress false \
--rule-action deny \
--group-rule-description "Block malicious IP range"
# 规则 10 会在规则 100 之前评估
# 因此来自 198.51.100.0/24 的流量会被拒绝
# 即使后续有允许规则
场景 2:私有子网 NACL:
# 创建私有子网 NACL
PRIVATE_NACL=$(aws ec2 create-network-acl \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=Private-Subnet-NACL}]' \
--query 'NetworkAcl.NetworkAclId' \
--output text)
# 入站:允许来自 VPC 内部的流量
aws ec2 create-network-acl-entry \
--network-acl-id $PRIVATE_NACL \
--rule-number 100 \
--protocol -1 \
--cidr-block 10.0.0.0/16 \
--egress false \
--rule-action allow
# 入站:允许临时端口(用于 NAT 响应)
aws ec2 create-network-acl-entry \
--network-acl-id $PRIVATE_NACL \
--rule-number 110 \
--protocol tcp \
--port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 \
--egress false \
--rule-action allow
# 出站:允许所有流量
aws ec2 create-network-acl-entry \
--network-acl-id $PRIVATE_NACL \
--rule-number 100 \
--protocol -1 \
--cidr-block 0.0.0.0/0 \
--egress true \
--rule-action allow
纵深防御架构
多层安全架构:
Internet
│
▼
┌────────────────────────────────┐
│ Internet Gateway │
└───────────┬────────────────────┘
│
┌───────▼────────┐
│ NACL (公有) │ ◄─── 第 1 层:子网级防护
│ ├─ 允许 80 │
│ ├─ 允许 443 │
│ └─ 拒绝特定IP │
└───────┬────────┘
│
┌───────▼────────┐
│ Security Group│ ◄─── 第 2 层:实例级防护
│ ├─ 允许 80 │
│ └─ 允许 443 │
└───────┬────────┘
│
┌───────▼────────┐
│ EC2 Instance │ ◄─── 第 3 层:主机防火墙
│ (iptables/nft)│
└────────────────┘
优势:
├─ 多层防护降低风险
├─ 不同层级的控制粒度
├─ 符合安全最佳实践
└─ 满足合规要求
VPC Flow Logs
Flow Logs 配置
什么是 Flow Logs:
VPC Flow Logs 记录:
├─ IP 流量信息
├─ 可应用于 VPC、子网或 ENI
├─ 发送到 CloudWatch Logs 或 S3
├─ 用于安全审计和故障排查
└─ 不影响网络性能
启用 Flow Logs:
# 1. 创建 CloudWatch 日志组
aws logs create-log-group \
--log-group-name /aws/vpc/flowlogs
# 2. 创建 IAM 角色
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "vpc-flow-logs.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF
ROLE_ARN=$(aws iam create-role \
--role-name VPCFlowLogsRole \
--assume-role-policy-document file://trust-policy.json \
--query 'Role.Arn' \
--output text)
# 3. 附加策略
cat > flow-logs-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
}]
}
EOF
aws iam put-role-policy \
--role-name VPCFlowLogsRole \
--policy-name FlowLogsPolicy \
--policy-document file://flow-logs-policy.json
# 4. 创建 Flow Logs
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /aws/vpc/flowlogs \
--deliver-logs-permission-arn $ROLE_ARN \
--tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=VPC-Flow-Logs}]'
Flow Logs 字段:
日志格式:
version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status
示例日志:
2 123456789012 eni-abc123 172.31.16.139 172.31.16.21 20641 22 6 20 4249 1418530010 1418530070 ACCEPT OK
字段说明:
├─ srcaddr: 源 IP 地址
├─ dstaddr: 目标 IP 地址
├─ srcport: 源端口
├─ dstport: 目标端口
├─ protocol: IANA 协议号(6=TCP, 17=UDP, 1=ICMP)
├─ packets: 数据包数量
├─ bytes: 字节数
├─ action: ACCEPT 或 REJECT
└─ log-status: OK, NODATA, SKIPDATA
Flow Logs 分析
使用 CloudWatch Insights 查询:
# 查找被拒绝的连接
fields @timestamp, srcaddr, dstaddr, srcport, dstport, action
| filter action = "REJECT"
| sort @timestamp desc
| limit 20
# 统计每个源 IP 的流量
fields srcaddr, sum(bytes) as total_bytes
| stats count(*) as connection_count, sum(total_bytes) as total_traffic by srcaddr
| sort total_traffic desc
| limit 10
# 查找异常大量连接的 IP
fields srcaddr, dstaddr
| stats count(*) as connection_count by srcaddr
| filter connection_count > 1000
| sort connection_count desc
安全威胁检测:
常见安全模式:
1. 端口扫描检测:
├─ 同一源 IP 访问大量不同端口
├─ 大量 REJECT 记录
└─ 短时间内大量连接尝试
2. DDoS 攻击检测:
├─ 单一目标的大量连接
├─ 来自多个源的相似流量模式
└─ 异常高的数据包速率
3. 数据渗出检测:
├─ 异常大的出站流量
├─ 非工作时间的大量数据传输
└─ 到未知目标的连接
安全最佳实践总结
设计原则:
1. 纵深防御
├─ NACL + 安全组组合使用
├─ WAF 保护 Web 应用
├─ 主机防火墙作为最后一层
└─ 定期安全审计
2. 最小权限
├─ 仅开放必需的端口
├─ 限制源 IP 范围
├─ 使用安全组引用而非 0.0.0.0/0
└─ 定期审查和清理规则
3. 监控和审计
├─ 启用 VPC Flow Logs
├─ 设置 CloudWatch 告警
├─ 定期分析安全日志
└─ 使用 GuardDuty 威胁检测
4. 网络隔离
├─ 公私子网分离
├─ 多层架构
├─ 使用 PrivateLink 访问 AWS 服务
└─ 避免将数据库暴露到公网
检查清单:
✅ 安全组配置:
├─ 无 0.0.0.0/0 到管理端口
├─ 使用描述性规则名称
├─ 定期审查未使用的规则
└─ 使用引用而非硬编码 IP
✅ NACL 配置:
├─ 阻止已知恶意 IP
├─ 正确配置临时端口
├─ 规则编号有序且有间隔
└─ 文档化规则用途
✅ 监控配置:
├─ 启用 VPC Flow Logs
├─ 配置 GuardDuty
├─ 设置异常流量告警
└─ 定期审查安全事件
✅ 访问控制:
├─ 跳板机用于管理访问
├─ VPN 用于远程访问
├─ SSM Session Manager 替代 SSH
└─ 定期轮换凭证
正确配置安全组和 NACL 是保护 VPC 资源的关键!