亚马逊AWS官方博客

跨 Region 同步 Amazon WAF Web ACL

使用亚马逊云科技的 WAF 功能的客户,经常需要在多个亚马逊云科技 Region 中部署 Web 应用程序,以确保高可用性、低延迟和遵守合规性要求。亚马逊云科技 Web 应用程序防火墙(WAF)是一项关键的安全服务,可帮助保护 Web 应用程序免受常见 Web 漏洞的攻击。亚马逊云科技的 WAF 分为 Global 和 Regional 两种 Scope,虽然可以利用 Amazon Firewall Manager 实现跨账号跨 Region 的统一配置和管理,但很多客户希望能够更加直接地在 Global 与 Region,Region 与 Region 之间复制 WAF Web ACL。

在本文中,我们将探讨利用亚马逊云科技的 SDK,通过脚本的方法来自动化和简化跨 Region WAF 规则和 Web ACL 的同步过程。

亚马逊云科技现有的跨 Region 复制 WAF 配置的工具和方案

Firewall Manager 是一项集中管理亚马逊云科技防火墙规则的服务,它允许您跨亚马逊云科技组织中的所有账户和资源统一配置和管理 WAF、Amazon Virtual Private Cloud(VPC)安全组以及 Amazon Network Firewall 等防火墙规则。虽然 Firewall Manager 已经提供了许多优秀的功能,但它仍然有进一步改进的空间,例如:

  • 目前 Firewall Manager 的服务是 Region 绑定的,因此用户无法自动将定义的策略部署到所有 Region;
  • 另外 Firewall Manager 中配置的策略目前暂不支持 WAF scope-down 规则。

亚马逊云科技 Firewall Manger 自动化是一套基于 Firewall Manager 的自动化方案,该解决方案简化在 Firewall Manager 中定义策略和配置规则集的过程,并且可以通过配置 Parameter Store 中/FMS/Regions 参数,在多个 Region 自动化创建和部署 Policy。该方案不仅仅可以支持多 Region,还可以支持多 Account/OU 的 Policy 自动化部署和管理,遵循亚马逊云科技最佳实践,适合多账号的组织进行防火墙策略的统一管理。

Firewall Manger 自动化方案很好地解决了组织内的防火墙管理工作,但是对于单账号场景,仅希望做多个 Region 间配置同步,或者需要使用 scope-down 的场景下并不十分适合。

为了实现轻量的 WAF 配置跨 Region 复制能力,其实亚马逊云科技提供了很多工具可以让用户来按照自己需要的工作流进行定制化,比如 Amazon SDKAmazon CLI 等 API 工具,以及 Amazon Lambda 等无服务器化平台。本文后续以 Python Boto3 SDK 为例,介绍实现跨 Region 复制 WAF 配置的方法,并在最后给出整个方案的 github 链接,便于用户直接获取参考,来简化我们的日常工作。

本文方法实现效果

演示中将 Scope 为 CLOUDFRONT 的 WebACL 为源,以 scope 为 REGIONAL 的 WebACL(Region id 为 us-west-2)为目的,做配置同步操作的演示。

演示使用的 WebACL 包含了 IP 集,正则集等相关的 WebACL 资源

现在,在目标 Region 中无任何 Web ACL 配置

WebACL 复制的实现效果

使用本文介绍的脚本,可以完整的将源 Scope 和 Region 的 WebACL 配置复制到目标范围内

脚本运行后,可以查看对应目标 Region

点开拷贝过去的 WebACL,可以看到所有源 WebACL 中的规则配置已经同步拷贝到了目标 Region 中

对比校验复制后的 WebACL 与源 WebACL 的配置实现效果

且此工具还可以支持拷贝过去的 WebACL 的回滚以及原始配置和拷贝后配置的对比

通过每次执行拷贝自动生成的 uuid,可以进行回滚操作

回滚操作的实际效果

目标 Region 中的被复制的配置,可以通过回滚的脚本进行删除

工作流程设计和考量

Web ACL 的组成部份

在进行实践之前,我们先来看一下亚马逊云科技 WAF 的 WebACL 中包含哪些组成部份,以及他们的嵌套关系。

Web ACL

Web 访问控制列表(ACL),最终和资源关联的控制列表。也是此文中希望达成进行跨 Region 复制的目标资源。ACL 是一众规则和规则组的集合。按照用户定义的优先级,进行规则或者规则组的逐条检查。

