亚马逊AWS官方博客

使用更具体的 Amazon VPC 路由检查子网到子网的流量

自 2019 年 12 月以来,Amazon Virtual Private Cloud(VPC)允许您将所有进站流量(也称为南北流量)路由到特定网络接口。您可能出于多种原因使用此功能。例如,使用入侵检测系统(IDS)设备检测进站流量或将进站流量路由到防火墙。

自我们推出此功能以来,许多用户要求我们提供类似的功能来分析从一个子网流向 VPC 内的另一个子网的流量,也称为东西流量。到今天为止,这仍然是不可能的,因为路由表中的路由不能比默认本地路由更具体(有关更多详细信息,请查看 VPC 文档)。简单地说,这意味着任何一个路由的目标使用的 CIDR 范围都不能小于默认本地路由(即整个 VPC 的 CIDR 范围)。例如,当 VPC 范围为 10.0.0/16 且子网有 10.0.1.0/24 时,通向 10.0.1.0/24 的路由比通向 10.0.0/16 的路由更具体。

路由表不再有此限制。路由表中的路由可以有比默认本地路由更具体的路由。您可以使用此类更具体的路由将所有流量发送到专用设备或服务,以检测、分析或过滤两个子网之间的所有流量(东西流量)。路由目标可以是连接到您构建或购买的设备的网络接口(ENI)、出于性能或高可用性原因将流量分配到多个设备的 AWS 网关负载均衡器(GWLB)终端节点、AWS Firewall Manager 终端节点或 NAT 网关。它还允许在子网和 AWS Transit Gateway 之间插入设备。

可以将设备链接起来,以便在源子网和目标子网之间进行多种类型的分析。例如,您可能希望首先使用防火墙(AWS 托管防火墙或第三方防火墙设备)筛选流量,然后将流量发送到入侵检测和防御系统,最后,执行深度数据包检测。您可以从我们的 AWS 合作伙伴网络AWS Marketplace 访问虚拟设备。

链接设备时,每个设备和每个终端节点都必须位于单独的子网中。

让我们动手试试这个新功能。

工作原理
在本博客文章中,我们假设我有一个具有三个子网的 VPC。第一个子网是公有子网,有一个堡垒主机。它需要访问资源,例如 API 或第二个子网中的数据库。第二个子网是私有子网。它托管堡垒所需的资源。我写了一个简单的 CDK 脚本来帮助您部署此设置。

更具体的 VPC 路由

出于合规性原因,我们公司要求此私有应用程序的流量流经入侵检测系统。CDK 脚本还创建了第三个子网(私有子网)来托管网络设备。它提供了三个 Amazon Elastic Compute Cloud(Amazon EC2)实例:堡垒主机、应用程序实例和网络分析设备。该脚本还创建了 NAT 网关,允许引导应用程序实例并使用 AWS Systems Manager Session Manager (SSM)连接到三个实例。

因为这是一个演示,所以网络设备是配置为 IP 路由器的常规 Amazon Linux EC2 实例。在现实生活中,您可能要使用我们的合作伙伴在 AWS Marketplace 上提供的众多设备之一,或者网关负载均衡器终端节点或 Network Firewall。

让我们修改路由表以通过设备发送流量。

使用 AWS 管理控制台AWS 命令行界面(CLI),我向 10.0.0.0/2410.0.1.0/24 子网路由表添加了更具体的路由。这些路由指向 eni0,即流量检测设备的网络接口。

使用 CLI,我首先收集设备的 VPC ID、子网 ID、路由表 ID 和 ENI ID。

VPC_ID=$(aws                                                    \
    --region $REGION cloudformation describe-stacks             \
    --stack-name SpecificRoutingDemoStack                       \
    --query "Stacks[].Outputs[?OutputKey=='VPCID'].OutputValue" \
    --output text)
echo $VPC_ID

