如何在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') }); } }
注意事项
- 使用宏时,需先部署宏的模板,再部署主模板
- 宏的Lambda函数仅需要日志权限即可,无需额外资源访问权限
- 使用CDK时,需确保已配置好CDK环境并熟悉基础语法
内容的提问来源于stack exchange,提问作者Shashank Goyal
相关产品推荐
相关产品推荐