该资源为亚马逊云科技 WAF 的资源类型,有其 Amazon 资源名称(ARN)。

IP 集和正则表达式模式集

WAF 将一些更复杂的信息存储在集合中,您可以通过在规则中引用这些信息来使用这些信息。其中每个集都有一个名称,并在创建时分配了一个 ARN,这部分的集合包含两个类型:

  • IP 集:要在规则语句中一起使用的 IP 地址和 IP 地址范围的集合,为亚马逊云科技具体资源,有分配其 ARN。
  • 正则表达式模式集:提供了要在规则语句中一起使用的正则表达式的集合,为亚马逊云科技具体资源,有分配其 ARN。

规则(Rules)

每条规则包含其定义的条件检查语句,规则只能在规则组或者 Web ACL 之内存在。其不算做 WAF 的具体资源,因此对于规则,没有 ARN。

规则组(Rule Group)

添加到 Web ACL 的一组可重复使用的规则,规则组是 Web ACL 中的常用组件,为亚马逊云科技资源,有 ARN,其类型有如下几种:

  • 用户自己定义的规则组
  • 由亚马逊云科技托管的规则
  • 亚马逊云科技 marketplace 的规则组
  • 由其他服务(例如 Firewall Manger 和 Amazon Shield Advanced)拥有和管理的规则组

规则组和 WebACL 都是规则的集合,但是有多个区别,可以参考链接中的描述,其中和配置复制相关的一些特点如下:

  • 规则组不能包含规则组引用语句。也就意味着规则组中不支持再嵌套其他规则组。由于没有嵌套内容,因此对规则组做复制的脚本逻辑比 Web ACL 简化很多。

以上涉及到的资源的嵌套关系可以汇总如下:

通过以上的汇总,我们可以了解,在复制 Web ACL 中,如果存在 Web ACL 中使用了规则组、IP 集、正则表达式集时,需要优先在目标 Region 创建与源 Region 对应的这些资源,才可以将 Web ACL 以相同的配置拷贝到目标 Region。同时,我们通过上面对于各资源类型的汇总可以发现,这些需要预先创建的资源都有其对应的 ARN,而且,这些在目标 Region 创建的资源的 ARN 是与源 Region Web ACL 中嵌套的资源的 ARN 是不同的,在进行 Web ACL 跨 Region 复制时,需要将 ARN 替换为对应目标的 ARN。

因此进行跨 Region 的 WAF 同步时,将整个工作流进行拆分,并将其用到的 Python Boto3 API 对应汇总的结果如下:

步骤 内容 用到的 boto3 API
1 获取源 Region 的 Web ACL 的配置 WAFV2.Client.get_web_acl(**kwargs)
2 从获取的源 WebACL 配置中,检索获取其嵌套的资源(规则组、IP 集、正则表达式集)
3 依照获取的嵌套资源的 name、ARN,获取对应资源的配置 WAFV2.Client.get_rule_group(**kwargs)
WAFV2.Client.get_ip_set(**kwargs)
WAFV2.Client.get_regex_pattern_set(**kwargs)
4 依照 3 中获取的嵌套资源的配置,在目的 Region 创建对应的资源 WAFV2.Client.create_regex_pattern_set(**kwargs)
WAFV2.Client.create_web_acl(**kwargs)
WAFV2.Client.create_rule_group(**kwargs)
5 依照获取 1 中获取的 Web ACL 的配置,在目的 Region 创建对应的 Web ACL,使用 4 中创建的资源作为其嵌套资源 WAFV2.Client.create_ip_set(**kwargs)
6(可选) 对比 1 中的配置和 5 中的配置,确认复制是否有不匹配项目

设计考量

在使用 Boto3 API 进行实现时,本例中,有一些设计和实现上的取舍以及注意的内容,详细的安装配置,可以参考 https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html

1)在使用 Boto3 SDK 时,如果使用亚马逊云科技 CLI 进行 Authentication Credentials 的设置,那么在初始化 Boto3 WAFV2 client 时,会使用亚马逊云科技 CLI 配置时默认指定的 Region。

import boto3
client = boto3.client('wafv2')

但是我们进行配置跨 region 拷贝时,需要使用 client 去连接获取不同的 region 的资源。因此可以使用 Region 参数手动修改我们的 client 的 Region 属性。

wafv2_client = boto3.client('wafv2', Region_name=<Region>)

