亚马逊AWS官方博客

Global Accelerator IP Prefix 白名单生成和管理

1. 方案总结

该方案旨在为客户构建一个基于 AWS Global Accelerator(AGA)的加速链路,并使用 Network Load Balancer(NLB)作为回源目标的场景下,通过 AWS IP Range 提取 AGA 用来回源 NLB 的 IP 列表,构建 NLB 安全组所需的 AGA IP Prefix,限制 NLB 流量只来自于 AGA,减小攻击面,增强面向公网 NLB 的安全性。

Global Accelerator 是一种联网服务,可以帮助您提高公共应用程序的可用性、性能和安全性。Global Accelerator 提供两个全局静态公共 IP,作为应用程序端点的固定入口点,如应用程序负载均衡器、网络负载均衡器、Amazon Elastic Compute Cloud(EC2)实例和弹性 IP。

由于 NLB 的目标组位于客户的内部数据中心(通过 Direct Connect 连接),为了增加安全性,我们需要在 NLB 的安全组中仅允许来自 AGA 的 IP 地址访问。然而,AWS 目前没有为 AGA 提供托管的 IP Prefix 列表,因此我们需要自动从 AWS IP 范围 URL 获取 AGA 的 IP Prefix,并将其应用于 NLB 的安全组。

注意:本方案建立在 AGA 没有开启客户端 IP 保留的场景下(如 AGA 加速端点为 DX 的场景下,无法开启客户端 IP 保留,效果如下图),如果 AGA 开启了保留客户端 IP 功能,则应用日志中看到的回源 IP 就会被替换成客户端 IP,客户端 IP 并不在本方案提供的 AGA IP Range 中,这里请特别注意。

2. 背景介绍

Global Accelerator 是一种使用 AWS 全球网络提供高性能、Global Accelerator 使用拥有 117 个节点的全球网络,它们位于 51 个国家/地区的 94 个城市。它通过优化网络路径路由优化,最大程度地缩短了客户端和应用程序之间的延迟。

加速链路如下图所示:

在该方案中,客户端请求路径是 AGA 接入,然后通过 NLB 走 Direct Connect,最终把流量发回客户的 IDC。由于 NLB 在使用 Direct Connect 的场景下只能是面向互联网的 NLB 且关闭了客户端 IP 保留,为了增加安全性,要确保只有来自 AGA  的流量才能够访问 NLB,我们需要在 NLB 的安全组中限制来源 IP 的范围,如下图所示:

AWS 目前没有为 AGA 提供托管的 IP Prefix。因此我们需要编写一个自动化脚本,从 AWS 提供的 IP Range 库中(https://ip-ranges.amazonaws.com/ip-ranges.json) 获取 AGA 的 IP Prefix 列表,并将其应用于 NLB 的安全组中,提升面向互联网 NLB 的安全性。

3. 代码解释

该代码核心功能是利用 AWS Lambda 函数(也可以是客户的程序发起)自动管理 AGA 的 IP Prefix列表。

注意:以下代码为逻辑说明为主,请根据自己的需求进行重新编写和调整。

整体代码逻辑如下如所示:

它包括以下几个主要部分:

A) 第一步通过 IP Ranges 提供的 URL 获取 AGA 回源 IP 列表

import json
import urllib.request
url = 'https://ip-ranges.amazonaws.com/ip-ranges.json‘
def get_aga_ip_list(url):
    # 获取JSON数据
    with urllib.request.urlopen(url) as response:
        data = json.loads(response.read().decode())
    # 过滤AGA IP地址
    aga_ips = [prefix['ip_prefix'] for prefix in data['prefixes']
               if prefix['service'] == 'GLOBALACCELERATOR' and prefix['region'] != 'GLOBAL']
    return aga_ips

该函数从 AWS IP 范围 URL 获取 JSON 数据,并过滤出 AGA 的 IP Prefix 列表,代码逻辑参考链接请点击这里

B) 创建并管理 IP Prefix 列表

def lambda_handler_local():
    # 获取AGA IP列表
    aga_ips = get_aga_ip_list(url)
    # 创建EC2客户端
    ec2 = boto3.client('ec2')
    # 定义Prefix列表名称
    prefix_list_name = 'AGA-IP-Prefixes'
    # 尝试描述现有的Prefix列表
    response = ec2.describe_managed_prefix_lists(Filters=[{'Name': 'prefix-list-name', 'Values': [prefix_list_name]}])
 
    if response['PrefixLists']:
        # 如果Prefix列表存在,更新它
        ...
    else:
        # 如果Prefix列表不存在,创建新的
        ...

该函数首先获取 AGA IP 列表,然后检查是否存在名为 “AGA-IP-Prefixes” 的托管 Prefix 列表。如果存在,则更新该 Prefix 列表以反映最新的 AGA IP 范围;否则将创建一个新的 Prefix 列表。

