AWS CDK (v2) PythonでWAFv2を使ってベーシック認証を実装する方法

プロジェクトでAWS CDKで開発を行う機会があったため、開発で得た知識を発信しようと思います。
今回の記事では、AWS CDK (v2) Pythonを使ってAWS WAFv2でベーシック認証を実装する方法を紹介します。
なお、CloudFrontに付与するWAFをイメージしているため、WAFはus-east-1に作成します。

全コードはGithubを参照してください
[Githubはここ](https://github.com/ShinyaT1994/cdk-wafv2-basic-auth.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.必要なパッケージをインストール
ここでは、CDKアプリを初期化した際に作成された.venvを有効化しています。

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

### 3.スタックの作成
```python:cdk_wafv2_basic_auth_stack.py
import os

import aws_cdk as cdk

from constructs import Construct
from aws_cdk import (
    Duration,
    Stack,
    App,
    aws_wafv2 as wafv2_,
)

class CdkWafv2BasicAuthStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        # WebACLを作成
        webacl = wafv2_.CfnWebACL(
            self,
            'WebAcl',
            default_action={'allow': {}},
            scope='CLOUDFRONT',
            name='my-webacl',
            visibility_config=wafv2_.CfnWebACL.VisibilityConfigProperty(
                cloud_watch_metrics_enabled=True,
                metric_name='webacl',
                sampled_requests_enabled=True,
            ),
            rules=[
                wafv2_.CfnWebACL.RuleProperty(
                    name='custom-basic-auth-rule',
                    priority=1,
                    statement=wafv2_.CfnWebACL.StatementProperty(
                        not_statement=wafv2_.CfnWebACL.NotStatementProperty(
                            statement=wafv2_.CfnWebACL.StatementProperty(
                                byte_match_statement = \
                                    wafv2_.CfnWebACL.ByteMatchStatementProperty(
                                        field_to_match= \
                                            wafv2_.CfnWebACL.FieldToMatchProperty(
                                                single_header={'name': 'authorization'},
                                            ),
                                        positional_constraint='EXACTLY',
                                        text_transformations= [
                                            wafv2_.CfnWebACL.TextTransformationProperty(
                                                priority=0,
                                                type='NONE'
                                            )
                                        ],
                                        # コマンドライン等で作成したusername:passwordのBase64Encode
                                        search_string='Basic dGVzdDp0ZXN0Cg==',
                                    ),
                            ),
                        ),
                    ),
                    visibility_config=wafv2_.CfnWebACL.VisibilityConfigProperty(
                        cloud_watch_metrics_enabled=True,
                        metric_name='custom-basic-auth-rule',
                        sampled_requests_enabled=True,
                    ),
                    action=wafv2_.CfnWebACL.RuleActionProperty(
                        block=wafv2_.CfnWebACL.BlockActionProperty(
                            custom_response= \
                                wafv2_.CfnWebACL.CustomResponseProperty(
                                    response_code=401,
                                    response_headers=[
                                        wafv2_.CfnWebACL.CustomHTTPHeaderProperty(
                                            name='www-authenticate',
                                            value='Basic'
                                        )
                                    ],
                                ),
                        ),
                    ),
                ),
            ],
        )
```

## 4.app.pyの編集
us-east-1にWAFをデプロイするための設定をapp.pyに追加します。
※コメント文の下のenv=を追加しています。
```python:app.py
CdkWafv2BasicAuthStack(
    app, "CdkWafv2BasicAuthStack",
    # ScopeをCloudFrontとするため、us-east-1にリソースを作成する
    env=cdk.Environment(region='us-east-1')
)
```

## 5.CDKアプリをデプロイ
Stackの準備が完了したため、デプロイします。

```bash
$ cdk deploy
```
デプロイが完了すると、us-east-1にWAFが作成されます。
今回はCloudFrontをScopeとするWAFを作成したため、確認するためにはPull DownからCloudFrontを選択する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/1b1bf340-537e-0f6c-09f0-ff19bbbbe1fc.png)

## まとめ
この記事では、CDK(ver.2) pythonを使って、WAFを作成する方法を紹介しました。
CloudFrontにつけるWAFを作成する場合、CDKではWAFのリソースをus-east-1で作成する必要があるので注意してください。
※WAF Ver.1（Classic WAF）では問題ないようです。

## 参考
[AWS CDK API Reference](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html)