另外,WAFV2 的 boto3 client 的参数中,有 Scope Region 的概念。

其中 Scope 有两个可选值,CLOUDFRONT REGIONAL,分别对应部署在 Amazon Cloudfront 资源上的 Web ACL 以及除 Cloudfront 之外的 Regional 资源,比如 ALB 或者 Amazon API Gateway。按照 Boto3 中的规定,scope:CLOUDFRONT 对应的 API endpoint 必须是 us-east-1 Region,因此对应的 client 的 Region 参数要为 us-east-1。scope:REGIONAL 对应的 endpoint 以资源实际所在的 Region 为准。

2)为了简化整个实现的逻辑,在本文给的示例中,在目标 Region 创建资源前,不去判断目标 Region 是否存在可以复用的有完全相同配置的资源(如 IP 集、正则表达式集等)存在可以复用。而是直接在目标 Region 创建对应的资源,使用描述或者资源名字中添加特定的尾缀用于标识。这仅代表本文中示例的实现方式,并不代表是最佳实践或者唯一方式,尽可按照自己业务需求进行设计符合自己业务场景的实现方式。这种实践方式可能会遇到 IP 集,正则表达式集或者 Web ACL 的配额的限制。在使用时需注意目标 region 的配额情况。

3)规则组也会引用 IP 集和正则表达式集等资源,因此获取了 Web ACL 中内嵌的规则集后,需要检查每个规则集中的IP集和正则表达式集的嵌套情况,以免有遗漏。

4)在应对包含了亚马逊云科技 ManagedRulesACFPRuleSet 或者亚马逊云科技 ManagedRulesATPRuleSet 规则集的 Web ACL 复制时,需要注意,这两个规则组中的 ResponseInspection 部分只在 scope 是 CLOUDFRONT 的 WebACL 中存在。因此在做包含以上两个规则组的 WebACL 的跨 scope 的复制时,需要注意这部分的处理。本示例中的处理方法如下:

  • 如果是在 scope 是 CLOUDFRONT REGIONAL 的场景,直接删除 ResponseInspection 部分
  • 如果是 scope 从 REGIONAL CLOUDFRONT 的场景,填充一个默认的 ResponseInspection,本例子中使用的是 {"StatusCode": {"SuccessCodes": [200], "FailureCodes": [401]}}

https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/aws-properties-wafv2-webacl-awsmanagedrulesacfpruleset.html

具体实现方式

以下将使用 Boto3 Python SDK 为例,展示实现方法。

在运行前,先定义全局变量,在整个脚本运行过程中,提供变量存储和 client 对象,这里的变量,主要用于存储源 WebACL 中的嵌套资源的 ARN,以及在目的 Region 中创建的新的嵌套资源的 ARN,两者成对存放在对应的变量中,便于在最后在目的 Region 创建 Web ACL 时进行调用,用于逐个将源 WebACL 配置文件中的源 Region 中的嵌套资源 ARN 替换成目标 Region 中的对应资源的 ARN;同时将替换后生成的新的 WebACL 配置文件作为目的 Reigon 的 Web ACL 的配置文件提交给 API 进行最终创建。

    IPSETARN = {}
    REGEXSETARN = {}
    RULEGROUPARN = {}
    CREATEDRESOURCE = {}
    src_waf_client = boto3.client('wafv2', Region_name=src_Region)
    dst_waf_client = boto3.client('wafv2', Region_name=dst_Region)

首先按照规划的步骤 1:

通过 WAFV2.Client.get_web_acl(**kwargs) API 来获取需要复制的源 WebACL 的详细信息。

get_web_acl() 会反馈 JSON 格式的 Web ACL 的详细信息,详细请参考 https://docs.aws.amazon.com/zh_cn/waf/latest/APIReference/API_GetWebACL.html。这里我们主要关注的是 Web ACL 中的 Rules,如果环境中还有 Firewall Manager 的场景,需要考虑 PostProcessFirewallManagerRuleGroups 等参数,出于简化和示例的目的,本例中只考虑用户使用 WAF 创建的 Web ACL 的场景。在 Rules 下的 Statements 中筛选如下几个关键参数是否存在

  • RuleGroupReferenceStatement
  • IPSetReferenceStatement
  • RegexPatternSetReferenceStatement

