[Azure] Application Gateway (WAF) で HTTP/2 を使うときの SSL ポリシーのお話

## TL;DR

- Application Gateway のフロント側で喋る TLS (SSL ポリシー) の話
- Application Gateway で HTTP/2 を有効化しただけでは TLSv1.2 の SSL ポリシーに自動的に切り替わらない
- また、HTTP/2 の RFC でブラックリストに規定された暗号スイートを、自動的に SSL ポリシーから排除することもしない
- なので HTTP/2 を有効化するときは、下に紹介する PowerShell スクリプトを参考に、HTTP/2 に適した自前の SSL ポリシーを定義しましょう

## SSL ポリシーとは？

Application Gateway (SKU によっては WAF とも呼ばれます) のフロント エンドで TLS を喋る場合、その最低 TLS バージョンや、利用可能な暗号スイートをユーザーが自由に決めることが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127839/4204b255-b2d0-a3b3-a6c0-713c81cbc507.png)
※この図だと TLS 1.1 以上を対応するクライアントじゃないと許可しない構成

Azure Application Gateway では、最低 TLS バージョン、順序に意味のある暗号スイートのリストの組を「SSL ポリシー」と呼んでいます。
SSL ポリシーの概要については、[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/application-gateway/application-gateway-ssl-policy-overview) を見てもらうのが早いかと思います。

また、各最低 TLS バージョンごとに、Microsoft がおすすめの暗号スイートを定義しており、「定義済み SSL ポリシー」の名前でこれを提供しています。
例えば、最低 TLS バージョンを TLS v1.2 としたとき、その定義済み SSL ポリシーの暗号スイートは以下の通りです。
 
```
PS> $policies = Get-AzApplicationGatewaySslPredefinedPolicy
PS> $policies | where MinProtocolVersion -eq TLSv1_2

Name: AppGwSslPolicy20170401S
MinProtocolVersion: TLSv1_2
CipherSuites:
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
    TLS_RSA_WITH_AES_256_GCM_SHA384
    TLS_RSA_WITH_AES_128_GCM_SHA256
    TLS_RSA_WITH_AES_256_CBC_SHA256
    TLS_RSA_WITH_AES_128_CBC_SHA256
    TLS_RSA_WITH_AES_256_CBC_SHA
    TLS_RSA_WITH_AES_128_CBC_SHA
```

## HTTP/2 × SSL ポリシーにまつわる問題

### HTTPS/2 を有効にしたとき SSL ポリシーが変わらない問題

Application Gateway では HTTP/2 がサポートされています。
HTTTP/2 を有効にするには、以下の Powershell コマンドを実行します。

```powershell
$appgw = Get-AzApplicationGateway -Name appgw-name -ResourceGroupName rg
$appgw.EnableHttp2 = $true
$appgw | Set-AzApplicationGateway
```

HTTP/2 は RFC 7540 (https://summerwind.jp/docs/rfc7540/) で規定されているように、TLS v1.2 以上を利用しなくてはいけません (RFC での MUST 条件) ので、上のコマンドを叩くと勝手に TLS v1.2 の SSL ポリシーに反映してもらえるのかと思うかもしれませんが、実際はそうなっていません。

> 9.2. TLS の機能の利用
>
> HTTP/2 の実装は、TLS 上の HTTP/2 に対して TLS バージョン1.2 [TLS12] 以上を使用しなければなりません (MUST)。[TLSBCP] にある TLS の一般的な利用ガイダンスに即すべきであり (SHOULD)、加えて HTTP/2 特有の制限もあります。

試しに、次のような SSL ポリシー状態の Application Gateway (v2 SKU)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127839/4f6c95b4-0a21-b5d4-25bf-45473f71000a.png)

に対して HTTP/2 を有効化すると…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127839/4f6c95b4-0a21-b5d4-25bf-45473f71000a.png)

**SSL ポリシーが変わらない！！！！**

これは良くないですね。

### HTTP/2 用の SSL ポリシーが提供されていない問題

加えて、HTTP/2 の RFC 7540 の「9.2.2. TLS 1.2 の暗号スイート」を見てもらいたいのですが、RFC 7540 では、セキュリティ上の懸念から、特定の暗号スイートを使用しないよう喚起しています。

> 9.2.2. TLS 1.2 の暗号スイート
>
> TLS 1.2 上の HTTP/2 のデプロイメントは、暗号スイートブラックリスト (付録A) に記載された暗号スイートを使用すべきではありません (SHOULD NOT)。

上で見た TLS v1.2 の定義済み SSL ポリシーは、HTTP/2 のブラックリストを考慮しておらず、ブラックリストに載っている暗号スイートも使ってしまっています。
HTTP/2 を使う場合は、この点も注意しましょう。

## 解決策

ということで、HTTP/2 を有効化するときは、一緒に SSL ポリシーも正しく構成しましょう。

方針としては 2 パターンあります。

1. TLS v1.2 の「定義済み SSL ポリシー」をベースに、ブラックリストの暗号スイートを削除したものを利用する
2. 自分でカスタマイズした (TLS v1.2 を最低バージョンにもつ) SSL ポリシーをベースに、ブラックリストの暗号スイートを削除したものを利用する

それぞれを PowerShell で実装したスクリプトを紹介しますので、参考にしてみてください。

1. [Set a custom-defined SSL Policy to Application Gateway based on the pre-defined SSL policy (TLS v1.2) but being filtered out cipher suites of the blacklist on HTTP/2 RFC (RFC 7540)](https://gist.github.com/OpenJNY/e946bb1fa1df65c661b6fba07e68097e)
2. [Filter out cipher suites of blacklist in HTTP/2 RFC from custom-defined SSL Policy of Application Gateway.](https://gist.github.com/OpenJNY/3090e9df161a7009d8f492e25513292d)

1 番目のものをちょっと紹介すると、こんな感じのコードになってます。
(定義済み SSL ポリシーを取得してフィルターにかけてるだけなので、解説は要らないかと思います)

```powershell
$appgw = Get-AzApplicationGateway -Name $Name -ResourceGroupName $ResourceGroupName

$policies = Get-AzApplicationGatewaySslPredefinedPolicy
$policy = $policies | where MinProtocolVersion -eq TLSv1_2

$filteredCipherSuites = $policy.CipherSuites | where { -not ($CipherSuitesBlacklistOnHTTP2 -contains $_) }

Set-AzApplicationGatewaySslPolicy -ApplicationGateway $appgw `
    -PolicyType Custom `
    -MinProtocolVersion "TLSv1_2" `
    -CipherSuite $filteredCipherSuites

Set-AzApplicationGateway -ApplicationGateway $appgw 
```