C) 创建新的 Prefix 列表

else:
    # 如果Prefix列表不存在,创建新的
    response = ec2.create_managed_prefix_list(
        PrefixListName=prefix_list_name,
        Entries=[{'Cidr': ip} for ip in aga_ips],
        MaxEntries=len(aga_ips) + 100,  # 为将来的增长留些空间
        AddressFamily='IPv4'
    )
    prefix_list_id = response['PrefixList']['PrefixListId']
    print(f"Created new prefix list: {prefix_list_id}")

如果不存在名为 “AGA-IP-Prefixes” 的 Prefix 列表,该部分代码将创建一个新的 Prefix 列表,并将 AGA IP 范围作为条目添加到其中。

D) 更新现有 Prefix 列表

if response['PrefixLists']:
    # 如果Prefix列表存在,更新它
    prefix_list_id = response['PrefixLists'][0]['PrefixListId']
    current_version = response['PrefixLists'][0]['Version']
 
    # 更新Prefix列表
    a_set = set(aga_ips)
    b_prefixes = ec2.get_managed_prefix_list_entries(PrefixListId=prefix_list_id)['Entries']
    b_set = {prefix['Cidr'] for prefix in b_prefixes}
    a_not_in_b = list(a_set - b_set)
    b_not_in_a = list(b_set - a_set)
    if len(a_not_in_b) > 0 :
        ec2.modify_managed_prefix_list(
            PrefixListId=prefix_list_id,
            CurrentVersion=current_version,
            AddEntries=[{'Cidr': ip} for ip in a_not_in_b]
        )
    if len(b_not_in_a) > 0 :
        ec2.modify_managed_prefix_list(
            PrefixListId=prefix_list_id,
            CurrentVersion=current_version,
            RemoveEntries=[{'Cidr': ip} for ip in b_not_in_a]
        )

该部分代码使用集合操作来比较现有 Prefix 列表中的 IP 范围和最新的 AGA IP 范围。然后添加新的 IP 范围,并删除任何不再属于 AGA 的 IP 范围,这里注意先增加再删除,防止 NLB 无法接收任何请求。

4. Lambda 函数创建和权限

为了在 AWS Lambda 中运行该代码,您需要完成以下步骤:

A) 创建 IAM 角色

首先,您需要创建一个 IAM 角色,并为该角色附加以下策略:

– `AWSLambdaBasicExecutionRole`: 这是 Lambda 函数所需的基本执行角色,角色命名可以可以按照自己的需要编写。

– `AmazonEC2FullAccess`: 代码测试阶段可以添加此策略,该策略授予 Lambda 函数创建、修改和删除 EC2 资源(如托管 Prefix 列表)的权限,测试通过后建议权限可以给的更小一些,比如只授予管理 prefix_lists 相关权限即可。

B) 创建 Lambda 函数

接下来,您需要在 AWS Lambda 控制台中创建一个新的 Python 函数。在创建过程中,请选择您之前创建的 IAM 角色。然后,将上述代码复制粘贴到 Lambda 函数的代码编辑器中。

C) 配置触发器(可选)

您可以选择为 Lambda 函数配置一个触发器,例如 CloudWatch 事件规则,以定期运行该函数。这样可以确保 AGA IP Prefix 列表始终保持最新状态,当然也可以自己构建定时任务,定时运行示例中提供的代码 DEMO。

在 EventBridge 中创建周期性计划并选择刚刚创建的 Lambda 函数定期执行获取新的 AGA IP 列表。

5. 安全最佳实践

除了上述内容外,在启用 AGA 作为加速产品时,您还可以考虑以下安全最佳实践:

为了防御 DDoS 攻击,您可以为 NLB 启用 AWS Shield Advanced 服务。它提供了高级 DDoS 缓解能力,可以有效保护您的应用程序免受多种 DDoS 攻击的影响。

总结

此方案类似于 AWS 托管的 Cloudfront 的 IP Prefix 方案,用类似的思路构建自动获取和更新 AGA 用来回源的 IP Prefix,通过 P Prefix 您可以限制面向公网的 NLB 流量只来自于 AGA,减小面向公网 NLB 的暴露面,增强 NLB 的安全性。

本篇作者

王文巍

亚马逊云科技资深解决方案架构师,10 多年互联网企业研发、团队管理经验,主要专注于电商、新零售、社交媒体等领域。

王骏兴

亚马逊云科技边缘产品架构师,负责亚马逊云科技 Edge 服务领域在中国的技术推广。在 CDN 内容分发以及 WAF 领域拥有多年实战经验,专注于边缘服务设计以及体验优化。