如何禁用AWS EC2公网直访,仅通过ELB访问且保障健康检查正常?
问题描述
我已经通过ELB的DNS地址成功实现对两台EC2实例的流量负载均衡,但EC2实例也能被公网直接访问,存在安全隐患。需要修改配置,实现:
- 禁止公网直接访问EC2实例
- 保留ELB的正常服务
注意:若将associate_public_ip_address设为false,目标组中的EC2实例会变为“Unhealthy”,导致无法访问任何内容。
解决方案
要实现需求,需从子网架构调整、安全组拆分、NAT网关配置三个维度修改:
1. 拆分公有/私有子网
- 公有子网:部署ALB,关联指向Internet Gateway的路由表,确保ALB能接收公网流量
- 私有子网:部署EC2实例,关联指向NAT Gateway的路由表,确保EC2能访问外网(如执行yum更新)但不直接暴露给公网
2. 独立配置安全组
- ALB安全组:仅允许公网访问80端口(或443,按需),出站允许所有流量
- EC2安全组:仅允许来自ALB安全组的80端口访问,同时仅允许指定IP的SSH访问(用于管理),出站允许所有流量
3. 配置NAT网关
为私有子网的EC2提供外网访问能力,确保user_data中的初始化脚本(如yum安装httpd)能正常执行,避免健康检查失败
修改后的Terraform代码
terraform { required_version = "~> 1.4.4" required_providers { aws = { source = "hashicorp/aws" version = "~> 4.30.0" } } } provider "aws" { profile = "development" region = local.region } # -- VARS ---------------------------------------------------------------------- locals { project = "api" region = "eu-west-1" vpc_cidr = "10.0.0.0/16" # 公有子网(用于ALB) public_subnets = { "a1" = { az = "${local.region}a" cidr = "10.0.1.0/24" }, "b1" = { az = "${local.region}b" cidr = "10.0.2.0/24" } } # 私有子网(用于EC2) private_subnets = { "a2" = { az = "${local.region}a" cidr = "10.0.3.0/24" }, "b2" = { az = "${local.region}b" cidr = "10.0.4.0/24" } } ec2_ami = "ami-0fb2f0b847d44d4f0" // Amazon Linux 2023 AMI 2023.1.20230705.0 x86_64 HVM kernel-6.1 ec2_instance_type = "t2.micro" # 替换为你的管理IP段,用于SSH访问EC2 admin_ip = "192.168.1.0/24" } # -- VPC ----------------------------------------------------------------------- resource "aws_vpc" "vpc" { cidr_block = local.vpc_cidr enable_dns_support = true enable_dns_hostnames = true tags = { Name = "${local.project}-vpc" } } # -- INTERNET GATEWAY ---------------------------------------------------------- resource "aws_internet_gateway" "internet_gateway" { vpc_id = aws_vpc.vpc.id tags = { Name = "${local.project}-internet-gateway" } } # -- 公有子网路由表 ----------------------------------------------------------- resource "aws_route_table" "public" { vpc_id = aws_vpc.vpc.id route { destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.internet_gateway.id } tags = { Name = "${local.project}-public-route-table" } } # -- 私有子网路由表 ----------------------------------------------------------- resource "aws_route_table" "private" { vpc_id = aws_vpc.vpc.id tags = { Name = "${local.project}-private-route-table" } } # -- NAT网关配置 -------------------------------------------------------------- resource "aws_eip" "nat" { vpc = true tags = { Name = "${local.project}-nat-eip" } } resource "aws_nat_gateway" "nat" { allocation_id = aws_eip.nat.id subnet_id = aws_subnet.public["a1"].id tags = { Name = "${local.project}-nat-gateway" } } # 给私有子网路由表添加NAT网关路由 resource "aws_route" "private_nat" { route_table_id = aws_route_table.private.id destination_cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.nat.id } # -- SUBNET -------------------------------------------------------------------- # 公有子网 resource "aws_subnet" "public" { for_each = local.public_subnets vpc_id = aws_vpc.vpc.id cidr_block = each.value.cidr availability_zone = each.value.az # 公有子网自动分配公网IP(给NAT网关用) map_public_ip_on_launch = true tags = { Name = "${local.project}-public-subnet-${each.key}" } } # 私有子网 resource "aws_subnet" "private" { for_each = local.private_subnets vpc_id = aws_vpc.vpc.id cidr_block = each.value.cidr availability_zone = each.value.az # 私有子网不自动分配公网IP map_public_ip_on_launch = false tags = { Name = "${local.project}-private-subnet-${each.key}" } } # 公有子网关联路由表 resource "aws_route_table_association" "public" { for_each = local.public_subnets subnet_id = aws_subnet.public[each.key].id route_table_id = aws_route_table.public.id } # 私有子网关联路由表 resource "aws_route_table_association" "private" { for_each = local.private_subnets subnet_id = aws_subnet.private[each.key].id route_table_id = aws_route_table.private.id } # -- SECURITY GROUPS ----------------------------------------------------------- # ALB安全组:允许公网访问80端口 resource "aws_security_group" "alb" { name = "${local.project}-alb" description = "Allow public inbound traffic to ALB" vpc_id = aws_vpc.vpc.id ingress { description = "Allow public HTTP traffic" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { description = "Allow all outbound traffic" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${local.project}-alb-sg" } } # EC2安全组:仅允许ALB访问80,以及指定IP访问SSH resource "aws_security_group" "ec2" { name = "${local.project}-ec2" description = "Allow traffic from ALB and admin SSH" vpc_id = aws_vpc.vpc.id ingress { description = "Allow SSH from admin IP" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = [local.admin_ip] } ingress { description = "Allow HTTP traffic from ALB security group" from_port = 80 to_port = 80 protocol = "tcp" security_groups = [aws_security_group.alb.id] } egress { description = "Allow all outbound traffic" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${local.project}-ec2-sg" } } # -- EC2 ----------------------------------------------------------------------- resource "aws_instance" "ec2" { for_each = local.private_subnets ami = local.ec2_ami instance_type = local.ec2_instance_type subnet_id = aws_subnet.private[each.key].id vpc_security_group_ids = [aws_security_group.ec2.id] # 不关联公网IP associate_public_ip_address = false user_data = <<-EOF #!/bin/bash sudo su yum update -y yum install httpd -y service httpd start chkconfig httpd on echo "<html><body>Hello from private subnet ${each.key}</body></html>" > /var/www/html/index.html EOF tags = { Name = "${local.project}-ec2-private-${each.key}" } } # -- LOAD BALANCER ------------------------------------------------------------- resource "aws_lb" "application" { name = "${local.project}-application-load-balancer" load_balancer_type = "application" security_groups = [aws_security_group.alb.id] # ALB部署在公有子网 subnets = [for sub in aws_subnet.public : sub.id] } resource "aws_lb_target_group" "application" { name = "${local.project}-http-application" port = 80 protocol = "HTTP" vpc_id = aws_vpc.vpc.id load_balancing_algorithm_type = "least_outstanding_requests" health_check { protocol = "HTTP" port = 80 path = "/" healthy_threshold = 3 unhealthy_threshold = 3 timeout = 10 interval = 30 matcher = 200 } } resource "aws_lb_target_group_attachment" "ec2" { for_each = aws_instance.ec2 port = 80 target_id = each.value.id target_group_arn = aws_lb_target_group.application.arn } resource "aws_lb_listener" "application" { load_balancer_arn = aws_lb.application.arn port = "80" protocol = "HTTP" default_action { type = "forward" target_group_arn = aws_lb_target_group.application.arn } tags = { Name = "${local.project}-application-load-listener" } }
关键修改说明
- 拆分公有/私有子网,ALB部署在公有子网,EC2部署在私有子网,彻底隔离EC2的公网暴露路径
- 独立安全组:ALB面向公网,EC2仅信任ALB的流量,同时限制SSH访问来源
- 配置NAT网关,确保私有子网的EC2能正常访问外网执行初始化脚本,避免健康检查失败
- EC2的
associate_public_ip_address设为false,彻底移除公网IP
内容的提问来源于stack exchange,提问作者BentCoder
相关产品推荐
相关产品推荐