APPLICATION_SUBNET_ID=$(aws                                                                      \
    --region $REGION ec2 describe-instances                                                      \
    --query "Reservations[].Instances[] | [?Tags[?Key=='Name' && Value=='application']].NetworkInterfaces[].SubnetId" \
    --output text)
echo $APPLICATION_SUBNET_ID

APPLICATION_SUBNET_ROUTE_TABLE=$(aws                                                             \
    --region $REGION  ec2 describe-route-tables                                                  \
    --query "RouteTables[?VpcId=='${VPC_ID}'] | [?Associations[?SubnetId=='${APPLICATION_SUBNET_ID}']].RouteTableId" \
    --output text)
echo $APPLICATION_SUBNET_ROUTE_TABLE

APPLIANCE_ENI_ID=$(aws                                                                           \
    --region $REGION ec2 describe-instances                                                      \
    --query "Reservations[].Instances[] | [?Tags[?Key=='Name' && Value=='appliance']].NetworkInterfaces[].NetworkInterfaceId" \
    --output text)
echo $APPLIANCE_ENI_ID

BASTION_SUBNET_ID=$(aws                                                                         \
    --region $REGION ec2 describe-instances                                                     \
    --query "Reservations[].Instances[] | [?Tags[?Key=='Name' && Value=='BastionHost']].NetworkInterfaces[].SubnetId" \
    --output text)
echo $BASTION_SUBNET_ID

BASTION_SUBNET_ROUTE_TABLE=$(aws \
 --region $REGION ec2 describe-route-tables \
 --query "RouteTables[?VpcId=='${VPC_ID}'] | [?Associations[?SubnetId=='${BASTION_SUBNET_ID}']].RouteTableId" \
 --output text)
echo $BASTION_SUBNET_ROUTE_TABLE

接下来,我会添加两条更具体的路由。一条路由通过设备网络接口将来自堡垒公有子网的流量发送到应用程序私有子网。 第二条路由与路由回复的方向相反。它通过设备网络接口将更具体的流量从应用程序私有子网路由到堡垒公有子网。 感到困惑? 让我们看看下图:

更具体的 VPC 路由

首先,让我们修改堡垒路由表:

aws ec2 create-route                                  \
     --region $REGION                                 \
     --route-table-id $BASTION_SUBNET_ROUTE_TABLE     \
     --destination-cidr-block 10.0.1.0/24             \
     --network-interface-id $APPLIANCE_ENI_ID

接下来,让我们修改应用程序路由表:

aws ec2 create-route                                  \
    --region $REGION                                  \
    --route-table-id $APPLICATION_SUBNET_ROUTE_TABLE  \
    --destination-cidr-block 10.0.0.0/24              \
    --network-interface-id $APPLIANCE_ENI_ID

我还可以使用 Amazon VPC 控制台进行这些修改。只需从 Routes (路由) 选项卡中选择“Bastion”(堡垒) 路由表,然后单击 Edit routes (编辑路由) 即可。MSR:选择路由表

我添加了一个路由,用于将 10.0.1.0/24(应用程序子网)的流量发送到设备 ENI(eni-055...)。MSR:创建路由

下一步是定义相反的回复路由,将 10.0.0.0/24 的流量从应用程序子网发送到设备 ENI(eni-05...)。 完成后,应用程序子网路由表应如下所示:

MSR:最终路由表

配置设备实例
最后,我将设备实例配置为转发其接收的所有流量。您的软件设备通常会为您完成此操作。当您使用 AWS Marketplace 设备或我为此演示提供的 CDK 脚本创建的实例时,无需执行额外步骤。如果您使用的是普通 Linux 实例,请完成以下两个额外步骤:

1.连接到 EC2 设备实例并在内核中配置 IP 流量转发:

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

2.将 EC2 实例配置为接受除本身之外的其他目标的流量(称为源/目标检查):

APPLIANCE_ID=$(aws --region $REGION ec2 describe-instances                     \
     --filter "Name=tag:Name,Values=appliance"                                 \
     --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \
     --output text)

