Application Gateway のカスタムエラーページを実装する

## はじめに
Azure には、Azure Load Balancer(ALB) という、トランスポートレイヤ(OSI Layer 4)で負荷分散してくれるロードバランサと、Application Gatewayという、アプリケーションレイヤー(OSI Layer 7)で負荷分散してくれるロードバランサがあります。
ALBはシンプルなインターフェイスでAzureマネージドサービスにも内包して使われているくらい汎用的なサービスになっていますが、Application GatewayはWebアプリケーション（HTTP/HTTPS）のみで利用されるサービスです。しかし、Webアプリケーションのレイヤにおいては、ALBとは比較にならないほどたくさんの機能を有しています。

私はよく仕事で Application Gateway を使います。 Application Gateway の諸々の機能についてはこれから順々に紹介できればと思いますが、今回はApplication Gatewayの機能で提供されている「カスタムエラーページ」を扱う機会がありましたので、紹介したいと思います。

## Application Gateway のカスタムエラーページ
まずはじめに、カスタムエラーページは Application Gateway が検知する下記のリターン値に対して適用することができます。

- HTTPステータスコード：502 → 無効なゲートウェイページ
  - トラフィックをルーティングするバックエンドが見つからない時
- HTTPステータスコード：403 → 未承認のアクセスページ
  - 悪意のあるトラフィックを検出し、そのトラフィックをブロックする時※WAF利用時

注意点として、Application Gateway のバックエンドサーバーでエラーが発生した場合はカスタムエラーページは表示されません。
Application Gateway の要求がバックエンドに到達できない場合に、カスタムエラーページを利用できるということになります。

また、設定単位は下記3つです。

- リスナーレベル
  - Application Gateway のリスナー（HTTP/HTTPS）に対するトラフィックに適用
- グローバルレベル
  - Application Gateway に展開されている全てのトラフィックに適用（現在はCLIのみ対応）
- 両方
  - 上記2つを設定することも可能（その場合リスナーレベルがクローバルレベルより優先される）

さらに、カスタムエラーページ（コンテンツ）自体にも下記の制約がありますので、覚えておきましょう。

- *.htm または *.html 拡張子タイプであること
- 1MB未満であること
- 内部または外部のイメージやCSSは参照可能だが、外部参照するリソースについてはパブリックアクセス可能な絶対リンク指定のみ可能（相対リンクは未サポート）

## カスタムエラーページを実装する

まず、適当な Application Gateway を作成します。
今回はカスタムエラーページの動作検証なので、Application Gateway 自体は非常にシンプルに構成します。
構成上バックエンドエンドプールは作りますが、実際のバックエンドは存在しないダミーのプ―ルとします。
また、フロントにはパブリックIPを付与し、”testagw.japaneast.cloudapp.azure.com”でアクセスできるようにしておきます。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/f002ddc4-7724-3918-3252-76791d9c5241.png)

さて、早速「testagw.japaneast.cloudapp.azure.com」にアクセスしてみます。
すると502のエラーぺ―ジに飛びますね。これは Application Gateway のデフォルトのエラーページですが、こちらのコンテンツをカスタマイズしていくことになります。

![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/9b5257bb-5cc7-d798-afb6-da3da3989322.png)

カスタムエラーページを設定するには、まず、Application Gateway のリスナーブレードに進みましょう。
対象のリスナーを選択すると「エラーページのURL」という設定がありますので、こちらを「はい」にします。
すると、502用と403用のカスタムエラーページパスの入力欄が表示されます。

![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/187439bb-5552-7c96-b5d4-a8b55de79970.png)

ここで一旦画面を戻り、カスタムエラーページを格納するストレージアカウントとコンテナを作成します。ここで作るストレージアカウントは、以下の条件を満たす必要があります。

- ストレージアカウントのファイアウォールを無効化する
- コンテナのアクセスポリシを「コンテナー(コンテナーと BLOB の匿名読み取りアクセス)」に設定する

![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/839437f9-9a74-c06f-dab8-8efb023425a8.png)

カスタムエラー用のHTMLを配置します。今回作成したHTMLは下記です。（全然作りこんでません・・・）

```
<html>
	<head>
		<title>502 (ばっとげーとうぇい)</title>
	</head>
	<body bgcolor="yellow">
		<center><h1><font color="red"><b>502 ばっとげーとうぇい～～<br>↑↑ウェイウェイ↑↑</b></font></h1></center>
		<center>カスタムしたエラーページだYO！</center>
	</body>
</html>
```
作成したHTMLファイルをコンテナに配置します。

![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/32d5356c-cf19-20e9-1262-895b5a3ca03c.png)

