Amazon CloudFrontおよびAWS WAFを用いてエッジサービスを構築する

## はじめに
この記事は”AWS Hands-on for BeginnersAmazon CloudFrontおよびAWS WAFを用いて エッジサービスの活用方法を学ぼう”を
参考に記事にしました。

## 今回構築するアーキテクチャ
今回構築するアーキテクチャは以下のようになっている。

![AWS Cloud (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/2c24658a-763e-8a2e-547d-3d1f2d1178c1.png)


## AWSグローバルインフラストラクチャとエッジサービスについて
AWS クラウドは、全世界 26 の地域にある 84 のアベイラビリティーゾーンにまたがっており、オーストラリア、カナダ、インド、イスラエル、ニュージーランド、スペイン、スイス、およびアラブ首長国連邦 (UAE) に 24 アベイラビリティーゾーンと 8 AWS リージョンを追加する計画が発表されている。（2022年8月現在）

AWSのサービスは大きく２つに分けられ、「エッジサービス」と「リージョンサービス」がある。

エッジサービスはエッジロケーションで提供されるサービスであり、「Amazon Route 53」、「AWS Global Accelerator」、「Amazon CloudFront」、「AWS WAF」、「AWS Shield」がある。

一方、リージョンサービスはリージョンエリアで提供されるサービスであり、「Amazon EC2」、「Amazon VPC」、「Amazon RDS」、「Amazon S3」などがある。

エッジサービスを導入することで解決できる課題として以下がある。
- サーバーから遠い場所にいるユーザーの高レイテンシーでのサービス提供
- 突発的な大量のリクエストに対する対応
- DDos攻撃やウェブアプリケーションに対する攻撃
などがある。

## AWSのエッジサービスとコンテンツデリバリーネットワークについて
コンテンツデリバリーネットワークであるCloudFrontを用いた流れ図を以下に示している。
図から分かる通り、２回目以降はCloudFrontがコンテンツをキャッシュしており、オリジンサーバーにリクエストを送らず、ユーザーにレスポンスを返すことができており、レスポンス速度を向上させることができる。
![AWS Cloud (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/03c49d83-c86b-40e2-33b5-eee2a7674bf6.png)

とはいえ、全てのコンテンツをキャッシュしていいわけではないので、「静的コンテンツ」のみをキャッシュしていいように設定する必要がある。
「静的コンテンツ」・・・ユーザーのリクエストによって変わらないコンテンツのこと
「動的コンテンツ」・・・ユーザーのリクエストによって変わるコンテンツのこと

## Amazon CloudFrontハンズオン１

### S3の設定を行う
CloudFrontの設定の前にS3に静的コンテンツを格納する。
マネジメントコンソールでリージョンをバージニア北部に設定→S3を検索→バケットを作成を選択。
以下のように設定し、バケットを作成した。

バケット名：hfb-taiboy
リージョン：バージニア北部

次にS3に静的コンテンツをアップロードする。
ディレクトリ配置は以下のようになっている。

root/
　┣index.html
　┣static/
　　　┣image/cloudfront.png
　　　┣js/script.js
　　　┣css/styles.css


```HTML:index.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="icon" type="image/x-icon" href="static/image/cloudfront.png">
    <link href="static/css/styles.css" rel="stylesheet" type="text/css">
    <script src="static/js/script.js"></script>
  </head>
  <body>
    <form id="form1" action="#">
        <ul>
            <li>
              <img src="static/image/cloudfront.png" width="100" height="100">
            </li>
            <li>
              <p>Input Message</p>
              <textarea id="input_message" rows="4" cols="40"></textarea>
            </li>
            <li>
                <input type="button" onclick="func1()" value="Send" />
                <input type="button" onclick="func2()" value="All Clear"  />
            </li>
            <li>
                <br />
                <p>Output Message</p>
                <textarea id="output_message" rows="4" cols="40"></textarea>
            </li>
          </ul>
    </form>
  </body>
</html>
```


```javascript:script.js
function func1() {
  var apiurl = 'https://' + document.domain + '/api?input_text=' + document.getElementById("input_message").value;
  var request = new XMLHttpRequest();

  request.open('GET', apiurl, true);
  request.responseType = 'text';
  request.onload = function () {
      var data = this.response;
      document.getElementById("output_message").innerHTML = data;
  };
  request.send();
}

function func2() {
    document.getElementById("input_message").value = '';
    document.getElementById("output_message").innerHTML = '';
}

```

### CloudFrontの設定
CloudFrontの設定の大枠はディストリビューションといい、そのなかに「ビヘイビア」と「オリジン」がある。
このハンズオンでは、ビヘイビアはパスの設定、オリジンはバックエンドとなるオリジンを指定するものと考える。


マネジメントコンソールからCloudFrontを選択し、ディストリビューションを作成をクリックする。
### オリジン設定
オリジンドメインネーム：hfb-taiboy
S3バケットアクセス：はい
オリジンアクセスアイデンティティ：新しいOAIを作成
バケットポリシー：はい

※S3バケットアクセス：クラウドフロントからS3バケットにアクセスするためのポリシーを有効にする。
![スクリーンショット 2022-08-11 11.33.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/bbb0f0d6-e8c8-5c93-499a-b7f0ffae2be7.png)

### ビヘイビア設定
以下のように設定した。
ビューワープロトコルポリシー：redirect HTTP to HTTPS(HTTPSの通信のみに限りたいため)
キャッシュポリシー：Caching Disbabled（キャッシュさせない設定にしてみる。）


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/c4b6c545-f0f1-32a3-667a-727cd55f24ec.png)

### ディストリビューション設定
デフォルトルートオブジェクト：index.html(こうすることで　URLの最後にindex.htmlがなくてもアクセスできるようになる。)

ディストリビューション設定で「カスタムドメイン」や「SSL証明書」を設定することができる。（ピンクの枠）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/143cb98e-4d2d-fbce-247f-f4bbbf375e01.png)


