AWS CDK（ver.2）PythonでCloudFrontにWAFv2を紐づける

AWS CDKを利用して、AWS WAFv2をCloudFrontに紐づける方法について紹介します。なお、本記事では、WAFV2をCloudFrontに紐づけるため、WAFV2はus-east-1のStackとする必要があります。また、CloudFrontはap-northeast-1のStackに含めているため、Cross Region参照用に別途Stackを作成しています。

全コードはGithubを参照してください。
[Githubはここ](https://github.com/ShinyaT1994/cdk-waf-cloudfront.git)

## 前提条件
- AWS CDK v.2 がインストールされていること
- Python 3.x がインストールされていること
- AWSアカウントがあり、AWS CLIが設定されていること

※Cloud9を使うとこの辺りがPassできるため、Cloud9を使って今回の記事の内容は作成しています。

## 構築手順
### 1. CDKアプリの初期化
先ずはCDKアプリの初期化を行います。

```bash
$ mkdir cdk-lambda-rds-proxy
$ cd cdk-lambda-rds-proxy
$ cdk init --language python
```

### 2.必要なパッケージをインストール
Cross Region参照を使うため、requirements.txtに以下を追加します。
```
cdk-remote-stack==2.0.10
```

続いて必要パッケージのインストールになります。
ここでは、CDKアプリを初期化した際に作成された.venvを有効化しています。

```bash
$ source .venv/bin/activate
$ pip install -r requirements.txt
```

### 3. WAFV2スタックの作成
WAFV2の作成については以下の記事にて紹介済みとなるため、参照してください。
[AWS CDK (v2) PythonでWAFv2を使ってベーシック認証を実装する方法](https://qiita.com/rm0063vpedc15/items/0147eac0cc3814f98495)

今回はCloudFrontと紐づけるため、WebACLのARNをOutputとして設定する必要があります。そのため、Stackを定義しているClassの末尾に以下の内容を追加しします。

```python
# WebACLのARNをOutputに設定
cdk.CfnOutput(self, 'WebAclArn', value=webacl.attr_arn)
```

### 4. CloudFrontスタックの作成
CloudFrontの作成についても既に記事にて紹介しているため、参照して下さい。
[AWS CDK Ver.2 PythonでCloudFrontとS3による静的サイトホスティングを作ってみた](https://qiita.com/rm0063vpedc15/items/2fdb60f257f137e13ab1)

今回はWebACLと紐づけるため、DistributionにWebACLの設定を追加します。

```python
distribution = cf.Distribution(
    self,
    'Distribution',
    default_behavior=cf.BehaviorOptions(
        origin=cf_origins.S3Origin(bucket)
    ),
    default_root_object='index.html',
    # Web ACLの設定を追加
    web_acl_id=webacl_arn
)
```

### 5. Cross region参照用のスタックを作成
ap-northeast-1のスタックから直接us-east-1のスタックを参照することはできないため、参照するためのスタックを作成します。
ここでは、WAFのスタックでOutputとして設定した値を受取っています。

```python
from constructs import Construct
from cdk_remote_stack import RemoteOutputs
from aws_cdk import (
    Stack,
)


class CrossRegionReferenceStack(Stack):
    def __init__(
            self, scope: Construct, id: str, wafv2_stack, **kwargs
            ) -> None:
        super().__init__(scope, id, **kwargs)
        
        # 依存関係を定義
        self.add_dependency(wafv2_stack)
        
        # CloudFormation outputを取得
        cfn_outputs = RemoteOutputs(self, 'CfnOutputs', stack=wafv2_stack)
        
        # Web ACL ARN を取得
        self.webacl_arn = cfn_outputs.get('WebAclArn')
```

### 6. app.pyの記述
それぞれのスタックをapp.pyに記述します。

```python:app.py
import aws_cdk as cdk

from cdk_waf_cloudfront.cloudfront import CloudFrontStack
from cdk_waf_cloudfront.cross_region_reference import CrossRegionReferenceStack
from cdk_waf_cloudfront.wafv2 import Wafv2Stack

app = cdk.App()

# ScopeをCloudFrontとするため、us-east-1にStackを作成する
wafv2_stack = Wafv2Stack(
    app, 'Wafv2Stack', env=cdk.Environment(region='us-east-1')
)

# us-east-1のWAFV2とap-northeast-1のCloudFrontを繋ぐためのStack
cross_region_reference_stack = CrossRegionReferenceStack(
    app, 'CrossRegionReferenceStack', wafv2_stack,
    env=cdk.Environment(region='ap-northeast-1')
)

# ap-northeast-1のStackでCloudFrontを作成する
cloudfront_stack = CloudFrontStack(
    app, 'CloudFrontStack', cross_region_reference_stack.webacl_arn,
    env=cdk.Environment(region='ap-northeast-1')
)

app.synth()
```

### 7. cdkをデプロイ
今回はスタックが3つあるため--allをつけてデプロイコマンドを実行します。

```bash
cdk deploy --all
```

デプロイが完了し、CloudFrontのドメインにアクセスするとベーシック認証画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/7c1a858e-6852-6590-41d1-a6279227d646.png)

ログインすると以下の画面となり、CloudFrontにWAFが紐づいていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/8d0eab54-97e3-a764-0ca5-f1415a8e3bb7.png)

## まとめ
今回は異なるリージョンのスタックで作成しているWAFV2とCloudFrontを紐づける方法について紹介しました。ご参考になれば幸いです。