最後に、先ほどの Application Gateway の「エラーページのURL」にHTMLの格納パスを貼り付けます。

![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/596e0e24-9cad-a199-5dc9-5600fae702d7.png)

これで準備は完了です！
改めて「testagw.japaneast.cloudapp.azure.com」にアクセスしてみましょう。

![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/5bb27006-a651-1e45-da4b-14ca52c02d1a.png)

カスタムエラーページが表示されました！
が、、文字化けしてしまいました。。
HTMLファイルのエンコードを修正して再度ストレージアカウントのコンテナに登録、、しても、実は結果が変わりません。

その理由はApplication GatewayがHTMLファイルをキャッシュしているからです。Microsoftサポートによると、このキャッシュは意図的にリセットすることができず、Application Gatewayのリセット時や不定のタイミングでクリアされるそうです。

つまり利用者側で明示的に変更したい場合は、HTMLファイル名を変更して再登録するしかありません。
カスタムエラーページのURLはApplication Gateway側でラップされるためユーザに伝わることはありませんので実影響はありませんが、運用する側としては考慮しないといけませんね。
ということで、改めて「error2.html」とリネームしてストレージアクアントに再登録するとともに、Application Gateway 側の「エラーページのURL」のパスも変更します。


![キャプチャ8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/e6f0c088-cdf1-9e9c-21db-85122bd02097.png)


![キャプチャ9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/7a235bb8-dd7a-7552-c79f-4042183c66bd.png)

はい！無事に表示することができました！

![キャプチャ10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1199489/002b97f9-1595-ab84-ea54-c5f4ccacbe35.png)



## カスタムエラーページの注意点

カスタムエラーページを運用する際に注意すべきことは2つです。

#### 1) カスタムエラーページは Application Gateway 内にキャッシュされる
上述した通りですが、カスタムエラーページを差し替える場合は、同名ファイルで上書きするのではなく、別名のファイルを再登録するとともに Application Gateway 側の「エラーページのURL」のパスを変更する必要があります。さらに、キャッシュが消えるタイミングは不定であるため、ストレージアカウントは常に接続可能な状態にしておく必要があります。

#### 2) HTMLファイル格納用ストレージアカウントのアクセスポリシー設定が緩すぎる
こちらも上述しましたが、ストレージアカウントには下記の設定が必要です。

- ストレージアカウントのファイアウォールを無効化する
- コンテナのアクセスポリシを「コンテナー(コンテナーと BLOB の匿名読み取りアクセス)」に設定する

この2つの設定ですが、Microsoft Defender for Cloud や Prisma Cloud といったCSPMツールを導入している場合に、”リスク高”でアラート検知されるレベルの設定になります。
Azureの仕様とはいえ、エンタープライズ利用の場合は、コンプライアンス部門から説明を求められることがあるでしょう。
これに対して、漏洩リスクと改竄リスクを考えてみました。

##### 漏洩リスク
カスタムエラーページ自体、個人情報なんてまずありませんし、秘匿情報を載せることもないでしょうから、そもそも漏洩リスクは無いと考えることができると思います。
ただ、パブリックに公開され誰からもアクセス可能な情報にはなりますので、それを考慮した上でコンテンツを作成する必要があると思います。

##### 改竄リスク
カスタムエラーページの改竄や、このストレージアカウントに別のファイルを登録され悪用されるリスクを考えてみます。コンテナのアクセスポリシ「コンテナー(コンテナーと BLOB の匿名読み取りアクセス)」というのは、読み取りアクセスについては不特定多数で可能となりますが、更新や登録といった操作については、ストレージアカウントのアクセスキーが必要になります。このアクセスキーを守ることが重要になります。
アクセスキーは「閲覧者」ロールでは参照することができませんが、ストレージアカウントの更新権限があれば参照可能です。最小権限の原則に則り防御することが重要です。
また、「読み取り専用ロック」も効果的です。ストレージカウントに対して「読み取り専用ロック」を付けることで、ロックを解除するまでアクセスキーを参照することができません。リソースロックの操作は「共同作成者」ロールでも不可で「所有者」相当のロールが必要になります。これにより更に強固に守ることができます。ここまで考慮しておけば、改竄リスクも低くなると言えます。

こういった考慮も必要になる機能になりますので、留意いただければと思います。

## おわりに
今回は、Application Gateway のカスタムエラーページについて解説しました。
一見シンプルに使えそうに見えますが、実際使おうとすると考慮しないといけないことが多々ありますので、注意事項をよく考えながら利用するようにしましょう。

・・・ちなみに「Application Gateway」って、みなさんどう略してますか？
AppGW？AGW？APG？
いつも悩んでいます・・・・・ご意見いただけると幸いです。