You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在AWS CloudFormation中遍历CommaDelimitedList生成WebACL的IPSetReferenceStatement

解决AWS CloudFormation动态生成WebACL OrStatement的IPSet引用问题

核心问题

CloudFormation原生不支持直接遍历列表生成资源属性,你之前尝试的Fn::Contains在Conditions中不可用,逐个配置又过于繁琐,这里提供两种可行方案:


方案1:使用CloudFormation宏(Macro)实现动态遍历

宏是CloudFormation的扩展能力,可通过自定义代码处理模板逻辑,帮你遍历提供商列表自动生成对应的IPSetReferenceStatement。

步骤1:部署自定义宏资源

先创建包含Lambda处理逻辑的宏模板:

AWSTemplateFormatVersion: "2010-09-09"
Resources:
  GenerateIPSetStatementsMacro:
    Type: AWS::CloudFormation::Macro
    Properties:
      Name: GenerateIPSetStatements
      Description: 从提供商列表生成IPSet引用语句
      FunctionName: !GetAtt GenerateIPSetStatementsFunction.Arn

  GenerateIPSetStatementsFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.12
      Handler: index.lambda_handler
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):
              template = event["fragment"]
              params = event["templateParameterValues"]
              country = params["Country"]
              providers = params["ExternalProvidersWhitelist"]
              
              # 生成IPSet引用语句数组
              statements = []
              # 先添加基础白名单(按需保留)
              statements.append({
                  "IPSetReferenceStatement": {
                      "Arn": {"Fn::ImportValue": f"{country}-BaseWhitelist-Arn"}
                  }
              })
              # 遍历提供商列表生成对应语句
              for provider in providers:
                  statements.append({
                      "IPSetReferenceStatement": {
                          "Arn": {"Fn::ImportValue": f"{country}-{provider}-Arn"}
                      }
                  })
              
              # 替换模板中的空Statements数组
              template["Resources"]["WebACL"]["Properties"]["Rules"][0]["Statement"]["OrStatement"]["Statements"] = statements
              
              return {
                  "requestId": event["requestId"],
                  "status": "success",
                  "fragment": template
              }
      Role: !GetAtt MacroExecutionRole.Arn

  MacroExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CloudFormationMacroAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"

步骤2:在主模板中调用宏

修改你的原模板,添加宏调用并预留空的Statements数组:

AWSTemplateFormatVersion: "2010-09-09"
Description: "WAF Configuration"
Transform: [GenerateIPSetStatements] # 调用自定义宏

Parameters:
  Country:
    Type: String
  ExternalProvidersWhitelist:
    Type: CommaDelimitedList
    Description: 需要白名单的外部提供商列表
  DefaultBehaviour:
    Type: String
    AllowedValues: [ allow, block ]

Conditions:
  DefaultBehaviourAllow: !Equals [ !Ref DefaultBehaviour, allow ]

Resources:
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Sub '${Country}-WebACL-common'
      DefaultAction:
        Fn::If: [ DefaultBehaviourAllow, Allow: { }, Block: { } ]
      Scope: CLOUDFRONT
      VisibilityConfig:
        MetricName: !Sub '${Country}-WebACL-common'
        CloudWatchMetricsEnabled: true
        SampledRequestsEnabled: true
      Rules:
        - Name: !Sub '${Country}-WebACL-IPWhitelist'
          Action:
            Allow: { }
          Priority: 1
          VisibilityConfig:
            MetricName: !Sub '${Country}-WebACL-IPWhitelist-metric'
            CloudWatchMetricsEnabled: true
            SampledRequestsEnabled: true
          Statement:
            OrStatement:
              Statements: [] # 由宏动态填充

方案2:使用AWS CDK(更简洁的代码方式)

如果可以切换到CDK,利用编程语言的原生循环能力,实现逻辑会更直观:

import * as cdk from 'aws-cdk-lib';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import { Construct } from 'constructs';

export class WafStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 定义参数
    const country = new cdk.CfnParameter(this, 'Country', { type: 'String' });
    const externalProviders = new cdk.CfnParameter(this, 'ExternalProvidersWhitelist', {
      type: 'CommaDelimitedList',
      description: '需要白名单的外部提供商列表'
    });
    const defaultBehaviour = new cdk.CfnParameter(this, 'DefaultBehaviour', {
      type: 'String',
      allowedValues: ['allow', 'block']
    });

    // 生成IPSet引用语句数组
    const ipSetStatements: wafv2.CfnWebACL.StatementProperty[] = [];
    // 添加基础白名单
    ipSetStatements.push({
      ipSetReferenceStatement: {
        arn: cdk.Fn.importValue(`${country.valueAsString}-BaseWhitelist-Arn`)
      }
    });
    // 遍历提供商列表生成对应语句
    externalProviders.valueAsList.forEach(provider => {
      ipSetStatements.push({
        ipSetReferenceStatement: {
          arn: cdk.Fn.importValue(`${country.valueAsString}-${provider}-Arn`)
        }
      });
    });

    // 创建WebACL
    new wafv2.CfnWebACL(this, 'WebACL', {
      name: `${country.valueAsString}-WebACL-common`,
      defaultAction: cdk.Fn.conditionIf(
        'DefaultBehaviourAllow',
        { allow: {} },
        { block: {} }
      ),
      scope: 'CLOUDFRONT',
      visibilityConfig: {
        metricName: `${country.valueAsString}-WebACL-common`,
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true
      },
      rules: [{
        name: `${country.valueAsString}-WebACL-IPWhitelist`,
        action: { allow: {} },
        priority: 1,
        visibilityConfig: {
          metricName: `${country.valueAsString}-WebACL-IPWhitelist-metric`,
          cloudWatchMetricsEnabled: true,
          sampledRequestsEnabled: true
        },
        statement: {
          orStatement: {
            statements: ipSetStatements
          }
        }
      }]
    });

    // 定义默认行为条件
    new cdk.CfnCondition(this, 'DefaultBehaviourAllow', {
      expression: cdk.Fn.equals(defaultBehaviour.valueAsString, 'allow')
    });
  }
}

注意事项

  1. 使用宏时,需先部署宏的模板,再部署主模板
  2. 宏的Lambda函数仅需要日志权限即可,无需额外资源访问权限
  3. 使用CDK时,需确保已配置好CDK环境并熟悉基础语法

内容的提问来源于stack exchange,提问作者Shashank Goyal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 01:12:42