### ドメインへアクセス
CloudFrontに表示されているドメインにアクセスする。
開発者ツールのNetworkタブを開く→画像を選択し、x-cacheの項目を見る

Miss from CloudFrontと書いており、CloudFrontでキャッシュがヒットしていないことが分かる。
なので、オリジンサーバーがCloudFrontを経由してレスポンスを返したということになる。

※x-cacheとはユーザーのリクエストがキャッシュにヒットしたかどうかを表す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/a2e06d0c-5e2b-6da1-6616-e04fa7635b51.png)

### Amazon CloudFront設定２
CloudFrontにキャッシュを持たせる。

### Cacheポリシーの設定項目
TTL settings:キャッシュ時間のコントロールが可能
- デフォルトTTL：オリジンがキャッシュコントロールヘッダーを指定しない場合に利用
- 最小TTL：CloudFront側でキャッシュすべき最小TTL
- 最大TTL：CloudFront側でキャッシュすべき最大TTL

Cache Key Contents：キャッシュのKeyが設定可能
- Headers:キャッシュのKeyとなるHTTPヘッダーを指定
- Cookies:キャッシュのKeyとなるクッキーを指定
- Query strings:キャッシュのKeyとなるクエリストリングを指定

CloudFrontのコンソール画面から→ポリシー→新しいポリシーを作成

名前：hfb-cachepolicy

これだけ入力し、ポリシーを作成をクリック

ポリシーができたらCloudFrontに設定する。
ディストリビューション→先ほどのディストリビューションを選択→ビヘイビアタブ→新しいビヘイビアを作成

