🎉

複数のスタックから参照されるAWS WAFのWebACLを書いた (CDK)

2023/06/15に公開

はじめに

API Gatewayに対してAWS WAFを使ってみよう、と思い立ち、こちらのページを参考にしてCDKで構築してみました。
https://dev.classmethod.jp/articles/aws-cdk-create-wafv2-2022-9/

このページで紹介いただいているスタックは、WebACLの作成とリソースの紐付けを1つのスタック内に持っているのですが、

  • リソースごとにWebACLを作るより1つのWebACLを複数のリソースと紐づける方が料金的に有利
  • 複数のリソースでWebACLを使い回すことを考えると、WebACLと同じスタック内で紐付けを行う形をとると使い勝手が悪い

という事情により、WebACLを定義するWAF部分を1つのスタック、紐付けはWebACLを利用するリソースを含むスタック、という構成で使えるように変更してみました。

WAF部分

WebACLに加えてWebACLのARNを格納するパラメータストアを含むスタックにしています。
S3用のバケットは以下コードでは含めてないですが、必要に応じてお好きにどうぞ…

実際のコードから抜粋しているので、このままだと動かないかもですが、見て欲しいところは「WebAclのARNを格納するパラメータストア」というコメントが入っている箇所になります。

なお、スタックの上位側(binディレクトリに置いてあるコード)の方からパラメータストア名を渡して使う形にしています。

waf-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

type WafStackProps = cdk.StackProps & {
  webAclArnParameterName: string;
};

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

    // WAF Web ACL
    const webAcl = new cdk.aws_wafv2.CfnWebACL(this, "wafV2WebAcl", {
      defaultAction: { allow: {} },
      scope: "REGIONAL",
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: "wafV2WebAcl",
      },
      rules: [
        {
          name: "AWSManagedRulesCommonRuleSet",
          priority: 1,
          statement: {
            managedRuleGroupStatement: {
              vendorName: "AWS",
              name: "AWSManagedRulesCommonRuleSet",
            },
          },
          overrideAction: {
            none: {}
          },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            sampledRequestsEnabled: true,
            metricName: "AWSManagedRulesCommonRuleSet",
          },
        },
	// 必要なルールセットはここに追加
      ],
    });

    // WebAclのARNを格納するパラメータストア
    new cdk.aws_ssm.StringParameter(this, 'wafV2WebAclArnParameter', {
      parameterName: props.webAclArnParameterName,
      stringValue: webAcl.attrArn,
    });
  }
}

せっかくのCDKなのに、WAFはL1 constructsしか使えないのがしんどいですね。

WAFを使う側

せっかくCDKでWAF側を書いたのですが、現在使う側はSAMで書かれているAPI Gatewayのみの状況のため、SAMテンプレートの抜粋を記述しておきます。
テンプレートパラメータWafWebAclArnに上のスタックで作成したパラメータストアの名前を指定する形になります。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
  WafWebAclArn:
    Type: 'AWS::SSM::Parameter::Value<String>'
Resources:
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      # いろいろ書く
  Api:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      EndpointConfiguration:
        Type: REGIONAL
      # いろいろ書く
  WebAclAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      ResourceArn: !Sub 'arn:aws:apigateway:${AWS::Region}::/restapis/${Api}/stages/${Api.Stage}'
      WebACLArn: !Ref WafWebAclArn

※初出時、DependsOnApiを指定していましたが、これだとAWS::ApiGateway::RestApiに依存するという記載なのでダメでした。AWS::ApiGateway::Stageに依存させる必要があるため、ResourceArnとしてApi.Stageを使用しています。(2023/08/02修正・追記)

使っているのはパラメータストアなので、CDKでもStringParameter.valueForStringParameterを使って実現可能です。こちらのページをご参照ください。

https://dev.classmethod.jp/articles/aws-cdk-ssm-secrets-manager/

Discussion