CloudFrontとWAFを利用してウェブサーバを構築してみた

[前回](https://qiita.com/buti111/items/f939ba8677cefe2a5903)に引き続き、AWS初心者向けハンズオンから
[Amazon CloudFrontおよびAWS WAFを用いて エッジサービスの活用方法を学ぼう](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-CF_WAF-2022-reg-event.html?trk=aws_introduction_page)を実際に構築してみましたので、内容をかいつまんでお伝えします。

# アーキテクチャ図
ハンズオンで利用するアーキテクチャは次の通りです。
ユーザはWAFで通信を許可するか否かの判定を行い、許可する場合はURLのパスを基にCloudFrontにて接続先の振り分けを行います。静的コンテンツであればS3に、動的コンテンツであればAPI Gatewayに遷移させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/ab2c0958-a702-96c5-ff53-b86842335f46.png)

# 利用しているAWSサービス
###### AWS WAF ######
境界型のウェブアプリケーションファイアウォールです。
CloudFront、Application Load Balancer、AppSync、API Gatewayなどに紐づけることができ、通信内容を検査して不正なアクセスを遮断することができます。

###### Amazon CloudFront ######
CDS（Contents Delivery Service)を提供するサービスで、エッジロケーションで動作します。
ユーザからのリクエストを受け付け、リクエストパスによって接続先のサーバ(オリジンサーバ)への向き先を設定することができます。また、コンテンツの内容をキャッシュするか否かもパスによって指定可能です。
設定項目は大きく以下の３つに分けられる。
* Distribution　１つのWebアプリケーションを表す。BehaviorとOriginの設定をまとめたもの
* Behavior　パスの設定
* Origin　バックエンドとなるオリジンサーバの設定

なお、CloudFrontの設定画面ではリージョンが **グローバル** となっており変更ができません。
このようなサービスは、設定する中でリージョンを指定するか、あらかじめ使用するリージョンが固定で定められているということを意味します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/77731270-36ca-0419-2be1-d8978137085e.png)

###### Amazon S3 ######
静的コンテンツを格納します。

###### Amazon API Gateway ######
動的コンテンツ用のオリジンサーバとして作成します。内部でLambda関数を呼び出します。

###### AWS Lambda ######
リクエストのクエリストリングで受け取った日本語を英語に訳して返却する処理を実装します。

# 構築(ポイントとなる箇所のみ)
### S3 ###
ユーザからのリクエストに従って返却される静的コンテンツをバケットに格納する。
ここでは**20241231-handson-edge**という名前でバケットを作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/ba7ec992-66c3-7e8f-8188-9fe91313055f.png)

CloudFrontからバケットへの読み取りアクセスを許可するため、バケットの**アクセス許可**設定にてバケットポリシーを以下の通り設定します。

```
{
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "AllowCloudFrontServicePrincipal",
                "Effect": "Allow",
                "Principal": {
                    "Service": "cloudfront.amazonaws.com"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::20241231-handson-edge/*",
                "Condition": {
                    "StringEquals": {
                      "AWS:SourceArn": "arn:aws:cloudfront::[アカウントID]:distribution/E1P98BAVG7N8WE"
                    }
                }
            }
        ]
}
```

### CloudFront ###
###### 静的コンテンツ用の設定 ######
Origin  ：上で作成したS3のバケットを設定します。バケットのリージョンをバージニア北部以外で作成した場合はした画像のように明示的にオリジンの指定先にリージョン名(ここでは東京リージョンを指定)を含める必要があるので注意

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/e54d8924-4a5c-8636-19e5-bb9730005155.png)

Behavior：パスパターンを**デフォルト(*)** とし、キャッシュポリシーを**CachingDisabled**（キャッシュしない）に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/576b50b4-2ecb-0bcd-ce12-3c1b6a1b0724.png)

Distribution：ウェブページにアクセスする際のURLにてindex.htmlという入力を省略可能とするため、**Default Root Object**に**index.html**と入力します。

###### 動的コンテンツ用の設定 ######
デフォルトの設定ではCloudFrontはユーザリクエストに設定されたヘッダー、クッキー、クエリストリングをバックエンド側には送らないようになっています。
今回は**input_text**というクエリストリングを設定し、それをlambdaで処理したいので、オリジンリクエストポリシーを新規作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/7250fabe-d23a-863c-4ac0-30f53495231f.png)

Origin：作成したAPI Gatewayを設定します。
Behavior：パスパターンを**api**とし、オリジンリクエストポリシーで上で作成したオリジンリクエストポリシーを設定します。

### Lambda ###
API Gatewayから呼び出される処理を設定します。
```
import boto3

def lambda_handler(event, context):
    translate = boto3.client(service_name='translate', use_ssl=True)
    result = translate.translate_text(Text=event['queryStringParameters']['input_text'], SourceLanguageCode="ja", TargetLanguageCode="en").get('TranslatedText')
    return {
        'statusCode': 200,
        'body': result
    }
```
処理の中でAmazon Translateを利用したいので、Lambdaのロールに対して**TranslateReadOnly**権限を追加します。

### API Gateway ###
API GatewayではリソースとしてHTTPの**GET**メソッドを受け付けて上記のLambda関数を呼び出すように設定します。その際、メソッドリクエストに含まれる**input_text**を渡すLambdaに渡すように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/c9f3b978-8752-7cf7-4753-048647c52dce.png)

### WAF ###
WAFではサービスとの紐づけ、ルールの設定の2つの設定を行います。
紐づけるサービスは上で作成したCloudFrontを設定します。
ルールには**Originates from a country in Japan**の場合に**Block**を設定することで日本からのアクセスをWAFでブロックし、CloudFrontへのアクセスを遮断します。
なお、WAFではAWSや3rdPartyが作成したマネージドルールを設定することも可能で、OWASP Top 10をメインに対応を実施するようなルールもあります。

# 動作確認
###### 静的コンテンツ ######
CloudFrontの設定画面にディストリビューションドメイン名が表示されているので、そちらにアクセスするとデフォルトで設定したindex.htmlが表示されます。
こちらはオリジンをS3に設定し、Behaviorでキャッシュしないように設定しましたので、下の画像の赤枠の通り
x-cache:Miss from cloudfront
となっており、キャッシュがされていないことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/e291b878-8d9c-d945-e760-2165524e0a6d.png)

ここでBehaviorとしてパスパターンを**static/\*** とし、キャッシュするように自作したキャッシュポリシーを設定してからstatic配下の画像ファイルのヘッダを確認すると、キャッシュの結果は
x-cache:Hit from cloudfront
となり、今度はキャッシュされるようになったことがわかります。


###### 動的コンテンツ ######
まずWAFでアクセスを遮断する前(リクエストをCloudFrontが直で受ける)の状態で挙動を確認します。
Input Messageで入力した内容が**input_text**に格納されてAPI Gatewayに渡され、Lambdaで翻訳された結果がOutput Messageに表示されることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/926be807-59ea-364a-3c97-b77b1742164a.png)

WAFで日本からのリクエストを遮断する設定を行った後は画像の通り403 ERRORとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3956392/31a5e1f9-01cf-88fe-b19c-ad83d9defa84.png)


# 感想
今回はWAFを使ったルールベースでのファイヤウォールの設定、および、CloudFrontでのパスの振り分けやキャッシュの設定などにフォーカスしました。
WAFでは様々なルールが事前提供されており、セキュリティもマネージドにすることができる点は利用する側にとって（特にセキュリティ面にあまり知見がない場合には）有用であると感じました。
今後は他にどのような種類のルールが利用できるかも調べていきたいと思います。
それではまた。