这三个 statements 中包含对于规则组、IP 集和正则表达式集相关的引用。而这三个引用是有包含资源 ARN 在配置中的,需要将其找出,并记录,参考脚本如下:

    for item in rules:
        statement = item['Statement']
        if 'IPSetReferenceStatement' in statement:
            ip_set_name, ip_set_id = statement['IPSetReferenceStatement']['ARN'].split('/')[-2:]
            if ip_set_name in ipset:
                continue
            else:
                print('IPSET:Name: %s, ID: %s ' % (ip_set_name, ip_set_id))
                ipset[ip_set_name] = ip_set_id
        if 'RegexPatternSetReferenceStatement' in statement:
            regex_set_name, regex_set_id = statement['RegexPatternSetReferenceStatement']['ARN'].split('/')[-2:]
            if regex_set_name in regset:
                continue
            else:
                print('REGSET:Name: %s, ID: %s ' % (regex_set_name, regex_set_id))
                regset[regex_set_name] = regex_set_id
        if process_rulegroup and 'RuleGroupReferenceStatement' in statement:
            rule_group_name, rule_group_id = statement['RuleGroupReferenceStatement']['ARN'].split('/')[-2:]
            if rule_group_name in rulegroup:
                continue
            else:
                print('RULEGROUP:Name: %s, ID: %s ' % (rule_group_name, rule_group_id))
                rulegroup[rule_group_name] = [statement['RuleGroupReferenceStatement']['ARN'], rule_group_id]

然后按照步骤 2 至 4:

基于上一步中获取的对应资源信息,通过:

  • Client.get_rule_group(**kwargs)
  • Client.get_ip_set(**kwargs)
  • Client.get_regex_pattern_set(**kwargs)

API 进行每个对应资源的详细配置信息的获取,获取后,由于这三类资源内除了 RuleGroup 外,并再无其他嵌套资源,可以使用 Boto3 API,以源资源的配置作为目标 Region 资源的配置信息进行资源创建。如有需要可以使用自定义的命名和描述来进行新创建的资源的标识。

创建后,将新建资源的 ARN 等相关信息与源资源的 ARN,成对存放在对应的全局变量中,以备后续使用。

由于 RuleGroup 中会包含嵌套的资源,因此对于 Web ACL 中获取的嵌套的 RuleGroup,需要再对其进行一次其包含的嵌套的资源的获取,同样可以基于:

  • IPSetReferenceStatement
  • RegexPatternSetReferenceStatement

进行筛选,然后再进行配置获取以及目标 Region 的资源创建。

最后按照步骤 5:

基于上述操作,已经在目标 Region 中将源 WebACL 中的嵌套的资源拷贝到了目标 Region 中,只剩将 Web ACL 本身进行拷贝。

拷贝源 Web ACL 的配置后,需要使用已经在目标 Region 中创建好的新的嵌套资源的 ARN 对源 Web ACL 的配置进行更新,可以参考下面的示例,在源 Web ACL 中基于:

  • RuleGroupReferenceStatement
  • IPSetReferenceStatement
  • RegexPatternSetReferenceStatement

进行筛选,并基于保存好的源嵌套资源和新建嵌套资源的 ARN 信息进行对应替换。

def update_ARN(Rules):
    """
    :param Rules: 字典
    :return: 字典

    基于几个global变量中存储的ARN 键-值对,替换源waf acl的json配置中使用的资源的ARN为目的waf acl的Region中新创建的对应资源的ARN
    global 变量的ARN键值对的格式是
    "源资源ARN":"目的资源ARN"

    """
    global REGEXSETARN
    global RULEGROUPARN
    global IPSETARN
    for i in range(len(Rules)):
        rule = Rules[i]
        statement = rule['Statement']
        if 'IPSetReferenceStatement' in statement:
            src_ipset_arn = statement['IPSetReferenceStatement']['ARN']

            if src_ipset_arn in IPSETARN:
                dst_ipset_arn = IPSETARN[src_ipset_arn]
                Rules[i]['Statement']['IPSetReferenceStatement']['ARN'] = dst_ipset_arn
            else:
                continue
        if 'RuleGroupReferenceStatement' in statement:
            src_rule_group_arn = statement['RuleGroupReferenceStatement']['ARN']

            if src_rule_group_arn in RULEGROUPARN:
                dst_rule_group_arn = RULEGROUPARN[src_rule_group_arn]
                Rules[i]['Statement']['RuleGroupReferenceStatement']['ARN'] = dst_rule_group_arn
            else:
                continue
        if 'RegexPatternSetReferenceStatement' in statement:
            src_regex_arn = statement['RegexPatternSetReferenceStatement']['ARN']

            if src_regex_arn in REGEXSETARN:
                dst_regex_arn = REGEXSETARN[src_regex_arn]
                Rules[i]['Statement']['RegexPatternSetReferenceStatement']['ARN'] = dst_regex_arn
            else:
                continue

    return Rules