パスパターン：static/*
ビューワープロトコルポリシー：Redirect HTTP to HTTPS
キャッシュポリシー：hfb-cachepolicy(先ほど作成したポリシー)

作成ボタンをクリック

以下のように設定できた。
static/*のリクエストはキャッシュポリシーhfb-cachepolicyを実行し、それ以外はcacheDisableポリシーを実行する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/4390197b-52da-1ea6-a695-f1b6d7d06777.png)

webアプリを更新して、開発者ツールの画像を選択してみると、x-cacheの部分に「Hit from cloudfront」とあるので、うまくキャッシュされていることが分かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/3ee16200-fa2c-89c1-f984-583e40932341.png)

## Amazon CloudFront 設定３（動的コンテンツの配信）
Amazon API Gateway とAWS Lambdaを用いた動的コンテンツとなる、APIの実装を行う。

### Lambda関数を作成
マネジメントコンソールからLambdaを開く→新しい関数を作成（右上のリージョンが「バージニア北部になっているか確認する。」）

関数名：　h4b-function
ランタイム：　Python3.8
と設定し、関数を作成する。

以下のコードを貼り付ける。
```Python:lambda.py
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
    }

```

次にこのラムダ関数に紐づいているロールにtranslateReadonlyポリシーをアタッチする。
設定→アクセス権限→ポリシーをアタッチ→検索窓にtranslateと検索し、translateReadonlyポリシーをアタッチする。

### APIGatewayを構築する
マネジメントコンソールから「APIGateway」と入力する。→リージョンが「バージニア北部」なのを確認する。→APIを作成するをクリック。→RESTAPIをクリック

プロトコル：新しいAPI
名前：h4b-api
エンドタイプ：リージョン
と設定した。

次に、
アクションをクリックし、メソッドを作成→プルダウンからGETを選び、横のチェックをクリック。

統合タイプ：Lambda関数
Lambdaプロキシ統合を使用にチェック
Lambdaリージョン：us-east-1(バージニア北部なので)
Lambda関数：h4b-function

に設定。

次にこのAPIに渡すクエリストリングを設定するため、メソッドリクエストを編集する。
URLクエリ文字列パラメータのタブからクエリ文字列の追加をクリックする。→「input_text」を入力し、チェックする。

次に、アクションからAPIのデプロイを選択
デプロイされるステージ：[新しいステージ]
ステージ名：api
と設定した。

URLの呼び出しのURLをクリックし、「?input_text=こんにちは世界」をURLの最後に付け加えることでHello Worldと返る。

ここまでくると、APIGatewayはうまく設定できた。

## 動的コンテンツをCloudFrontへ紐づける
AmazonAPIGatewayをCloudFrontに紐づける。
AmazonAPIGatewayでは「input_text」というクエリ文字列パラメータを設定したことから、CloudFront側でもそのクエリ文字列を設定しないといけない。

ユーザーからのリクエストは一旦、CloudFrontが受け取るため、「ヘッダー」「クッキー」「クエリストリングス」の情報はCloudFrontにて、フィルタリングされた状態でオリジンサーバーに送られる。なので、デフォルト設定のままだと、ユーザーが付与したクエリストリングスはオリジンサーバーには渡らないことになる。
ユーザーが付与したクエリストリングスをオリジンに送りたい場合はCloudFrontのフィルタリングにてホワイトリストを追加する必要があり、この設定を「オリジンリクエストポリシー」という。このオリジンリクエストポリシーはビヘイビアの設定項目の一部として行う。

### オリジンリクエストポリシーの作成
マネジメントコンソールからCloudFrontと検索する。→ポリシーを選択しオリジンリクエストポリシーのタブを選択する。→オリジンリクエストポリシーを作成をクリック。
以下のように設定した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/7f5dd154-9152-5190-4643-6ec533341939.png)

### オリジンの設定
ここまで来たら、ディストリビューションズに移り、自分が設定したCloudFrontに設定をアタッチする。
今回はAPIGatewayを新しいオリジンとして追加していきたいので、オリジンのタブを選択し、オリジンの作成をクリックする。

オリジンドメインネーム：https://「ご自身のドメイン」.us-east-1.amazonaws.com/
オリジンプロトコルポリシー：HTTPS only

### ビヘイビアの設定
このオリジンにアクセスするためのビヘイビアを設定する。

パスパターンはAPIGatewayのステージ名「api」とし、オリジンとオリジングループはAPIGWのドメインをコピペした。
ビューワープロトコルをHTTPSのみの「Redirect HTTP to HTTPS」とした。今回は動的コンテンツのため、キャッシュポリシーを「CashingDisabled」にし、
オリジンリクエストポリシーを先ほど作成した「h4b-originrequestpolicy」を選択した。
CloudFrontのマネジメントコンソールからビヘイビアタブを選択し、ビヘイビアを作成をクリック
以下のように設定した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/79872f01-05ae-0898-391d-2e9a54534ec6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/bfc23ede-3f0f-93bc-c178-c758aa918f0c.png)

## キャッシュコンテンツの確認
クラウドフロントのドメインネームを検索窓に貼り付け、アプリケーションを確認する。
下の画像から動的コンテンツのx-cacheがMiss from cloudfrontになっていることがわかる。

![スクリーンショット 2022-08-15 12.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/006d0066-2f0c-46da-5c92-dbf310a8294e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/2103233e-4a5e-442a-c512-ee9f342b3081.png)

## AWS WAFをAmazon CloudForntに紐づける
マネジメントコンソールからAWS WAFを検索（このときリージョンがグローバルになっていることを確認。）し、create WEB ACLをクリック。

name：h4b-waf
Resourse type：CloudFront distoributions

![スクリーンショット 2022-08-16 11.04.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/9ad9a45c-b97f-6190-1bf9-65be141197f2.png)

以上を設定したら、下へスクロールし、Associated AWS resourcesの項目でAdd AWS resourcesをクリック。

先ほど作成したCloudFrontを選択し、Addをクリックする。

![スクリーンショット 2022-08-16 11.04.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/6e548488-463f-1abc-92fe-22bacdea432f.png)

ここまでできたら、Nextボタンをクリック。
次にルールを設定する画面になるので、Add rules をクリックし、自分独自のルールを設定するので、「Add my own rules and rule group」を選択する。

name:h4b-rule
if a request: matches trhe statement
Inspect: Originates from a country in
country codes: Japan
Action:Block

Ip address to use to determine the country originの項目では、Source IP addressを選択しているが、IP address in headerにすると、ソースIPアドレス以外からIPアドレスを引っ張ることができ、例えばXFF(X-Forwarded-For)の中のIPアドレスを引っ張ることもできる。

X-Forwarded-Forとは、HTTPヘッダフィールドの1つであり、ロードバランサなどの機器を経由してWebサーバに接続するクライアントの送信元IPアドレスを特定する際のデファクトスタンダード。

日本からのアクセスをブロックしてみる。

![スクリーンショット 2022-08-16 11.05.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/abb9a923-a457-6ee5-0180-b0be2c676a2f.png)

![スクリーンショット 2022-08-16 11.07.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/3a2f72d1-9768-8112-f9f3-e71b4865e2fc.png)

ここまで入力したらAdd ruleをクリック。その次のプライオリティーやメトリクスはデフォルトのまま設定した。
サマリーの画面で、問題ないことを確認し、Create WEB ACLをクリック。

CloudFrontに上部の設定が反映されているか確認する。ドメインにアクセスしてみると、以下のようなページが表示されるため、
設定が正しくできていることがわかる。

![スクリーンショット 2022-08-16 11.10.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2040882/51c35650-1107-d453-e515-b750bcc03704.png)


次にマネージドルールを追加する。再度、AWS WAFと検索し、Wab ACLsのタブをクリックする。このとき、リージョンがグローバルになっていることを確認する。
先ほど作ったWebACLsを選択し、Rulesタブをクリック。右上のAddRuleからAdd managed rule groupsを選択する。AWS managed ruleからCore rule　setを選択する。この設定のまま、Add rulesをクリックする。プリオリティーもそのままでSaveをクリック。これでマネージドルールが設定できた。


## リソースの削除手順
Amazon CloudFrontの削除
↓
AWS WAFの削除
↓
Amazon API Gatewayの削除
↓
AWS Lambdaの削除
↓
Amazon S3の削除

### Amazon CloudFrontの削除
該当のディストリビューションを選択し、無効にする。直接、削除はできない。
しばらくして、ステータスが無効、状態がデプロイになったら、ページを更新し、削除を選択する。
次にキャッシュポリシー、オリジンリクエストポリシーも削除する。
ポリシータブを押して自身が作成したポリシーを削除。オリジンリクエストポリシーのタブに移動し、これも自身が作成した　ポリシーを削除する。

### Amazon S3の削除
該当のバケットを選択し、まず、「空にする」を選択後、削除する。