aws ec2 modify-instance-attribute --region $REGION     \
                         --no-source-dest-check        \
                         --instance-id $APPLIANCE_ID

测试设置
设备现已做好将流量转发到其他 EC2 实例的准备。

如果您使用的是演示设置,则堡垒主机上未安装 SSH 密钥。通过 AWS Systems Manager Session Manager 进行访问。

BASTION_ID=$(aws --region $REGION ec2 describe-instances                      \
    --filter "Name=tag:Name,Values=BastionHost"                               \
    --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \
    --output text)

aws --region $REGION ssm start-session --target $BASTION_ID

连接到堡垒主机后,发出以下 cURL 命令以连接到应用程序主机:

sh-4.2$ curl -I 10.0.1.239 # use the private IP address of your application host
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Mon, 24 May 2021 10:00:22 GMT
Content-Type: text/html
Content-Length: 12338
Last-Modified: Mon, 24 May 2021 09:36:49 GMT
Connection: keep-alive
ETag: "60ab73b1-3032"
Accept-Ranges: bytes

要验证流量是否真正流经设备,您可以再次对实例启用源/目标检查。将 --source-dest-check 参数与上面的 modify-instance-attribute CLI 命令一起使用。当源/目标检查启用时,流量将受阻。

我还可以连接到设备主机并使用 tcpdump 命令检测流量。

(on your laptop)
APPLIANCE_ID=$(aws --region $REGION ec2 describe-instances     \
                   --filter "Name=tag:Name,Values=appliance" \
		   --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" \
  		   --output text)

aws --region $REGION ssm start-session --target $APPLIANCE_ID

(on the appliance host)
tcpdump -i eth0 host 10.0.0.16 # the private IP address of the bastion host

08:53:22.760055 IP ip-10-0-0-16.us-west-2.compute.internal.46934 > ip-10-0-1-104.us-west-2.compute.internal.http: Flags [S], seq 1077227105, win 26883, options [mss 8961,sackOK,TS val 1954932042 ecr 0,nop,wscale 6], length 0
08:53:22.760073 IP ip-10-0-0-16.us-west-2.compute.internal.46934 > ip-10-0-1-104.us-west-2.compute.internal.http: Flags [S], seq 1077227105, win 26883, options [mss 8961,sackOK,TS val 1954932042 ecr 0,nop,wscale 6], length 0
08:53:22.760322 IP ip-10-0-1-104.us-west-2.compute.internal.http > ip-10-0-0-16.us-west-2.compute.internal.46934: Flags [S.], seq 4152624111, ack 1077227106, win 26847, options [mss 8961,sackOK,TS val 4094021737 ecr 1954932042,nop,wscale 6], length 0
08:53:22.760329 IP ip-10-0-1-104.us-west-2.compute.internal.http > ip-10-0-0-16.us-west-2.compute.internal.46934: Flags [S.], seq 4152624111, ack 1077227106, win 26847, options [mss 

清理
如果您使用了我为此博文提供的 CDK 脚本,请务必在完成后运行 cdk destroy,这样就无需为我用于此演示的三个 EC2 实例和 NAT 网关付费。在 us-west-2 中运行演示脚本的费用为每小时 0.062 美元。

注意事项。
在使用更具体的 VPC 路由时,请记住以下几点:

  • 您要向其发送流量的网络接口或服务终端节点必须位于专用子网中。它不能位于流量的源子网或目标子网中。
  • 您可以将设备链接起来。每台设备必须位于其专用子网中。
  • 您添加的每个子网都会占用一个 IP 地址块。 如果您使用的是 IPv4,请注意所用的 IP 地址数量(一个 /24 子网使用来自您的 VPC 的 256 个地址)。子网中允许的最小 CIDR 范围是 /28,它只使用 16 个 IP 地址。
  • 设备的安全组必须有规则接受所需端口上的传入流量。同样,应用程序的安全组必须授权来自设备安全组或 IP 地址的流量。

此新功能在所有 AWS 区域均可使用,无需额外付费。

您可以立即开始使用。