替换后,最后,使用 WAFV2.Client.create_ip_set() API 进行目标 Region 的 Web ACL 创建,即可完成整个工作流。

由于 Web ACL 中内容众多,牵扯的条目和嵌套资源复杂,因此为了保证源和目的的拷贝的结果的一致性。本例中使用了 deepdiff 模块来对比了源和目的配置的差异,便于用户检查拷贝的结果。

由于在本文的实践中,是将所有创建的资源(比如 IP 集、规则组、正则表达式集、WebACL 等),以及原始 Web ACL 的详细配置都通过脚本存储在了云上的 S3 桶中,便于后续检查和留档。因此代码实践中会从 S3 中读取在运行中存储在 S3 中的信息。参考代码如下:

def compare_src_dst(unique_id,web_acl_name,dst_scope,dst_waf_client,temp_data):
    """

    :param unique_id: str
    :param web_acl_name: str
    :param dst_waf_client: boto3client
    :param temp_data: dict
    :return:
    """
    src_file_name = unique_id + '_WebACL_' + web_acl_name
    s3_config = Config(
        s3={
            'use_accelerate_endpoint': False
        }
    )
    s3 = boto3.client('s3',config=s3_config)
    bucket_name = 'your S3 bucket name'
    object_key = 'your s3 bucket path/%s.json' % (src_file_name)
    # 从S3获取对象
    response = s3.get_object(
        Bucket=bucket_name,
        Key=object_key
    )
    # 读取对象的内容
    json_data = response['Body'].read().decode('utf-8')
    # 将JSON字符串加载为Python字典
    src_json = json.loads(json_data)
    dst = dst_waf_client.get_web_acl(
            Scope=dst_scope,
            Name=temp_data['Name'],
            Id=temp_data['Id']
        )
    ddiff = DeepDiff(src_json, dst, ignore_order=True,ignore_string_type_changes=True)
    pprint.pprint(ddiff)
    return None

以上便是实现整个跨 Region 同步 Web ACL 配置的过程。

回滚的实现方式

本文中的实践,通过在脚本中定义了一个随机 ID 来统一标识脚本的一次执行,如文章开始的展示中截图显示的。

单次执行中创建的所有的资源(比如 IP 集、规则组、正则表达式集、Web ACL 等),以及原始 Web ACL 的详细配置都以该标识作为文件名标记,存储在了 S3 桶中。

后续只要调用预先编写好的回滚脚本,给定特定的标识 ID,就可以对该次 ID 标识的 WAF 同步操作中创建的所有资源进行删除回滚。

详细的脚本和参考代码可以参考 github 连接中的代码 https://github.com/dzkd2007/waf_crossRegion_copy

实践经验总结

本文提供了一种跨 Region 进行 WAF 同步的实现方式,通过使用亚马逊云科技丰富全面的 API、SDK 的能力,以 Python Boto3 SDK 为例,给出了通过脚本实现 WAF 配置同步的方式。文中在给出实践细节前,首先对 WAF 配置的组成元素进行了分析,给出了资源之间的嵌套关系,以便基于资源间的嵌套关系,确定整体的脚本实现思路和步骤。此后,对实现上 WAF 配置中的一些细节和取舍进行了拆解,并给出了本文实践使用的方法供参考。希望自行编写或者希望将其引入自建运营自动化平台的读者可以借鉴其中的某些实现。最后,详细地解释了具体的实现步骤和对应使用的 API 以及参数,便于本文读者理解,或者按照自己的业务逻辑进行改写。

通过本文的分享,读者可以快速获取一个开箱即用的脚本用于日常的 WAF 同步工作,并且在基于本文内容了解了整体的实现机制后,为读者按照其业务进行自定义或者再开发提供了参考。

本篇作者

裴峰

亚马逊云科技合作伙伴解决方案架构师,主要负责合作伙伴架构咨询和方案设计,同时致力于 AWS 云服务在国内的应用及推广,有多年大型企业数据中心网络架构设计实战经验。