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

# はじめに
API Gatewayに対してAWS WAFを使ってみよう、と思い立ち、[こちらのページ](https://dev.classmethod.jp/articles/aws-cdk-create-wafv2-2022-9/)を参考にして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ディレクトリに置いてあるコード)の方からパラメータストア名を渡して使う形にしています。

```typescript: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`に上のスタックで作成したパラメータストアの名前を指定する形になります。

```yaml: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/Prod'
      WebACLArn: !Ref WafWebAclArn
    DependsOn: Api
```

使っているのはパラメータストアなので、CDKでも`StringParameter.valueForStringParameter`を使って実現可能です。[こちらのページ](https://dev.classmethod.jp/articles/aws-cdk-ssm-secrets-manager/)をご参照ください。

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